Compare commits

...

47 Commits

Author SHA1 Message Date
Brent Westbrook
5810961b7f wip add ScopeKind::DunderClassCell like ruff 2025-08-25 13:55:17 -04:00
Brent Westbrook
d0bcf56bd9 Improve diff rendering for notebooks (#20036)
## Summary

As noted in a code TODO, our `Diff` rendering code previously didn't
have any
special handling for notebooks. This was particularly obvious when the
diffs
were rendered right next to the corresponding diagnostic because the
diagnostic
used cell-based line numbers, while the diff was still using line
numbers from
the concatenated source. This PR updates the diff rendering to handle
notebooks
too.

The main improvements shown in the example below are:

- Line numbers are now remapped to be relative to their cell
- Context lines from other cells are suppressed

```
error[unused-import][*]: `math` imported but unused                             
 --> notebook.ipynb:cell 2:2:8                                                  
  |                                                                             
1 | # cell 2                                                                    
2 | import math                                                                 
  |        ^^^^                                                                 
3 |                                                                             
4 | print('hello world')                                                        
  |                                                                             
help: Remove unused import: `math`                                              
                                                                                
ℹ Safe fix                                                                      
1 1 | # cell 2                                                                  
2   |-import math                                                               
3 2 |                                                                           
4 3 | print('hello world')                                                      
```

I tried a few different approaches here before finally just splitting
the notebook into separate text ranges by cell and diffing each one
separately. It seems to work and passes all of our tests, but I don't
know if it's actually enforced anywhere that a single edit doesn't span
cells. Such an edit would silently be dropped right now since it would
fail the `contains_range` check. I also feel like I may have overlooked
an existing way to partition a file into cells like this.

## Test Plan

Existing notebook tests, plus a new one in `ruff_db`
2025-08-25 09:20:42 -04:00
Eric Jolibois
f9bbee33f6 [ty] validate constructor call of TypedDict (#19810)
## Summary
Implement validation for `TypedDict` constructor calls and dictionary
literal assignments, including support for `total=False` and proper
field management.
Also add support for `Required` and `NotRequired` type qualifiers in
`TypedDict` classes, along with proper inheritance behavior and the
`total=` parameter.
Support both constructor calls and dict literal syntax

part of https://github.com/astral-sh/ty/issues/154

### Basic Required Field Validation
```py
class Person(TypedDict):
    name: str
    age: int | None

# Error: Missing required field 'name' in TypedDict `Person` constructor
incomplete = Person(age=25)

# Error: Invalid argument to key "name" with declared type `str` on TypedDict `Person`
wrong_type = Person(name=123, age=25)

# Error: Invalid key access on TypedDict `Person`: Unknown key "extra"
extra_field = Person(name="Bob", age=25, extra=True)
```
<img width="773" height="191" alt="Screenshot 2025-08-07 at 17 59 22"
src="https://github.com/user-attachments/assets/79076d98-e85f-4495-93d6-a731aa72a5c9"
/>

### Support for `total=False`
```py
class OptionalPerson(TypedDict, total=False):
    name: str
    age: int | None

# All valid - all fields are optional with total=False
charlie = OptionalPerson()
david = OptionalPerson(name="David")
emily = OptionalPerson(age=30)
frank = OptionalPerson(name="Frank", age=25)

# But type validation and extra fields still apply
invalid_type = OptionalPerson(name=123)  # Error: Invalid argument type
invalid_extra = OptionalPerson(extra=True)  # Error: Invalid key access
```

### Dictionary Literal Validation
```py
# Type checking works for both constructors and dict literals
person: Person = {"name": "Alice", "age": 30}

reveal_type(person["name"])  # revealed: str
reveal_type(person["age"])   # revealed: int | None

# Error: Invalid key access on TypedDict `Person`: Unknown key "non_existing"
reveal_type(person["non_existing"])  # revealed: Unknown
```

### `Required`, `NotRequired`, `total`
```python
from typing import TypedDict
from typing_extensions import Required, NotRequired

class PartialUser(TypedDict, total=False):
    name: Required[str]      # Required despite total=False
    age: int                 # Optional due to total=False
    email: NotRequired[str]  # Explicitly optional (redundant)

class User(TypedDict):
    name: Required[str]      # Explicitly required (redundant)
    age: int                 # Required due to total=True
    bio: NotRequired[str]    # Optional despite total=True

# Valid constructions
partial = PartialUser(name="Alice")  # name required, age optional
full = User(name="Bob", age=25)      # name and age required, bio optional

# Inheritance maintains original field requirements
class Employee(PartialUser):
    department: str                  # Required (new field)
    # name: still Required (inherited)
    # age: still optional (inherited)

emp = Employee(name="Charlie", department="Engineering")  # 
Employee(department="Engineering")  # 
e: Employee = {"age": 1}  # 
```

<img width="898" height="683" alt="Screenshot 2025-08-11 at 22 02 57"
src="https://github.com/user-attachments/assets/4c1b18cd-cb2e-493a-a948-51589d121738"
/>

## Implementation
The implementation reuses existing validation logic done in
https://github.com/astral-sh/ruff/pull/19782

### ℹ️ Why I did NOT synthesize an `__init__` for `TypedDict`:

`TypedDict` inherits `dict.__init__(self, *args, **kwargs)` that accepts
all arguments.
The type resolution system finds this inherited signature **before**
looking for synthesized members.
So `own_synthesized_member()` is never called because a signature
already exists.

To force synthesis, you'd have to override Python’s inheritance
mechanism, which would break compatibility with the existing ecosystem.

This is why I went with ad-hoc validation. IMO it's the only viable
approach that respects Python’s
inheritance semantics while providing the required validation.

### Refacto of `Field`

**Before:**
```rust
struct Field<'db> {
    declared_ty: Type<'db>,
    default_ty: Option<Type<'db>>,     // NamedTuple and dataclass only
    init_only: bool,                   // dataclass only  
    init: bool,                        // dataclass only
    is_required: Option<bool>,         // TypedDict only
}
```

**After:**
```rust
struct Field<'db> {
    declared_ty: Type<'db>,
    kind: FieldKind<'db>,
}

enum FieldKind<'db> {
    NamedTuple { default_ty: Option<Type<'db>> },
    Dataclass { default_ty: Option<Type<'db>>, init_only: bool, init: bool },
    TypedDict { is_required: bool },
}
```

## Test Plan
Updated Markdown tests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-25 14:45:52 +02:00
Dhruv Manilawala
376e3ff395 [ty] Limit argument expansion size for overload call evaluation (#20041)
## Summary

This PR limits the argument type expansion size for an overload call
evaluation to 512.

The limit chosen is arbitrary but I've taken the 256 limit from Pyright
into account and bumped it x2 to start with.

Initially, I actually started out by trying to refactor the entire
argument type expansion to be lazy. Currently, expanding a single
argument at any position eagerly creates the combination (argument
lists) and returns that (`Vec<CallArguments>`) but I thought we could
make it lazier by converting the return type of `expand` from
`Iterator<Item = Vec<CallArguments>>` to `Iterator<Item = Iterator<Item
= CallArguments>>` but that's proving to be difficult to implement
mainly because we **need** to maintain the previous expansion to
generate the next expansion which is the main reason to use
`std::iter::successors` in the first place.

Another approach would be to eagerly expand all the argument types and
then use the `combinations` from `itertools` to generate the
combinations but we would need to find the "boundary" between arguments
lists produced from expanding argument at position 1 and position 2
because that's important for the algorithm.

Closes: https://github.com/astral-sh/ty/issues/868

## Test Plan

Add test case to demonstrate the limit along with the diagnostic
snapshot stating that the limit has been reached.
2025-08-25 09:43:04 +00:00
renovate[bot]
b57cc5be33 Update actions/checkout digest to ff7abcd (#20061)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| actions/checkout | action | digest | `08c6903` -> `ff7abcd` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2025-08-25 11:04:07 +05:30
renovate[bot]
3c1fe12259 Update Rust crate regex-automata to v0.4.10 (#20068)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[regex-automata](https://redirect.github.com/rust-lang/regex/tree/master/regex-automata)
([source](https://redirect.github.com/rust-lang/regex)) |
workspace.dependencies | patch | `0.4.9` -> `0.4.10` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>rust-lang/regex (regex-automata)</summary>

###
[`v0.4.10`](https://redirect.github.com/rust-lang/regex/compare/regex-automata-0.4.9...regex-automata-0.4.10)

[Compare
Source](https://redirect.github.com/rust-lang/regex/compare/regex-automata-0.4.9...regex-automata-0.4.10)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:44:59 +05:30
renovate[bot]
e4289deb5a Update Rust crate regex to v1.11.2 (#20067)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [regex](https://redirect.github.com/rust-lang/regex) |
workspace.dependencies | patch | `1.11.1` -> `1.11.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>rust-lang/regex (regex)</summary>

###
[`v1.11.2`](https://redirect.github.com/rust-lang/regex/blob/HEAD/CHANGELOG.md#1112-2025-08-24)

[Compare
Source](https://redirect.github.com/rust-lang/regex/compare/1.11.1...1.11.2)

\===================
This is a new patch release of `regex` with some minor fixes. A larger
number
of typo or lint fix patches were merged. Also, we now finally recommend
using
`std::sync::LazyLock`.

Improvements:

- [BUG
#&#8203;1217](https://redirect.github.com/rust-lang/regex/issues/1217):
  Switch recommendation from `once_cell` to `std::sync::LazyLock`.
- [BUG
#&#8203;1225](https://redirect.github.com/rust-lang/regex/issues/1225):
  Add `DFA::set_prefilter` to `regex-automata`.

Bug fixes:

- [BUG
#&#8203;1165](https://redirect.github.com/rust-lang/regex/pull/1150):
Remove `std` dependency from `perf-literal-multisubstring` crate
feature.
- [BUG
#&#8203;1165](https://redirect.github.com/rust-lang/regex/pull/1165):
  Clarify the meaning of `(?R)$` in the documentation.
- [BUG
#&#8203;1281](https://redirect.github.com/rust-lang/regex/pull/1281):
Remove `fuzz/` and `record/` directories from published crate on
crates.io.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:44:47 +05:30
renovate[bot]
dbbcb7f452 Update cargo-bins/cargo-binstall action to v1.15.1 (#20075)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cargo-bins/cargo-binstall](https://redirect.github.com/cargo-bins/cargo-binstall)
| action | minor | `v1.14.4` -> `v1.15.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>cargo-bins/cargo-binstall (cargo-bins/cargo-binstall)</summary>

###
[`v1.15.1`](https://redirect.github.com/cargo-bins/cargo-binstall/releases/tag/v1.15.1)

[Compare
Source](https://redirect.github.com/cargo-bins/cargo-binstall/compare/v1.15.0...v1.15.1)

*Binstall is a tool to fetch and install Rust-based executables as
binaries. It aims to be a drop-in replacement for `cargo install` in
most cases. Install it today with `cargo install cargo-binstall`, from
the binaries below, or if you already have it, upgrade with `cargo
binstall cargo-binstall`.*

##### In this release:

- fix failure to create settings manifest file
([#&#8203;2268](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2268)
[#&#8203;2271](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2271))
- fix race condition in creating, loading and writing of settings
manifest file
([#&#8203;2272](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2272))
- fix infinite hang on `--self-install` due to the quickinstall consent
([#&#8203;2269](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2269)
[#&#8203;2273](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2273))

###
[`v1.15.0`](https://redirect.github.com/cargo-bins/cargo-binstall/releases/tag/v1.15.0)

[Compare
Source](https://redirect.github.com/cargo-bins/cargo-binstall/compare/v1.14.4...v1.15.0)

*Binstall is a tool to fetch and install Rust-based executables as
binaries. It aims to be a drop-in replacement for `cargo install` in
most cases. Install it today with `cargo install cargo-binstall`, from
the binaries below, or if you already have it, upgrade with `cargo
binstall cargo-binstall`.*

##### In this release:

- confirm on the first time using quickinstall
([#&#8203;2223](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2223))

##### Other changes:

- upgrade dependencies

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:43:27 +05:30
renovate[bot]
c6dfdb1d39 Update Rust crate url to v2.5.7 (#20073)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [url](https://redirect.github.com/servo/rust-url) |
workspace.dependencies | patch | `2.5.4` -> `2.5.7` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>servo/rust-url (url)</summary>

###
[`v2.5.5`](https://redirect.github.com/servo/rust-url/releases/tag/v2.5.5)

[Compare
Source](https://redirect.github.com/servo/rust-url/compare/v2.5.4...v2.5.5)

##### What's Changed

- ci: downgrade crates when building for Rust 1.67.0 by
[@&#8203;mxinden](https://redirect.github.com/mxinden) in
[https://github.com/servo/rust-url/pull/1003](https://redirect.github.com/servo/rust-url/pull/1003)
- ci: run unit tests with sanitizers by
[@&#8203;mxinden](https://redirect.github.com/mxinden) in
[https://github.com/servo/rust-url/pull/1002](https://redirect.github.com/servo/rust-url/pull/1002)
- fix small typo by [@&#8203;hkBst](https://redirect.github.com/hkBst)
in
[https://github.com/servo/rust-url/pull/1011](https://redirect.github.com/servo/rust-url/pull/1011)
- chore: fix clippy errors on main by
[@&#8203;dsherret](https://redirect.github.com/dsherret) in
[https://github.com/servo/rust-url/pull/1019](https://redirect.github.com/servo/rust-url/pull/1019)
- perf: remove heap allocation in parse\_query by
[@&#8203;dsherret](https://redirect.github.com/dsherret) in
[https://github.com/servo/rust-url/pull/1020](https://redirect.github.com/servo/rust-url/pull/1020)
- perf: slightly improve parsing a port by
[@&#8203;dsherret](https://redirect.github.com/dsherret) in
[https://github.com/servo/rust-url/pull/1022](https://redirect.github.com/servo/rust-url/pull/1022)
- perf: improve to\_file\_path() by
[@&#8203;dsherret](https://redirect.github.com/dsherret) in
[https://github.com/servo/rust-url/pull/1018](https://redirect.github.com/servo/rust-url/pull/1018)
- perf: make parse\_scheme slightly faster by
[@&#8203;dsherret](https://redirect.github.com/dsherret) in
[https://github.com/servo/rust-url/pull/1025](https://redirect.github.com/servo/rust-url/pull/1025)
- update LICENSE-MIT by
[@&#8203;wmjae](https://redirect.github.com/wmjae) in
[https://github.com/servo/rust-url/pull/1029](https://redirect.github.com/servo/rust-url/pull/1029)
- perf: url encode path segments in longer string slices by
[@&#8203;dsherret](https://redirect.github.com/dsherret) in
[https://github.com/servo/rust-url/pull/1026](https://redirect.github.com/servo/rust-url/pull/1026)
- Disable the default features on serde by
[@&#8203;rilipco](https://redirect.github.com/rilipco) in
[https://github.com/servo/rust-url/pull/1033](https://redirect.github.com/servo/rust-url/pull/1033)
- docs: base url relative join by
[@&#8203;tisonkun](https://redirect.github.com/tisonkun) in
[https://github.com/servo/rust-url/pull/1013](https://redirect.github.com/servo/rust-url/pull/1013)
- perf: remove heap allocation in parse\_host by
[@&#8203;dsherret](https://redirect.github.com/dsherret) in
[https://github.com/servo/rust-url/pull/1021](https://redirect.github.com/servo/rust-url/pull/1021)
- Update tests to Unicode 16.0 by
[@&#8203;hsivonen](https://redirect.github.com/hsivonen) in
[https://github.com/servo/rust-url/pull/1045](https://redirect.github.com/servo/rust-url/pull/1045)
- Add some some basic functions to `Mime` by
[@&#8203;mrobinson](https://redirect.github.com/mrobinson) in
[https://github.com/servo/rust-url/pull/1047](https://redirect.github.com/servo/rust-url/pull/1047)
- ran `cargo clippy --fix -- -Wclippy::use_self` by
[@&#8203;mrobinson](https://redirect.github.com/mrobinson) in
[https://github.com/servo/rust-url/pull/1048](https://redirect.github.com/servo/rust-url/pull/1048)
- Fix MSRV and clippy CI by
[@&#8203;Manishearth](https://redirect.github.com/Manishearth) in
[https://github.com/servo/rust-url/pull/1058](https://redirect.github.com/servo/rust-url/pull/1058)
- Update `Url::domain` docs to show that it includes subdomain by
[@&#8203;supercoolspy](https://redirect.github.com/supercoolspy) in
[https://github.com/servo/rust-url/pull/1057](https://redirect.github.com/servo/rust-url/pull/1057)
- set\_hostname should error when encountering colon ':' by
[@&#8203;edgul](https://redirect.github.com/edgul) in
[https://github.com/servo/rust-url/pull/1060](https://redirect.github.com/servo/rust-url/pull/1060)
- version bump to 2.5.5 by
[@&#8203;edgul](https://redirect.github.com/edgul) in
[https://github.com/servo/rust-url/pull/1061](https://redirect.github.com/servo/rust-url/pull/1061)

##### New Contributors

- [@&#8203;mxinden](https://redirect.github.com/mxinden) made their
first contribution in
[https://github.com/servo/rust-url/pull/1003](https://redirect.github.com/servo/rust-url/pull/1003)
- [@&#8203;hkBst](https://redirect.github.com/hkBst) made their first
contribution in
[https://github.com/servo/rust-url/pull/1011](https://redirect.github.com/servo/rust-url/pull/1011)
- [@&#8203;wmjae](https://redirect.github.com/wmjae) made their first
contribution in
[https://github.com/servo/rust-url/pull/1029](https://redirect.github.com/servo/rust-url/pull/1029)
- [@&#8203;rilipco](https://redirect.github.com/rilipco) made their
first contribution in
[https://github.com/servo/rust-url/pull/1033](https://redirect.github.com/servo/rust-url/pull/1033)
- [@&#8203;tisonkun](https://redirect.github.com/tisonkun) made their
first contribution in
[https://github.com/servo/rust-url/pull/1013](https://redirect.github.com/servo/rust-url/pull/1013)
- [@&#8203;supercoolspy](https://redirect.github.com/supercoolspy) made
their first contribution in
[https://github.com/servo/rust-url/pull/1057](https://redirect.github.com/servo/rust-url/pull/1057)

**Full Changelog**:
https://github.com/servo/rust-url/compare/v2.5.4...v2.5.5

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:42:38 +05:30
renovate[bot]
c65029f9a5 Update Rust crate syn to v2.0.106 (#20070)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [syn](https://redirect.github.com/dtolnay/syn) |
workspace.dependencies | patch | `2.0.104` -> `2.0.106` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>dtolnay/syn (syn)</summary>

###
[`v2.0.106`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.106)

[Compare
Source](https://redirect.github.com/dtolnay/syn/compare/2.0.105...2.0.106)

- Replace `~const` syntax with `[const]` conditionally const syntax in
trait bounds
([#&#8203;1896](https://redirect.github.com/dtolnay/syn/issues/1896),
[rust-lang/rust#139858](https://redirect.github.com/rust-lang/rust/pull/139858))
- Support conditionally const impl Trait types
([#&#8203;1897](https://redirect.github.com/dtolnay/syn/issues/1897))
- Reject polarity modifier and lifetime binder used in the same trait
bound
([#&#8203;1899](https://redirect.github.com/dtolnay/syn/issues/1899),
[rust-lang/rust#127054](https://redirect.github.com/rust-lang/rust/pull/127054))
- Parse const trait bounds with bound lifetimes
([#&#8203;1902](https://redirect.github.com/dtolnay/syn/issues/1902))
- Parse bound lifetimes with lifetime bounds
([#&#8203;1903](https://redirect.github.com/dtolnay/syn/issues/1903))
- Allow type parameters and const parameters in trait bounds and generic
closures
([#&#8203;1904](https://redirect.github.com/dtolnay/syn/issues/1904),
[#&#8203;1907](https://redirect.github.com/dtolnay/syn/issues/1907),
[#&#8203;1908](https://redirect.github.com/dtolnay/syn/issues/1908),
[#&#8203;1909](https://redirect.github.com/dtolnay/syn/issues/1909))

###
[`v2.0.105`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.105)

[Compare
Source](https://redirect.github.com/dtolnay/syn/compare/2.0.104...2.0.105)

- Disallow "negative" inherent impls like `impl !T {}`
([#&#8203;1881](https://redirect.github.com/dtolnay/syn/issues/1881),
[rust-lang/rust#144386](https://redirect.github.com/rust-lang/rust/pull/144386))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:41:48 +05:30
renovate[bot]
a0bba718f6 Update Rust crate tracing-indicatif to v0.3.13 (#20072)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[tracing-indicatif](https://redirect.github.com/emersonford/tracing-indicatif)
| workspace.dependencies | patch | `0.3.12` -> `0.3.13` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>emersonford/tracing-indicatif (tracing-indicatif)</summary>

###
[`v0.3.13`](https://redirect.github.com/emersonford/tracing-indicatif/blob/HEAD/CHANGELOG.md#0313---2025-08-15)

[Compare
Source](https://redirect.github.com/emersonford/tracing-indicatif/compare/0.3.12...0.3.13)

- eliminate panics on internal lock poison

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:41:33 +05:30
renovate[bot]
1eab4dbd95 Update Rust crate thiserror to v2.0.16 (#20071)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [thiserror](https://redirect.github.com/dtolnay/thiserror) |
workspace.dependencies | patch | `2.0.12` -> `2.0.16` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>dtolnay/thiserror (thiserror)</summary>

###
[`v2.0.16`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.16)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.15...2.0.16)

- Add to "no-std" crates.io category
([#&#8203;429](https://redirect.github.com/dtolnay/thiserror/issues/429))

###
[`v2.0.15`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.15)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.14...2.0.15)

- Prevent `Error::provide` API becoming unavailable from a future new
compiler lint
([#&#8203;427](https://redirect.github.com/dtolnay/thiserror/issues/427))

###
[`v2.0.14`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.14)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.13...2.0.14)

- Allow build-script cleanup failure with NFSv3 output directory to be
non-fatal
([#&#8203;426](https://redirect.github.com/dtolnay/thiserror/issues/426))

###
[`v2.0.13`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.13)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.12...2.0.13)

- Documentation improvements

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:41:18 +05:30
renovate[bot]
3eb3c3572b Update astral-sh/setup-uv action to v6.6.0 (#20074)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | minor | `v6.4.3` -> `v6.6.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>astral-sh/setup-uv (astral-sh/setup-uv)</summary>

###
[`v6.6.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.6.0):
🌈 Support for .tools-versions

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.5.0...v6.6.0)

##### Changes

This release adds support for [asdf](https://asdf-vm.com/)
`.tool-versions` in the `version-file` input

##### 🐛 Bug fixes

- Add log message before long API calls to GitHub
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;530](https://redirect.github.com/astral-sh/setup-uv/issues/530))

##### 🚀 Enhancements

- Add support for .tools-versions
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;531](https://redirect.github.com/astral-sh/setup-uv/issues/531))

##### 🧰 Maintenance

- Bump dependencies
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;532](https://redirect.github.com/astral-sh/setup-uv/issues/532))
- chore: update known versions for 0.8.12
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;529](https://redirect.github.com/astral-sh/setup-uv/issues/529))
- chore: update known versions for 0.8.11
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;526](https://redirect.github.com/astral-sh/setup-uv/issues/526))
- chore: update known versions for 0.8.10
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;525](https://redirect.github.com/astral-sh/setup-uv/issues/525))

###
[`v6.5.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.5.0):
🌈 Better error messages, bug fixes and copilot agent settings

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.4.3...v6.5.0)

##### Changes

This release brings better error messages in case the GitHub API is
impacted, fixes a few bugs and allows to disable [problem
matchers](https://redirect.github.com/actions/toolkit/blob/main/docs/problem-matchers.md)
for better use in Copilot Agent workspaces.

##### 🐛 Bug fixes

- Improve error messages on GitHub API errors
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;518](https://redirect.github.com/astral-sh/setup-uv/issues/518))
- Ignore backslashes and whitespace in requirements
[@&#8203;axm2](https://redirect.github.com/axm2)
([#&#8203;501](https://redirect.github.com/astral-sh/setup-uv/issues/501))

##### 🚀 Enhancements

- Add input add-problem-matchers
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;517](https://redirect.github.com/astral-sh/setup-uv/issues/517))

##### 🧰 Maintenance

- chore: update known versions for 0.8.9
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;512](https://redirect.github.com/astral-sh/setup-uv/issues/512))
- chore: update known versions for 0.8.6-0.8.8
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;510](https://redirect.github.com/astral-sh/setup-uv/issues/510))
- chore: update known versions for 0.8.5
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;509](https://redirect.github.com/astral-sh/setup-uv/issues/509))
- chore: update known versions for 0.8.4
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;505](https://redirect.github.com/astral-sh/setup-uv/issues/505))
- chore: update known versions for 0.8.3
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;502](https://redirect.github.com/astral-sh/setup-uv/issues/502))

##### 📚 Documentation

- add note on caching to read disable-cache-pruning
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;506](https://redirect.github.com/astral-sh/setup-uv/issues/506))

##### ⬆️ Dependency updates

- Bump actions/checkout from 4 to 5
@&#8203;[dependabot\[bot\]](https://redirect.github.com/apps/dependabot)
([#&#8203;514](https://redirect.github.com/astral-sh/setup-uv/issues/514))
- bump dependencies
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;516](https://redirect.github.com/astral-sh/setup-uv/issues/516))
- Bump biome to v2
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;515](https://redirect.github.com/astral-sh/setup-uv/issues/515))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:40:46 +05:30
renovate[bot]
41bb24a87e Update Rust crate serde_json to v1.0.143 (#20069) 2025-08-24 22:20:39 -04:00
renovate[bot]
87f0da139a Update Rust crate proc-macro2 to v1.0.101 (#20066) 2025-08-24 22:20:24 -04:00
renovate[bot]
f9bfc9ab5b Update Rust crate ordermap to v0.5.9 (#20065) 2025-08-24 22:20:17 -04:00
renovate[bot]
59f7102606 Update Rust crate bitflags to v2.9.3 (#20063) 2025-08-24 22:19:54 -04:00
renovate[bot]
862d2d0687 Update dependency ruff to v0.12.10 (#20062) 2025-08-24 22:19:44 -04:00
renovate[bot]
48edf46f3b Update Rust crate filetime to v0.2.26 (#20064) 2025-08-24 22:19:34 -04:00
Jelle Zijlstra
ec86a4e960 [ty] Add Top[] and Bottom[] special forms, replacing top_materialization_of() function (#20054)
Part of astral-sh/ty#994

## Summary

Add new special forms to `ty_extensions`, `Top[T]` and `Bottom[T]`.
Remove `ty_extensions.top_materialization` and
`ty_extensions.bottom_materialization`.

## Test Plan

Converted the existing `materialization.md` mdtest to the new syntax.
Added some tests for invalid use of the new special form.
2025-08-23 11:20:56 -07:00
Andrew Gallant
e7237652a9 [ty] Lightly refactor document symbols AST visitor
This makes use of early continue/return to keep rightward drift under
control. (I also find it easier to read.)
2025-08-23 12:53:41 -04:00
Andrew Gallant
205eae14d2 [ty] Rejigger workspace symbols for more efficient caching
In effect, we make the Salsa query aspect keyed only on whether we want
global symbols. We move everything else (hierarchical and querying) to
an aggregate step *after* the query.

This was a somewhat involved change since we want to return a flattened
list from visiting the source while also preserving enough information
to reform the symbols into a hierarchical structure that the LSP
expects. But I think overall the API has gotten simpler and we encode
more invariants into the type system. (For example, previously you got a
runtime assertion if you tried to provide a query string while enabling
hierarchical mode. But now that's prevented by construction.)
2025-08-23 12:53:41 -04:00
Andrew Gallant
f407f12f4c [ty] Parallelize workspace symbols
This is a pretty naive approach, but it makes cold times for listing
workspace symbols in home-assistant under 1s on my machine.

Courtesy of Micha:
https://github.com/astral-sh/ruff/pull/20030#discussion_r2292924171
2025-08-23 12:53:41 -04:00
Andrew Gallant
fb2d0af18c [ty] Optimize "workspace symbols" retrieval
Basically, this splits the implementation into two pieces:
the first piece does the traversal and finds *all* symbols
across the workspace. The second piece does filtering based
on a user provided query string. Only the first piece is
cached by Salsa.

This brings warm "workspace symbols" requests down from
500-600ms to 100-200ms.
2025-08-23 12:53:41 -04:00
Andrew Gallant
8ead02e0b1 [ty] Optimize query string matching
While this doesn't typically matter, when ty returns a very
large list of symbols, this can have an impact. Specifically,
when searching `async` in home-assistant, this gets times
closer to 500ms versus closer to 600ms before this change.
It looks like an overall ~50ms improvement (so around 10%),
but variance is all over the place and I didn't do any
statistical tests.

But this does make intuitive sense. Previously, we were
allocating intermediate strings, doing UTF-8 decoding and
consulting Unicode casing tables. Now we're just doing what
is likely a single DFA scan. In effect, we front load all
of the Unicode junk into regex compilation.
2025-08-23 12:53:41 -04:00
Andrew Gallant
330bb4efbf [ty] Add some unit tests for "query matches symbol"
There is a small amount of subtlety to this matching routine,
and it could be implemented in a faster way. So let's right some
tests for what we have to ensure we don't break anything when
we optimize it.
2025-08-23 12:53:41 -04:00
Andrew Gallant
ad8c98117a [ty] Move query filtering outside of symbol visitor
This is prep work for turning this into a Salsa query.
Specifically, we don't want the Salsa query to be
dependent upon the query string.
2025-08-23 12:53:41 -04:00
Andrew Gallant
06dbec8479 [ty] Add debug trace for workspace symbol elapsed time
Useful for ad hoc debugging, but it's also useful to have permanently to
enable serendipitous discovery of performance problems.
2025-08-23 12:53:41 -04:00
Andrew Gallant
85931ab594 [ty] Add a TODO for linting on todo! 2025-08-23 12:53:41 -04:00
Ibraheem Ahmed
b3cc733f06 [ty] Remove duplicate global lint registry (#20053)
## Summary

Looks like an oversight at some point that led to two identical globals,
the one in `ty_project` just calls `ty_python_semantic::register_lints`.
2025-08-22 19:43:12 -04:00
Ibraheem Ahmed
7abc41727b [ty] Shrink size of AstNodeRef (#20028)
## Summary

Removes the `module_ptr` field from `AstNodeRef` in release mode, and
change `NodeIndex` to a `NonZeroU32` to reduce the size of
`Option<AstNodeRef<_>>` fields.

I believe CI runs in debug mode, so this won't show up in the memory
report, but this reduces memory by ~2% in release mode.
2025-08-22 17:03:22 -04:00
chiri
886c4e4773 [flake8-use-pathlib] Fix PTH211 autofix (#20049)
## Summary
Part of #20009
2025-08-22 13:35:08 -05:00
Alex Waygood
bc6ea68733 [ty] Add precise iteration and unpacking inference for string literals and bytes literals (#20023)
## Summary

Previously we held off from doing this because we weren't sure that it
was worth the added complexity cost. But our code has changed in the
months since we made that initial decision, and I think the structure of
the code is such that it no longer really leads to much added complexity
to add precise inference when unpacking a string literal or a bytes
literal.

The improved inference we gain from this has real benefits to users (see
the mypy_primer report), and this PR doesn't appear to have a
performance impact.

## Test plan

mdtests
2025-08-22 19:33:08 +01:00
Micha Reiser
796819e7a0 [ty] Disallow std::env and io methods in most ty crates (#20046)
## Summary

We use the `System` abstraction in ty to abstract away the host/system
on which ty runs.
This has a few benefits:

* Tests can run in full isolation using a memory system (that uses an
in-memory file system)
* The LSP has a custom implementation where `read_to_string` returns the
content as seen by the editor (e.g. unsaved changes) instead of always
returning the content as it is stored on disk
* We don't require any file system polyfills for wasm in the browser


However, it does require extra care that we don't accidentally use
`std::fs` or `std::env` (etc.) methods in ty's code base (which is very
easy).

This PR sets up Clippy and disallows the most common methods, instead
pointing users towards the corresponding `System` methods.

The setup is a bit awkward because clippy doesn't support inheriting
configurations. That means, a crate can only override the entire
workspace configuration or not at all.
The approach taken in this PR is:

* Configure the disallowed methods at the workspace level
* Allow `disallowed_methods` at the workspace level
* Enable the lint at the crate level using the warn attribute (in code)


The obvious downside is that it won't work if we ever want to disallow
other methods, but we can figure that out once we reach that point.

What about false positives: Just add an `allow` and move on with your
life :) This isn't something that we have to enforce strictly; the goal
is to catch accidental misuse.

## Test Plan

Clippy found a place where we incorrectly used `std::fs::read_to_string`
2025-08-22 11:13:47 -07:00
Vivek Dasari
5508e8e528 Add testing helper to compare stable vs preview snapshots (#19715)
## Summary
This PR implements a diff test helper `assert_diagnostics_diff` as
described in #19351. The diff file includes both the settings ( e.g.
`+linter.preview = enabled`) and the snapshot data itself.

The current implementation looks for each old diagnostic in the new
snapshot. This works when the preview behavior adds/removes a couple
diagnostics. This implementation does not work well when every
diagnostic is modified (e.g. a "fix" is added).
https://github.com/astral-sh/ruff/pull/19715#discussion_r2259410763 has
ideas for future improvements to this implementation.

The example usage in this PR writes the diff to `preview_diff` file
instead of `preview` file, which might be a useful convention to keep.


## Test Plan
- Included a unit test at:
https://github.com/astral-sh/ruff/pull/19715/files#diff-d49487fe3e8a8585529f62c2df2a2b0a4c44267a1f93d1e859dff1d9f8771d36R523
- Example usage of this new test helper:
https://github.com/astral-sh/ruff/pull/19715/files#diff-2a33ac11146d1794c01a29549a6041d3af6fb6f9b423a31ade12a88d1951b0c2R1
2025-08-22 12:49:34 -05:00
chiri
0be3e1fbbf [flake8-use-pathlib] Add autofix for PTH211 (#20009)
## Summary
Part of https://github.com/astral-sh/ruff/issues/2331
2025-08-22 12:38:37 -05:00
Micha Reiser
5d217b7f46 [ty] Add type as detail to completion items (#20047)
## Summary

@BurntSushi was so kind as to find me an easy task to do some coding
before I'm off to PTO.

This PR adds the type to completion items (see the gray little text at
the end of a completion item).



https://github.com/user-attachments/assets/c0a86061-fa12-47b4-b43c-3c646771a69d
2025-08-22 12:32:53 -04:00
Dylan
0b6ce1c788 [ruff] Handle empty t-strings in unnecessary-empty-iterable-within-deque-call (RUF037) (#20045)
Adds a method to `TStringValue` to detect whether the t-string is empty
_as an iterable_. Note the subtlety here that, unlike f-strings, an
empty t-string is still truthy (i.e. `bool(t"")==True`).

Closes #19951
2025-08-22 10:23:49 -05:00
Matthew Mckee
0e9d77e43a Fix incorrect lsp inlay hint type (#20044) 2025-08-22 17:12:49 +02:00
Carl Meyer
8b827c3c6c [ty] rename BareTypeAliasType to ManualPEP695TypeAliasType (#20037)
## Summary

Rename `TypeAliasType::Bare` to `TypeAliasType::ManualPEP695`, and
`BareTypeAliasType` to `ManualPEP695TypeAliasType`.

Why?

Both existing variants of `TypeAliasType` are specific to features added
in PEP 695 (which introduced both the `type` statement and
`types.TypeAliasType`), so it doesn't make sense to name one with the
name `PEP695` and not the other.

A "bare" type alias, in my mind, is a legacy type alias like `IntOrStr =
int | str`, which is "bare" in that there is nothing at all
distinguishing it as a type alias. I will want to use the "bare" name
for this variant, in a future PR.

The renamed variant here describes a type alias created with `IntOrStr =
types.TypeAliasType("IntOrStr", int | str)`, which is not "bare", it's
just "manually" instantiated instead of using the `type` statement
syntax sugar. (This is useful when using the `typing_extensions`
backport of `TypeAliasType` on older Python versions.)

## Test Plan

Pure rename, existing tests pass.
2025-08-22 07:40:29 -07:00
Max Mynter
c22395dbc6 [ruff] Fix false positive for t-strings in default-factory-kwarg (RUF026) (#20032)
Closes #19993

## Summary
Recognize t strings as never being callable to avoid false positives on
RUF026.
2025-08-22 09:29:42 -05:00
Micha Reiser
11f521c768 [ty] Close signature help after ) (#20017) 2025-08-22 16:09:22 +02:00
Micha Reiser
c5e05df966 [ty] Cancel background tasks when shutdown is requested (#20039) 2025-08-22 10:20:13 +02:00
github-actions[bot]
7a44ea680e [ty] Sync vendored typeshed stubs (#20031)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-08-21 21:32:48 +00:00
Alex Waygood
f82025d919 [ty] Improve diagnostics for bad calls to functions (#20022) 2025-08-21 22:00:44 +01:00
Micha Reiser
365f521c37 [ty] Fix incorrect docstring in call signature completion (#20021)
## Summary

This PR fixes https://github.com/astral-sh/ty/issues/1071

The core issue is that `CallableType` is a salsa interned but
`Signature` (which `CallableType` stores) ignores the `Definition` in
its `Eq` and `Hash` implementation.

This PR tries to simplest fix by removing the custom `Eq` and `Hash`
implementation. The main downside of this fix is that it can increase
memory usage because `CallableType`s that are equal except for their
`Definition` are now interned separately.

The alternative is to remove `Definition` from `CallableType` and
instead, call `bindings` directly on the callee (call_expression.func).
However, this would require
addressing the TODO 

here
39ee71c2a5/crates/ty_python_semantic/src/types.rs (L4582-L4586)

This might probably be worth addressing anyway, but is the more involved
fix. That's why I opted for removing the custom `Eq` implementation.

We already "ignore" the definition during normalization, thank's to
Alex's work in https://github.com/astral-sh/ruff/pull/19615

## Test Plan



https://github.com/user-attachments/assets/248d1cb1-12fd-4441-adab-b7e0866d23eb
2025-08-21 16:36:40 -04:00
Aria Desires
fc5321e000 [ty] fix GotoTargets for keyword args in nested function calls (#20013)
While implementing similar logic for initializers I noticed that this
code appeared to be walking the ancestors in the wrong direction, and so
if you have nested function calls it would always grab the outermost one
instead of the closest-ancestor.

The four copies of the test are because there's something really evil in
our caching that can't seem to be demonstrated in our cursor testing
framework, which I'm filing a followup for.
2025-08-21 20:19:52 +00:00
913 changed files with 26833 additions and 24755 deletions

View File

@@ -441,7 +441,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
with:
tool: cargo-fuzz@0.11.2
- name: "Install cargo-fuzz"
@@ -463,7 +463,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
name: Download Ruff binary to test
id: download-cached-binary
@@ -664,7 +664,7 @@ jobs:
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: Fuzz
env:
FORCE_COLOR: 1
@@ -694,7 +694,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
- uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -734,7 +734,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
@@ -777,7 +777,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt --system
@@ -909,7 +909,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: "Install Rust toolchain"
run: rustup show
@@ -942,7 +942,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: "Install Rust toolchain"
run: rustup show

View File

@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"

View File

@@ -39,7 +39,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
@@ -82,7 +82,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*

View File

@@ -61,7 +61,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
@@ -175,7 +175,7 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
@@ -251,7 +251,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive

View File

@@ -65,7 +65,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -117,7 +117,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -155,7 +155,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- name: Setup git
run: |
git config --global user.name typeshedbot

View File

@@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

164
Cargo.lock generated
View File

@@ -257,9 +257,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.2"
version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
[[package]]
name = "bitvec"
@@ -295,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"regex-automata 0.4.9",
"regex-automata 0.4.10",
"serde",
]
@@ -485,7 +485,7 @@ checksum = "85a8ab73a1c02b0c15597b22e09c7dc36e63b2f601f9d1e83ac0c3decd38b1ae"
dependencies = [
"nix 0.29.0",
"terminfo",
"thiserror 2.0.12",
"thiserror 2.0.16",
"which",
"windows-sys 0.59.0",
]
@@ -955,7 +955,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1035,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1084,14 +1084,14 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.25"
version = "0.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1118,9 +1118,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
@@ -1231,7 +1231,7 @@ dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.9",
"regex-automata 0.4.10",
"regex-syntax 0.8.5",
]
@@ -1241,7 +1241,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"ignore",
"walkdir",
]
@@ -1430,9 +1430,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.0.3"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
@@ -1459,7 +1459,7 @@ dependencies = [
"globset",
"log",
"memchr",
"regex-automata 0.4.9",
"regex-automata 0.4.10",
"same-file",
"walkdir",
"winapi-util",
@@ -1486,9 +1486,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
@@ -1521,7 +1521,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"inotify-sys",
"libc",
]
@@ -1780,7 +1780,7 @@ dependencies = [
"paste",
"peg",
"regex",
"thiserror 2.0.12",
"thiserror 2.0.16",
]
[[package]]
@@ -1809,7 +1809,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"libc",
"redox_syscall",
]
@@ -2014,7 +2014,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2026,7 +2026,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2054,7 +2054,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"fsevent-sys",
"inotify",
"kqueue",
@@ -2127,9 +2127,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordermap"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d6bff06e4a5dc6416bead102d3e63c480dd852ffbb278bf8cfeb4966b329609"
checksum = "2fd6fedcd996c8c97932075cc3811d83f53280f48d5620e4e3cab7f6a12678c4"
dependencies = [
"indexmap",
"serde",
@@ -2283,9 +2283,9 @@ dependencies = [
[[package]]
name = "percent-encoding"
version = "2.3.1"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
@@ -2294,7 +2294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
dependencies = [
"memchr",
"thiserror 2.0.12",
"thiserror 2.0.16",
"ucd-trie",
]
@@ -2473,9 +2473,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.96"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
@@ -2490,7 +2490,7 @@ dependencies = [
"pep440_rs",
"pep508_rs",
"serde",
"thiserror 2.0.12",
"thiserror 2.0.16",
"toml 0.8.23",
]
@@ -2505,7 +2505,7 @@ dependencies = [
"newtype-uuid",
"quick-xml",
"strip-ansi-escapes",
"thiserror 2.0.12",
"thiserror 2.0.16",
"uuid",
]
@@ -2666,7 +2666,7 @@ version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
]
[[package]]
@@ -2677,18 +2677,18 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.12",
"thiserror 2.0.16",
]
[[package]]
name = "regex"
version = "1.11.1"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.9",
"regex-automata 0.4.10",
"regex-syntax 0.8.5",
]
@@ -2703,9 +2703,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
@@ -2749,7 +2749,7 @@ dependencies = [
"argfile",
"assert_fs",
"bincode 2.0.1",
"bitflags 2.9.2",
"bitflags 2.9.3",
"cachedir",
"clap",
"clap_complete_command",
@@ -2793,7 +2793,7 @@ dependencies = [
"strum",
"tempfile",
"test-case",
"thiserror 2.0.12",
"thiserror 2.0.16",
"tikv-jemallocator",
"toml 0.9.5",
"tracing",
@@ -2888,7 +2888,7 @@ dependencies = [
"serde_json",
"similar",
"tempfile",
"thiserror 2.0.12",
"thiserror 2.0.16",
"tracing",
"tracing-subscriber",
"ty_static",
@@ -2935,6 +2935,7 @@ dependencies = [
"tracing-subscriber",
"ty",
"ty_project",
"ty_python_semantic",
"ty_static",
"url",
]
@@ -3001,7 +3002,7 @@ version = "0.12.10"
dependencies = [
"aho-corasick",
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.3",
"clap",
"colored 3.0.0",
"fern",
@@ -3048,7 +3049,7 @@ dependencies = [
"strum_macros",
"tempfile",
"test-case",
"thiserror 2.0.12",
"thiserror 2.0.16",
"toml 0.9.5",
"typed-arena",
"unicode-normalization",
@@ -3091,7 +3092,7 @@ dependencies = [
"serde_json",
"serde_with",
"test-case",
"thiserror 2.0.12",
"thiserror 2.0.16",
"uuid",
]
@@ -3107,7 +3108,7 @@ name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"aho-corasick",
"bitflags 2.9.2",
"bitflags 2.9.3",
"compact_str",
"get-size2",
"is-macro",
@@ -3122,7 +3123,7 @@ dependencies = [
"salsa",
"schemars",
"serde",
"thiserror 2.0.12",
"thiserror 2.0.16",
]
[[package]]
@@ -3176,7 +3177,7 @@ dependencies = [
"similar",
"smallvec",
"static_assertions",
"thiserror 2.0.12",
"thiserror 2.0.16",
"tracing",
]
@@ -3195,7 +3196,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"itertools 0.14.0",
"ruff_python_ast",
"unic-ucd-category",
@@ -3206,7 +3207,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.3",
"bstr",
"compact_str",
"get-size2",
@@ -3231,7 +3232,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"insta",
"is-macro",
"ruff_cache",
@@ -3252,7 +3253,7 @@ dependencies = [
name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"unicode-ident",
]
@@ -3306,7 +3307,7 @@ dependencies = [
"serde",
"serde_json",
"shellexpand",
"thiserror 2.0.12",
"thiserror 2.0.16",
"toml 0.9.5",
"tracing",
"tracing-log",
@@ -3429,11 +3430,11 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -3577,9 +3578,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.142"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
dependencies = [
"itoa",
"memchr",
@@ -3790,9 +3791,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
@@ -3916,11 +3917,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.16",
]
[[package]]
@@ -3936,9 +3937,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
@@ -4137,9 +4138,9 @@ dependencies = [
[[package]]
name = "tracing-indicatif"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1983afead46ff13a3c93581e0cec31d20b29efdd22cbdaa8b9f850eccf2c352"
checksum = "04d4e11e0e27acef25a47f27e9435355fecdc488867fa2bc90e75b0700d2823d"
dependencies = [
"indicatif",
"tracing",
@@ -4239,17 +4240,22 @@ dependencies = [
name = "ty_ide"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"get-size2",
"insta",
"itertools 0.14.0",
"rayon",
"regex",
"ruff_db",
"ruff_index",
"ruff_memory_usage",
"ruff_python_ast",
"ruff_python_parser",
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"salsa",
"smallvec",
"tracing",
"ty_project",
@@ -4272,7 +4278,7 @@ dependencies = [
"pep440_rs",
"rayon",
"regex",
"regex-automata 0.4.9",
"regex-automata 0.4.10",
"ruff_cache",
"ruff_db",
"ruff_macros",
@@ -4285,7 +4291,7 @@ dependencies = [
"salsa",
"schemars",
"serde",
"thiserror 2.0.12",
"thiserror 2.0.16",
"toml 0.9.5",
"tracing",
"ty_combine",
@@ -4298,7 +4304,7 @@ name = "ty_python_semantic"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.3",
"bitvec",
"camino",
"colored 3.0.0",
@@ -4338,7 +4344,7 @@ dependencies = [
"strum_macros",
"tempfile",
"test-case",
"thiserror 2.0.12",
"thiserror 2.0.16",
"tracing",
"ty_python_semantic",
"ty_static",
@@ -4351,7 +4357,7 @@ name = "ty_server"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.3",
"crossbeam",
"dunce",
"insta",
@@ -4372,7 +4378,7 @@ dependencies = [
"serde_json",
"shellexpand",
"tempfile",
"thiserror 2.0.12",
"thiserror 2.0.16",
"tracing",
"tracing-subscriber",
"ty_combine",
@@ -4394,7 +4400,7 @@ name = "ty_test"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.2",
"bitflags 2.9.3",
"camino",
"colored 3.0.0",
"insta",
@@ -4413,7 +4419,7 @@ dependencies = [
"serde",
"smallvec",
"tempfile",
"thiserror 2.0.12",
"thiserror 2.0.16",
"toml 0.9.5",
"tracing",
"ty_python_semantic",
@@ -4590,9 +4596,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.4"
version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [
"form_urlencoded",
"idna",
@@ -5144,7 +5150,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
]
[[package]]

View File

@@ -215,6 +215,8 @@ unexpected_cfgs = { level = "warn", check-cfg = [
[workspace.lints.clippy]
pedantic = { level = "warn", priority = -2 }
# Enabled at the crate level
disallowed_methods = "allow"
# Allowed pedantic lints
char_lit_as_u8 = "allow"
collapsible_else_if = "allow"
@@ -253,6 +255,7 @@ unused_peekable = "warn"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"
[profile.release]
# Note that we set these explicitly, and these values
# were chosen based on a trade-off between compile times

View File

@@ -24,3 +24,20 @@ ignore-interior-mutability = [
# The expression is read-only.
"ruff_python_ast::hashable::HashableExpr",
]
disallowed-methods = [
{ path = "std::env::var", reason = "Use System::env_var instead in ty crates" },
{ path = "std::env::current_dir", reason = "Use System::current_directory instead in ty crates" },
{ path = "std::fs::read_to_string", reason = "Use System::read_to_string instead in ty crates" },
{ path = "std::fs::metadata", reason = "Use System::path_metadata instead in ty crates" },
{ path = "std::fs::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "dunce::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "std::fs::read_dir", reason = "Use System::read_directory instead in ty crates" },
{ path = "std::fs::write", reason = "Use WritableSystem::write_file instead in ty crates" },
{ path = "std::fs::create_dir_all", reason = "Use WritableSystem::create_directory_all instead in ty crates" },
{ path = "std::fs::File::create_new", reason = "Use WritableSystem::create_new_file instead in ty crates" },
# Path methods that have System trait equivalents
{ path = "std::path::Path::exists", reason = "Use System::path_exists instead in ty crates" },
{ path = "std::path::Path::is_dir", reason = "Use System::is_directory instead in ty crates" },
{ path = "std::path::Path::is_file", reason = "Use System::is_file instead in ty crates" },
]

View File

@@ -325,6 +325,11 @@ impl Diagnostic {
self.inner.fix.as_ref()
}
#[cfg(test)]
pub(crate) fn fix_mut(&mut self) -> Option<&mut Fix> {
Arc::make_mut(&mut self.inner).fix.as_mut()
}
/// Set the fix for this diagnostic.
pub fn set_fix(&mut self, fix: Fix) {
debug_assert!(

View File

@@ -2622,6 +2622,13 @@ watermelon
self.config = config;
}
/// Show a diff for the fix when rendering.
pub(super) fn show_fix_diff(&mut self, yes: bool) {
let mut config = std::mem::take(&mut self.config);
config = config.show_fix_diff(yes);
self.config = config;
}
/// The lowest fix applicability to show when rendering.
pub(super) fn fix_applicability(&mut self, applicability: Applicability) {
let mut config = std::mem::take(&mut self.config);

View File

@@ -2,12 +2,13 @@ use std::borrow::Cow;
use std::num::NonZeroUsize;
use anstyle::Style;
use ruff_notebook::NotebookIndex;
use similar::{ChangeTag, TextDiff};
use ruff_annotate_snippets::Renderer as AnnotateRenderer;
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::OneIndexed;
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::diagnostic::render::{FileResolver, Resolved};
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
@@ -81,6 +82,7 @@ impl<'a> FullRenderer<'a> {
struct Diff<'a> {
fix: &'a Fix,
diagnostic_source: DiagnosticSource,
notebook_index: Option<NotebookIndex>,
stylesheet: &'a DiagnosticStylesheet,
}
@@ -90,12 +92,11 @@ impl<'a> Diff<'a> {
stylesheet: &'a DiagnosticStylesheet,
resolver: &'a dyn FileResolver,
) -> Option<Diff<'a>> {
let file = &diagnostic.primary_span_ref()?.file;
Some(Diff {
fix: diagnostic.fix()?,
diagnostic_source: diagnostic
.primary_span_ref()?
.file
.diagnostic_source(resolver),
diagnostic_source: file.diagnostic_source(resolver),
notebook_index: resolver.notebook_index(file),
stylesheet,
})
}
@@ -106,19 +107,24 @@ impl std::fmt::Display for Diff<'_> {
let source_code = self.diagnostic_source.as_source_code();
let source_text = source_code.text();
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
let mut output = String::with_capacity(source_text.len());
let mut last_end = TextSize::default();
for edit in self.fix.edits() {
output.push_str(source_code.slice(TextRange::new(last_end, edit.start())));
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
}
output.push_str(&source_text[usize::from(last_end)..]);
let diff = TextDiff::from_lines(source_text, &output);
// Partition the source code into end offsets for each cell. If `self.notebook_index` is
// `None`, indicating a regular script file, all the lines will be in one "cell" under the
// `None` key.
let cells = if let Some(notebook_index) = &self.notebook_index {
let mut last_cell = OneIndexed::MIN;
let mut cells: Vec<(Option<OneIndexed>, TextSize)> = Vec::new();
for (row, cell) in notebook_index.iter() {
if cell != last_cell {
let offset = source_code.line_start(row);
cells.push((Some(last_cell), offset));
last_cell = cell;
}
}
cells.push((Some(last_cell), source_text.text_len()));
cells
} else {
vec![(None, source_text.text_len())]
};
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
@@ -133,59 +139,97 @@ impl std::fmt::Display for Diff<'_> {
// tests, which is the only place these are currently used.
writeln!(f, " {}", fmt_styled(message, self.stylesheet.separator))?;
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let mut last_end = TextSize::ZERO;
for (cell, offset) in cells {
let range = TextRange::new(last_end, offset);
last_end = offset;
let input = source_code.slice(range);
let digit_with = OneIndexed::from_zero_indexed(largest_new.max(largest_old)).digits();
let mut output = String::with_capacity(input.len());
let mut last_end = range.start();
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
let mut applied = 0;
for edit in self.fix.edits() {
if range.contains_range(edit.range()) {
output.push_str(source_code.slice(TextRange::new(last_end, edit.start())));
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
applied += 1;
}
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag(), self.stylesheet);
// No edits were applied, so there's no need to diff.
if applied == 0 {
continue;
}
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
output.push_str(&source_text[usize::from(last_end)..usize::from(range.end())]);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with
},
Line {
index: new_index,
width: digit_with
},
fmt_styled(line_style.apply_to(sign), self.stylesheet.emphasis),
)?;
let diff = TextDiff::from_lines(input, &output);
for (emphasized, value) in change.iter_strings_lossy() {
let value = show_nonprinting(&value);
if emphasized {
write!(
f,
"{}",
fmt_styled(line_style.apply_to(&value), self.stylesheet.underline)
)?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let digit_with = OneIndexed::from_zero_indexed(largest_new.max(largest_old)).digits();
if let Some(cell) = cell {
// Room for 2 digits, 2 x 1 space before each digit, 1 space, and 1 `|`. This
// centers the three colons on the pipe.
writeln!(f, "{:>1$} cell {cell}", ":::", 2 * digit_with.get() + 4)?;
}
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag(), self.stylesheet);
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with,
},
Line {
index: new_index,
width: digit_with,
},
fmt_styled(line_style.apply_to(sign), self.stylesheet.emphasis),
)?;
for (emphasized, value) in change.iter_strings_lossy() {
let value = show_nonprinting(&value);
if emphasized {
write!(
f,
"{}",
fmt_styled(
line_style.apply_to(&value),
self.stylesheet.underline
)
)?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
}
@@ -253,7 +297,7 @@ fn show_nonprinting(s: &str) -> Cow<'_, str> {
#[cfg(test)]
mod tests {
use ruff_diagnostics::Applicability;
use ruff_diagnostics::{Applicability, Fix};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::diagnostic::{
@@ -654,6 +698,107 @@ print()
");
}
/// Test that we remap notebook cell line numbers in the diff as well as the main diagnostic.
#[test]
fn notebook_output_with_diff() {
let (mut env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
env.show_fix_diff(true);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
error[unused-import][*]: `os` imported but unused
--> notebook.ipynb:cell 1:2:8
|
1 | # cell 1
2 | import os
| ^^
|
help: Remove unused import: `os`
Safe fix
::: cell 1
1 1 | # cell 1
2 |-import os
error[unused-import][*]: `math` imported but unused
--> notebook.ipynb:cell 2:2:8
|
1 | # cell 2
2 | import math
| ^^^^
3 |
4 | print('hello world')
|
help: Remove unused import: `math`
Safe fix
::: cell 2
1 1 | # cell 2
2 |-import math
3 2 |
4 3 | print('hello world')
error[unused-variable]: Local variable `x` is assigned to but never used
--> notebook.ipynb:cell 3:4:5
|
2 | def foo():
3 | print()
4 | x = 1
| ^
|
help: Remove assignment to unused variable `x`
Unsafe fix
::: cell 3
1 1 | # cell 3
2 2 | def foo():
3 3 | print()
4 |- x = 1
5 4 |
");
}
#[test]
fn notebook_output_with_diff_spanning_cells() {
let (mut env, mut diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
env.show_fix_diff(true);
// Move all of the edits from the later diagnostics to the first diagnostic to simulate a
// single diagnostic with edits in different cells.
let mut diagnostic = diagnostics.swap_remove(0);
let fix = diagnostic.fix_mut().unwrap();
let mut edits = fix.edits().to_vec();
for diag in diagnostics {
edits.extend_from_slice(diag.fix().unwrap().edits());
}
*fix = Fix::unsafe_edits(edits.remove(0), edits);
insta::assert_snapshot!(env.render(&diagnostic), @r"
error[unused-import]: `os` imported but unused
--> notebook.ipynb:cell 1:2:8
|
1 | # cell 1
2 | import os
| ^^
|
help: Remove unused import: `os`
Unsafe fix
::: cell 1
1 1 | # cell 1
2 |-import os
::: cell 2
1 1 | # cell 2
2 |-import math
3 2 |
4 3 | print('hello world')
::: cell 3
1 1 | # cell 3
2 2 | def foo():
3 3 | print()
4 |- x = 1
5 4 |
");
}
/// Carriage return (`\r`) is a valid line-ending in Python, so we should normalize this to a
/// line feed (`\n`) for rendering. Otherwise we report a single long line for this case.
#[test]

View File

@@ -1,3 +1,8 @@
#![warn(
clippy::disallowed_methods,
reason = "Prefer System trait methods over std methods"
)]
use crate::files::Files;
use crate::system::System;
use crate::vendored::VendoredFileSystem;
@@ -65,6 +70,10 @@ pub trait Db: salsa::Database {
/// to process work in parallel. For example, to index a directory or checking the files of a project.
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
/// watching the files for changes.
#[expect(
clippy::disallowed_methods,
reason = "We don't have access to System here, but this is also only used by the CLI and the server which always run on a real system."
)]
pub fn max_parallelism() -> NonZeroUsize {
std::env::var(EnvVars::TY_MAX_PARALLELISM)
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))

View File

@@ -92,14 +92,14 @@ impl ParsedModule {
self.inner.store(None);
}
/// Returns a pointer for this [`ParsedModule`].
/// Returns the pointer address of 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 () {
pub fn addr(&self) -> usize {
// 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()
Arc::as_ptr(&self.inner).addr()
}
}
@@ -202,9 +202,13 @@ mod indexed {
/// Returns the node at the given index.
pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> {
let index = index
.as_u32()
.expect("attempted to access uninitialized `NodeIndex`");
// 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()]
self.index[index as usize]
}
}
@@ -220,7 +224,7 @@ mod indexed {
T: HasNodeIndex + std::fmt::Debug,
AnyRootNodeRef<'a>: From<&'a T>,
{
node.node_index().set(self.index);
node.node_index().set(NodeIndex::from(self.index));
self.nodes.push(AnyRootNodeRef::from(node));
self.index += 1;
}

View File

@@ -46,7 +46,7 @@ pub type Result<T> = std::io::Result<T>;
/// * File watching isn't supported.
///
/// Abstracting the system also enables tests to use a more efficient in-memory file system.
pub trait System: Debug {
pub trait System: Debug + Sync + Send {
/// Reads the metadata of the file or directory at `path`.
///
/// This function will traverse symbolic links to query information about the destination file.
@@ -197,6 +197,8 @@ pub trait System: Debug {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn dyn_clone(&self) -> Box<dyn System>;
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]

View File

@@ -1,3 +1,5 @@
#![allow(clippy::disallowed_methods)]
use super::walk_directory::{
self, DirectoryWalker, WalkDirectoryBuilder, WalkDirectoryConfiguration,
WalkDirectoryVisitorBuilder, WalkState,
@@ -255,6 +257,10 @@ impl System for OsSystem {
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
std::env::var(name)
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl OsSystem {

View File

@@ -146,6 +146,10 @@ impl System for TestSystem {
fn case_sensitivity(&self) -> CaseSensitivity {
self.system().case_sensitivity()
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl Default for TestSystem {
@@ -394,6 +398,13 @@ impl System for InMemorySystem {
fn case_sensitivity(&self) -> CaseSensitivity {
CaseSensitivity::CaseSensitive
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(Self {
user_config_directory: Mutex::new(self.user_config_directory.lock().unwrap().clone()),
memory_fs: self.memory_fs.clone(),
})
}
}
impl WritableSystem for InMemorySystem {

View File

@@ -13,6 +13,7 @@ license = { workspace = true }
[dependencies]
ty = { workspace = true }
ty_project = { workspace = true, features = ["schemars"] }
ty_python_semantic = { workspace = true }
ty_static = { workspace = true }
ruff = { workspace = true }
ruff_formatter = { workspace = true }

View File

@@ -52,7 +52,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
}
fn generate_markdown() -> String {
let registry = &*ty_project::DEFAULT_LINT_REGISTRY;
let registry = ty_python_semantic::default_lint_registry();
let mut output = String::new();

View File

@@ -13,3 +13,11 @@ 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)
os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")

View File

@@ -118,3 +118,10 @@ def func():
return lambda: value
defaultdict(constant_factory("<missing>"))
def func():
defaultdict(default_factory=t"") # OK
def func():
defaultdict(default_factory=t"hello") # OK

View File

@@ -102,3 +102,8 @@ deque("abc") # OK
deque(b"abc") # OK
deque(f"" "a") # OK
deque(f"{x}" "") # OK
# https://github.com/astral-sh/ruff/issues/19951
deque(t"")
deque(t"" t"")
deque(t"{""}") # OK

View File

@@ -1124,6 +1124,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::OsMakedirs) {
flake8_use_pathlib::rules::os_makedirs(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsSymlink) {
flake8_use_pathlib::rules::os_symlink(checker, call, segments);
}
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
flake8_use_pathlib::rules::path_constructor_current_directory(
checker, call, segments,

View File

@@ -954,7 +954,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),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::OsSymlink),
// flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),

View File

@@ -169,6 +169,11 @@ pub(crate) const fn is_fix_os_makedirs_enabled(settings: &LinterSettings) -> boo
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/20009
pub(crate) const fn is_fix_os_symlink_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/11436
// https://github.com/astral-sh/ruff/pull/11168
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {

View File

@@ -137,7 +137,7 @@ impl AutoPythonType {
let expr = Expr::Name(ast::ExprName {
id: Name::from(binding),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
});
Some((expr, vec![no_return_edit]))
@@ -204,7 +204,7 @@ fn type_expr(python_type: PythonType) -> Option<Expr> {
Expr::Name(ast::ExprName {
id: name.into(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
})
}

View File

@@ -52,13 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
arguments: Arguments {
args: if let Some(msg) = msg {
@@ -68,10 +68,10 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
},
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))),
cause: None,
})

View File

@@ -113,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
}
.into()

View File

@@ -53,11 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})],
value: Box::new(value.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
generator.stmt(&stmt)
}

View File

@@ -10,7 +10,7 @@ mod tests {
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_diagnostics, settings};
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
@@ -31,19 +31,24 @@ mod tests {
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
fn preview_rules(path: &Path) -> Result<()> {
let snapshot = format!("preview__{}", path.to_string_lossy());
let diagnostics = test_path(
let snapshot = format!("preview_diff__{}", path.to_string_lossy());
let rules = vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
];
let settings_before = settings::LinterSettings::for_rules(rules.clone());
let settings_after = settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(rules)
};
assert_diagnostics_diff!(
snapshot,
Path::new("flake8_commas").join(path).as_path(),
&settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
])
},
)?;
assert_diagnostics!(snapshot, diagnostics);
&settings_before,
&settings_after
);
Ok(())
}
}

View File

@@ -1,33 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
invalid-syntax: Starred expression cannot be used here
--> COM81_syntax_error.py:3:5
|
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
2 | (
3 | *args
| ^^^^^
4 | )
|
invalid-syntax: Type parameter list cannot be empty
--> COM81_syntax_error.py:6:9
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
COM819 Trailing comma prohibited
--> COM81_syntax_error.py:6:38
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
help: Remove trailing comma

View File

@@ -0,0 +1,136 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 6
--- Added ---
COM812 [*] Trailing comma missing
--> COM81.py:655:6
|
654 | type X[
655 | T
| ^
656 | ] = T
657 | def f[
|
help: Add trailing comma
Safe fix
652 652 | }"""
653 653 |
654 654 | type X[
655 |- T
655 |+ T,
656 656 | ] = T
657 657 | def f[
658 658 | T
COM812 [*] Trailing comma missing
--> COM81.py:658:6
|
656 | ] = T
657 | def f[
658 | T
| ^
659 | ](): pass
660 | class C[
|
help: Add trailing comma
Safe fix
655 655 | T
656 656 | ] = T
657 657 | def f[
658 |- T
658 |+ T,
659 659 | ](): pass
660 660 | class C[
661 661 | T
COM812 [*] Trailing comma missing
--> COM81.py:661:6
|
659 | ](): pass
660 | class C[
661 | T
| ^
662 | ]: pass
|
help: Add trailing comma
Safe fix
658 658 | T
659 659 | ](): pass
660 660 | class C[
661 |- T
661 |+ T,
662 662 | ]: pass
663 663 |
664 664 | type X[T,] = T
COM819 [*] Trailing comma prohibited
--> COM81.py:664:9
|
662 | ]: pass
663 |
664 | type X[T,] = T
| ^
665 | def f[T,](): pass
666 | class C[T,]: pass
|
help: Remove trailing comma
Safe fix
661 661 | T
662 662 | ]: pass
663 663 |
664 |-type X[T,] = T
664 |+type X[T] = T
665 665 | def f[T,](): pass
666 666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:665:8
|
664 | type X[T,] = T
665 | def f[T,](): pass
| ^
666 | class C[T,]: pass
|
help: Remove trailing comma
Safe fix
662 662 | ]: pass
663 663 |
664 664 | type X[T,] = T
665 |-def f[T,](): pass
665 |+def f[T](): pass
666 666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:666:10
|
664 | type X[T,] = T
665 | def f[T,](): pass
666 | class C[T,]: pass
| ^
|
help: Remove trailing comma
Safe fix
663 663 |
664 664 | type X[T,] = T
665 665 | def f[T,](): pass
666 |-class C[T,]: pass
666 |+class C[T]: pass

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 0

View File

@@ -209,18 +209,18 @@ fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -
},
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
arguments: args,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}

View File

@@ -178,21 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let node3 = Expr::Call(ast::ExprCall {
func: Box::new(node2),
@@ -200,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let call = node3;
@@ -223,7 +223,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
})
.collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let bool_op = node;
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -92,14 +92,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
parenthesized: false,
})
}),
value: subscript.value.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
});
let fix = Fix::applicable_edit(

View File

@@ -187,7 +187,7 @@ fn generate_pep604_fix(
op: Operator::BitOr,
right: Box::new(right.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))
} else {
Some(right.clone())
@@ -202,7 +202,7 @@ fn generate_pep604_fix(
}
static VIRTUAL_NONE_LITERAL: Expr = Expr::NoneLiteral(ExprNoneLiteral {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::new(TextSize::new(0), TextSize::new(0)),
});

View File

@@ -133,17 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
value: Box::new(Expr::Name(ExprName {
id: Name::new(binding),
ctx: ExprContext::Store,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
slice: Box::new(Expr::Tuple(ExprTuple {
elts: nodes.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
parenthesized: false,
})),

View File

@@ -205,13 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
parenthesized: true,
})
@@ -235,7 +235,7 @@ fn create_fix(
UnionKind::BitOr => {
let none_expr = Expr::NoneLiteral(ExprNoneLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let union_expr = pep_604_union(&[new_literal_expr, none_expr]);
let content = checker.generator().expr(&union_expr);

View File

@@ -261,7 +261,7 @@ fn generate_pep604_fix(
op: Operator::BitOr,
right: Box::new(right.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))
} else {
Some(right.clone())

View File

@@ -140,12 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
parenthesized: true,
})),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
});
@@ -164,12 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
parenthesized: true,
})),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
}))
} else {

View File

@@ -134,12 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
slice: Box::new(pep_604_union(&elts)),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
if other_exprs.is_empty() {
@@ -159,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
slice: Box::new(Expr::Subscript(ast::ExprSubscript {
value: subscript.value.clone(),
@@ -171,22 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
if other_exprs.is_empty() {
@@ -202,12 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
checker.generator().expr(&union)

View File

@@ -301,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
flags,
});
Some(generator.expr(&node))
@@ -367,14 +367,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
flags: checker.default_string_flags(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
@@ -404,14 +404,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
flags: checker.default_string_flags(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node),
@@ -440,7 +440,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node),
@@ -485,7 +485,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -166,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}
@@ -176,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}
@@ -296,7 +296,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(expr.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}),
msg,
)
@@ -370,7 +370,7 @@ impl UnittestAssert {
};
let node = Expr::NoneLiteral(ast::ExprNoneLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let expr = compare(expr, cmp_op, &node);
Ok(assert(&expr, msg))
@@ -387,7 +387,7 @@ impl UnittestAssert {
id: Name::new_static("isinstance"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
@@ -395,10 +395,10 @@ impl UnittestAssert {
args: Box::from([(**obj).clone(), (**cls).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let isinstance = node1.into();
if matches!(self, UnittestAssert::IsInstance) {
@@ -408,7 +408,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(isinstance),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let expr = node.into();
Ok(assert(&expr, msg))
@@ -429,14 +429,14 @@ impl UnittestAssert {
id: Name::new_static("re"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
@@ -444,10 +444,10 @@ impl UnittestAssert {
args: Box::from([(**regex).clone(), (**text).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let re_search = node2.into();
if matches!(self, UnittestAssert::Regex | UnittestAssert::RegexpMatches) {
@@ -457,7 +457,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(re_search),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
Ok(assert(&node.into(), msg))
}

View File

@@ -421,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
};
let isinstance_call = ast::ExprCall {
@@ -430,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -438,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into();
@@ -458,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into();
let fixed_source = checker.generator().expr(&bool_op);
@@ -552,21 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
};
let node1 = ast::ExprName {
id: id.clone(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let in_expr = node2.into();
let mut diagnostic = checker.report_diagnostic(
@@ -589,7 +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_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
node.into()
};

View File

@@ -232,7 +232,7 @@ fn check_os_environ_subscript(checker: &Checker, expr: &Expr) {
}
}),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let new_env_var = node.into();
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -188,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -196,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -227,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -282,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node3.into()),

View File

@@ -186,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node.into()),
@@ -242,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node.into()),
@@ -284,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
@@ -292,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&node1.into()),

View File

@@ -190,7 +190,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node3 = ast::ExprCall {
func: Box::new(node2.into()),
@@ -198,17 +198,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let contents = checker.generator().stmt(&node5.into());
@@ -299,7 +299,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let fixed_node = ast::ExprCall {
func: Box::new(dict_get_node.into()),
@@ -307,10 +307,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let contents = checker.generator().expr(&fixed_node.into());

View File

@@ -266,13 +266,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node1 = ast::StmtAssign {
targets: vec![target_var.clone()],
value: Box::new(node.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
node1.into()
}
@@ -282,13 +282,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node1 = ast::StmtAssign {
targets: vec![target_var.clone()],
value: Box::new(node.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
node1.into()
}
@@ -296,12 +296,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
targets: vec![target_var.clone()],
value: Box::new(
(ast::ExprBoolOp {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
op: BoolOp::Or,
values: vec![left_value.clone(), right_value.clone()],
})

View File

@@ -256,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))
}
@@ -264,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
}
} else if if_test.is_compare_expr() {
@@ -277,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let call_node = ast::ExprCall {
func: Box::new(func_node.into()),
@@ -285,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
Some(Expr::Call(call_node))
} else {
@@ -301,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
});

View File

@@ -165,7 +165,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_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
node.into()
} else {
@@ -173,7 +173,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_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
node.into()
}
@@ -182,7 +182,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_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
node.into()
}
@@ -406,17 +406,17 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
ifs: vec![],
is_async: false,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}],
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: false,
};
let node1 = ast::ExprName {
id,
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
@@ -424,15 +424,15 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
args: Box::from([node.into()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let node3 = ast::StmtReturn {
value: Some(Box::new(node2.into())),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
generator.stmt(&node3.into())
}

View File

@@ -163,14 +163,14 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
Expr::from(StringLiteral {
value: Box::from(*elt),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
flags: element_flags,
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}

View File

@@ -101,7 +101,7 @@ fn fix_banned_relative_import(
names: names.clone(),
level: 0,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let content = generator.stmt(&node.into());
Some(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -436,7 +436,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
value: annotation.into_boxed_str(),
flags: self.flags,
}))

View File

@@ -129,6 +129,7 @@ mod tests {
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
#[test_case(Rule::OsSymlink, Path::new("PTH211.py"))]
fn preview_flake8_use_pathlib(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@@ -24,6 +24,7 @@ pub(crate) use os_rename::*;
pub(crate) use os_replace::*;
pub(crate) use os_rmdir::*;
pub(crate) use os_sep_split::*;
pub(crate) use os_symlink::*;
pub(crate) use os_unlink::*;
pub(crate) use path_constructor_current_directory::*;
pub(crate) use replaceable_by_pathlib::*;
@@ -54,6 +55,7 @@ mod os_rename;
mod os_replace;
mod os_rmdir;
mod os_sep_split;
mod os_symlink;
mod os_unlink;
mod path_constructor_current_directory;
mod replaceable_by_pathlib;

View File

@@ -0,0 +1,153 @@
use ruff_diagnostics::{Applicability, Edit, Fix};
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::preview::is_fix_os_symlink_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
has_unknown_keywords_or_starred_expr, is_keyword_only_argument_non_default,
is_pathlib_path_call,
};
use crate::{FixAvailability, Violation};
/// ## 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.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
///
/// ## 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 {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"`os.symlink` should be replaced by `Path.symlink_to`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Replace with `Path(...).symlink_to(...)`".to_string())
}
}
/// PTH211
pub(crate) fn os_symlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "symlink"] {
return;
}
// `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;
}
let range = call.range();
let mut diagnostic = checker.report_diagnostic(OsSymlink, call.func.range());
if !is_fix_os_symlink_enabled(checker.settings()) {
return;
}
if call.arguments.len() > 3 {
return;
}
if has_unknown_keywords_or_starred_expr(
&call.arguments,
&["src", "dst", "target_is_directory", "dir_fd"],
) {
return;
}
let (Some(src), Some(dst)) = (
call.arguments.find_argument_value("src", 0),
call.arguments.find_argument_value("dst", 1),
) else {
return;
};
let target_is_directory_arg = call.arguments.find_argument_value("target_is_directory", 2);
if let Some(expr) = &target_is_directory_arg {
if expr.as_boolean_literal_expr().is_none() {
return;
}
}
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let locator = checker.locator();
let src_code = locator.slice(src.range());
let dst_code = locator.slice(dst.range());
let target_is_directory = target_is_directory_arg
.and_then(|expr| {
let code = locator.slice(expr.range());
expr.as_boolean_literal_expr()
.is_none_or(|bl| bl.value)
.then_some(format!(", target_is_directory={code}"))
})
.unwrap_or_default();
let replacement = if is_pathlib_path_call(checker, dst) {
format!("{dst_code}.symlink_to({src_code}{target_is_directory})")
} else {
format!("{binding}({dst_code}).symlink_to({src_code}{target_is_directory})")
};
Ok(Fix::applicable_edits(
Edit::range_replacement(replacement, range),
[import_edit],
applicability,
))
});
}

View File

@@ -7,9 +7,7 @@ use crate::rules::flake8_use_pathlib::helpers::{
};
use crate::rules::flake8_use_pathlib::{
rules::Glob,
violations::{
BuiltinOpen, Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, OsSymlink, PyPath,
},
violations::{BuiltinOpen, Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, PyPath},
};
pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
@@ -62,20 +60,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
),
// PTH122
["os", "path", "splitext"] => checker.report_diagnostic_if_enabled(OsPathSplitext, 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;
}
checker.report_diagnostic_if_enabled(OsSymlink, range)
}
// PTH123
["" | "builtins", "open"] => {
// `closefd` and `opener` are not supported by pathlib, so check if they

View File

@@ -9,6 +9,7 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:6:1
@@ -18,6 +19,7 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
| ^^^^^^^^^^
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:9:1
@@ -29,6 +31,7 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
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
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:10:1
@@ -38,3 +41,58 @@ PTH211 `os.symlink` should be replaced by `Path.symlink_to`
| ^^^^^^^^^^
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:17:1
|
15 | os.close(fd)
16 |
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
| ^^^^^^^^^^
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:18:1
|
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
| ^^^^^^^^^^
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:20:1
|
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
| ^^^^^^^^^^
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:22:1
|
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
| ^^^^^^^^^^
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:23:1
|
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
| ^^^^^^^^^^
|
help: Replace with `Path(...).symlink_to(...)`

View File

@@ -0,0 +1,166 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:5:1
|
5 | os.symlink("usr/bin/python", "tmp/python")
| ^^^^^^^^^^
6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
2 2 | from pathlib import Path
3 3 |
4 4 |
5 |-os.symlink("usr/bin/python", "tmp/python")
5 |+Path("tmp/python").symlink_to("usr/bin/python")
6 6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:6:1
|
5 | os.symlink("usr/bin/python", "tmp/python")
6 | os.symlink(b"usr/bin/python", b"tmp/python")
| ^^^^^^^^^^
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
3 3 |
4 4 |
5 5 | os.symlink("usr/bin/python", "tmp/python")
6 |-os.symlink(b"usr/bin/python", b"tmp/python")
6 |+Path(b"tmp/python").symlink_to(b"usr/bin/python")
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
9 9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:9:1
|
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 |
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)
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
6 6 | os.symlink(b"usr/bin/python", b"tmp/python")
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
9 |-os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
9 |+Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
10 10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
11 11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
12 12 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:10:1
|
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)
| ^^^^^^^^^^
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
7 7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
8 8 |
9 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)
10 |+Path(b"tmp/python").symlink_to(b"usr/bin/python", target_is_directory=True)
11 11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
12 12 |
13 13 | fd = os.open(".", os.O_RDONLY)
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:17:1
|
15 | os.close(fd)
16 |
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
| ^^^^^^^^^^
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
help: Replace with `Path(...).symlink_to(...)`
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:18:1
|
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
| ^^^^^^^^^^
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
15 15 | os.close(fd)
16 16 |
17 17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 |-os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
18 |+Path("tmp/python").symlink_to("usr/bin/python")
19 19 |
20 20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 21 |
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:20:1
|
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 |
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
| ^^^^^^^^^^
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
17 17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
18 18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
19 19 |
20 |-os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
20 |+Path("tmp/python").symlink_to("usr/bin/python")
21 21 |
22 22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:22:1
|
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 |
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
| ^^^^^^^^^^
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
help: Replace with `Path(...).symlink_to(...)`
Safe fix
19 19 |
20 20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
21 21 |
22 |-os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
22 |+Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
23 23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
--> PTH211.py:23:1
|
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
| ^^^^^^^^^^
|
help: Replace with `Path(...).symlink_to(...)`

View File

@@ -310,45 +310,3 @@ 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()
}
}

View File

@@ -9,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}
@@ -18,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}

View File

@@ -91,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
return Some(node.into());
}
@@ -114,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
flags,
};
Some(node.into())

View File

@@ -182,7 +182,7 @@ fn function(
ExprEllipsisLiteral::default(),
))),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let parameters = lambda.parameters.as_deref().cloned().unwrap_or_default();
if let Some(annotation) = annotation {
@@ -230,7 +230,7 @@ fn function(
returns: Some(Box::new(return_type)),
type_params: None,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let generated = checker.generator().stmt(&func);
@@ -246,7 +246,7 @@ fn function(
returns: None,
type_params: None,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let generated = checker.generator().stmt(&function);

View File

@@ -72,11 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}],
level: 0,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.generator().stmt(&node.into()),

View File

@@ -147,7 +147,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
new_args.push(new_arg);
continue;
@@ -204,10 +204,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&flattened_expr),

View File

@@ -170,13 +170,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(),
node_index: AtomicNodeIndex::NONE,
})
} else {
Expr::Tuple(ast::ExprTuple {
elts: comparators.iter().copied().cloned().collect(),
range: TextRange::default(),
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
parenthesized: true,
})
@@ -194,12 +194,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(),
node_index: AtomicNodeIndex::NONE,
})))
.chain(after)
.collect(),
range: bool_op.range(),
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
})),
bool_op.range(),
)));

View File

@@ -188,7 +188,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))
),
&call.arguments,

View File

@@ -87,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})],
// Ex) `NamedTuple("MyType", [("a", int), ("b", str)])`
([_typename, fields], []) => {
@@ -165,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -173,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into()
}
@@ -184,7 +184,7 @@ fn create_fields_from_fields_arg(fields: &Expr) -> Option<Vec<Stmt>> {
if fields.is_empty() {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
Some(vec![node])
} else {
@@ -236,13 +236,13 @@ fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &Expr) ->
args: Box::from([base_class.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
body,
type_params: None,
decorator_list: vec![],
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into()
}

View File

@@ -150,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -158,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into()
}
@@ -179,13 +179,13 @@ fn create_class_def_stmt(
None => Box::from([]),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
body,
type_params: None,
decorator_list: vec![],
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into()
}
@@ -194,7 +194,7 @@ fn fields_from_dict_literal(items: &[ast::DictItem]) -> Option<Vec<Stmt>> {
if items.is_empty() {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
Some(vec![node])
} else {
@@ -228,7 +228,7 @@ fn fields_from_dict_call(func: &Expr, keywords: &[Keyword]) -> Option<Vec<Stmt>>
if keywords.is_empty() {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
Some(vec![node])
} else {
@@ -241,7 +241,7 @@ fn fields_from_keywords(keywords: &[Keyword]) -> Option<Vec<Stmt>> {
if keywords.is_empty() {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
return Some(vec![node]);
}
@@ -282,7 +282,7 @@ fn match_fields_and_total(arguments: &Arguments) -> Option<(Vec<Stmt>, Option<&K
([_typename], []) => {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
Some((vec![node], None))
}

View File

@@ -39,27 +39,27 @@ impl LiteralType {
LiteralType::Str => ast::StringLiteral {
value: Box::default(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
flags: checker.default_string_flags(),
}
.into(),
LiteralType::Bytes => ast::BytesLiteral {
value: Box::default(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
LiteralType::Float => ast::ExprNumberLiteral {
value: ast::Number::Float(0.0),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
LiteralType::Bool => ast::ExprBooleanLiteral::default().into(),

View File

@@ -116,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
remaining.insert(0, node.into());
}
@@ -128,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
};
format!("({})", checker.generator().expr(&node.into()))

View File

@@ -140,14 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
elts: constraints.iter().map(|expr| (*expr).clone()).collect(),
ctx: ast::ExprContext::Load,
parenthesized: true,
@@ -156,17 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
elts: vec![
Expr::Name(ExprName {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
id: Name::from("str"),
ctx: ast::ExprContext::Load,
}),
Expr::Name(ExprName {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
id: Name::from("bytes"),
ctx: ast::ExprContext::Load,
}),
@@ -184,13 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
name: Identifier::new(*name, TextRange::default()),
default: None,
}),
TypeParamKind::ParamSpec => TypeParam::ParamSpec(TypeParamParamSpec {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
name: Identifier::new(*name, TextRange::default()),
default: None,
}),

View File

@@ -130,7 +130,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
remaining.insert(0, node.into());
}
@@ -142,7 +142,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
};
format!("({})", checker.generator().expr(&node.into()))

View File

@@ -127,13 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
})
}),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
expr.range(),
),

View File

@@ -17,7 +17,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// Construct `name.method`.
let attr = ast::ExprAttribute {
@@ -25,7 +25,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// Make it into a call `name.method()`
let call = ast::ExprCall {
@@ -34,16 +34,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// 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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
generator.stmt(&stmt.into())
}
@@ -69,7 +69,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let new_content = generator.expr(&new_expr);

View File

@@ -185,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// Make the actual call `set.discard(element)`
let call = ast::ExprCall {
@@ -194,16 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// 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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
generator.stmt(&stmt.into())
}

View File

@@ -130,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let call = ast::ExprCall {
func: Box::new(name.into()),
@@ -138,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
SourceCodeSnippet::from_str(&generator.expr(&call.into()))
}

View File

@@ -298,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
ast::ExprCall {
func: Box::new(starmap.into()),
@@ -306,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
}
@@ -319,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
ast::ExprCall {
func: Box::new(name.into()),
@@ -327,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
}

View File

@@ -342,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
};
// Make `var.extend`.
@@ -352,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// Make the actual call `var.extend((elt1, elt2, ..., eltN))`
let call = ast::ExprCall {
@@ -361,16 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// 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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
generator.stmt(&stmt.into())
}

View File

@@ -232,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// Construct `len(name)`.
let len = ast::ExprCall {
@@ -241,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -249,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// Construct `range(len(name))`.
let range = ast::ExprCall {
@@ -261,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
),
@@ -269,16 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// 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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
generator.stmt(&stmt.into())
}

View File

@@ -148,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let mut arg = arg.clone();
relocate_expr(&mut arg, TextRange::default());
@@ -158,10 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
SourceCodeSnippet::from_str(&generator.expr(&call.into()))
}

View File

@@ -68,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
// We have to replace the entire statement,
// as the `print` could be empty and thus `call.range()`
@@ -114,7 +114,7 @@ mod print_arguments {
InterpolatedStringElement::Literal(InterpolatedStringLiteralElement {
value: part.value.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
})
.collect(),
@@ -131,7 +131,7 @@ mod print_arguments {
conversion: ConversionFlag::None,
format_spec: None,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
)],
}
@@ -154,7 +154,7 @@ mod print_arguments {
value: literal.value.clone(),
flags,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
Some(acc)
} else {
@@ -212,7 +212,7 @@ mod print_arguments {
value: combined_string.into(),
flags,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))
}
@@ -246,10 +246,10 @@ mod print_arguments {
elements: InterpolatedStringElements::from(fstring_elements),
flags,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))
}
@@ -285,7 +285,7 @@ mod print_arguments {
vec![InterpolatedStringElement::Literal(
InterpolatedStringLiteralElement {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
value: " ".into(),
},
)]

View File

@@ -78,7 +78,7 @@ fn make_splat_elts(
value: Box::from(splat_element.clone()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
let splat = node.into();
if splat_at_left {
@@ -166,14 +166,14 @@ fn concatenate_expressions(expr: &Expr) -> Option<(Expr, Type)> {
elts: new_elts,
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}
.into(),
Type::Tuple => ast::ExprTuple {
elts: new_elts,
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
}
.into(),

View File

@@ -123,7 +123,8 @@ fn is_non_callable_value(value: &Expr) -> bool {
| Expr::SetComp(_)
| Expr::DictComp(_)
| Expr::Generator(_)
| Expr::FString(_))
| Expr::FString(_)
| Expr::TString(_))
}
/// Generate an [`Expr`] to replace `defaultdict(default_factory=callable)` with

View File

@@ -140,7 +140,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
let content = checker.generator().expr(&new_expr);
let edit = Edit::range_replacement(content, expr.range());
@@ -160,12 +160,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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})),
slice: Box::new(expr.clone()),
ctx: ast::ExprContext::Load,

View File

@@ -245,16 +245,16 @@ fn generate_with_statement(
});
let context_call = ast::ExprCall {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
func: legacy_call.func.clone(),
arguments: ast::Arguments {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
args: expected.cloned().as_slice().into(),
keywords: match_arg
.map(|expr| ast::Keyword {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
// Take range from the original expression so that the keyword
// argument is generated after positional arguments
range: expr.range(),
@@ -267,11 +267,11 @@ fn generate_with_statement(
};
let func_call = ast::ExprCall {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
func: Box::new(func.clone()),
arguments: ast::Arguments {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
args: func_args.into(),
keywords: func_keywords.into(),
@@ -280,25 +280,25 @@ fn generate_with_statement(
let body = if let Some(assign_targets) = assign_targets {
Stmt::Assign(ast::StmtAssign {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
targets: assign_targets.to_vec(),
value: Box::new(func_call.into()),
})
} else {
Stmt::Expr(StmtExpr {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
value: Box::new(func_call.into()),
})
};
Some(StmtWith {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
is_async: false,
items: vec![WithItem {
node_index: AtomicNodeIndex::dummy(),
node_index: AtomicNodeIndex::NONE,
range: TextRange::default(),
context_expr: context_call.into(),
optional_vars: optional_vars.map(|var| Box::new(var.clone())),

View File

@@ -102,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
// Construct `key in keys`.
let comp = ast::Comprehension {
@@ -110,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
is_async: false,
};
// Construct the dict comprehension.
@@ -119,7 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
};
generator.expr(&dict_comp.into())
}

View File

@@ -164,12 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
parenthesized: true,
})),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
}))
},
expr.range(),

View File

@@ -103,6 +103,7 @@ pub(crate) fn unnecessary_literal_within_deque_call(checker: &Checker, deque: &a
Expr::StringLiteral(string) => string.value.is_empty(),
Expr::BytesLiteral(bytes) => bytes.value.is_empty(),
Expr::FString(fstring) => fstring.value.is_empty_literal(),
Expr::TString(tstring) => tstring.value.is_empty_iterable(),
_ => false,
};
if !is_empty_literal {

View File

@@ -116,14 +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(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
parenthesized: false,
})
}),
value: subscript.value.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
ctx: ExprContext::Load,
});
let fix = Fix::applicable_edit(

View File

@@ -303,7 +303,7 @@ impl<'a> ReFunc<'a> {
op: UnaryOp::Not,
operand: Box::new(expr),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
Some(negated_expr)
}
@@ -327,7 +327,7 @@ impl<'a> ReFunc<'a> {
ops: Box::new([op]),
comparators: Box::new([right.clone()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}
@@ -339,7 +339,7 @@ impl<'a> ReFunc<'a> {
attr: Identifier::new(method, TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
});
Expr::Call(ExprCall {
func: Box::new(method),
@@ -347,10 +347,10 @@ impl<'a> ReFunc<'a> {
args: args.into_boxed_slice(),
keywords: Box::new([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
})
}
}

View File

@@ -383,3 +383,42 @@ help: Replace with `deque()`
101 101 | deque("abc") # OK
102 102 | deque(b"abc") # OK
103 103 | deque(f"" "a") # OK
RUF037 [*] Unnecessary empty iterable within a deque call
--> RUF037.py:107:1
|
106 | # https://github.com/astral-sh/ruff/issues/19951
107 | deque(t"")
| ^^^^^^^^^^
108 | deque(t"" t"")
109 | deque(t"{""}") # OK
|
help: Replace with `deque()`
Safe fix
104 104 | deque(f"{x}" "") # OK
105 105 |
106 106 | # https://github.com/astral-sh/ruff/issues/19951
107 |-deque(t"")
107 |+deque()
108 108 | deque(t"" t"")
109 109 | deque(t"{""}") # OK
RUF037 [*] Unnecessary empty iterable within a deque call
--> RUF037.py:108:1
|
106 | # https://github.com/astral-sh/ruff/issues/19951
107 | deque(t"")
108 | deque(t"" t"")
| ^^^^^^^^^^^^^^^
109 | deque(t"{""}") # OK
|
help: Replace with `deque()`
Safe fix
105 105 |
106 106 | # https://github.com/astral-sh/ruff/issues/19951
107 107 | deque(t"")
108 |-deque(t"" t"")
108 |+deque()
109 109 | deque(t"{""}") # OK

View File

@@ -12,14 +12,12 @@ I001 [*] Import block is un-sorted or un-formatted
help: Organize imports
Safe fix
::: cell 1
1 |+import math
2 |+import random
1 3 | from pathlib import Path
2 |-import random
3 |-import math
4 4 | from typing import Any
5 5 | import collections
6 6 | # Newline should be added here
I001 [*] Import block is un-sorted or un-formatted
--> isort.ipynb:cell 2:1:1
@@ -33,17 +31,15 @@ I001 [*] Import block is un-sorted or un-formatted
help: Organize imports
Safe fix
1 1 | from pathlib import Path
2 2 | import random
3 3 | import math
4 |+import collections
4 5 | from typing import Any
5 |-import collections
6 |+
7 |+
6 8 | # Newline should be added here
7 9 | def foo():
8 10 | pass
::: cell 2
1 |+import collections
1 2 | from typing import Any
2 |-import collections
3 |+
4 |+
3 5 | # Newline should be added here
4 6 | def foo():
5 7 | pass
I001 [*] Import block is un-sorted or un-formatted
--> isort.ipynb:cell 3:1:1
@@ -57,15 +53,13 @@ I001 [*] Import block is un-sorted or un-formatted
help: Organize imports
Safe fix
6 6 | # Newline should be added here
7 7 | def foo():
8 8 | pass
9 |+import sys
9 10 | from pathlib import Path
10 |-import sys
11 11 |
12 12 | %matplotlib \
13 13 | --inline
::: cell 3
1 |+import sys
1 2 | from pathlib import Path
2 |-import sys
3 3 |
4 4 | %matplotlib \
5 5 | --inline
I001 [*] Import block is un-sorted or un-formatted
--> isort.ipynb:cell 3:7:1
@@ -79,9 +73,10 @@ I001 [*] Import block is un-sorted or un-formatted
help: Organize imports
Safe fix
12 12 | %matplotlib \
13 13 | --inline
14 14 |
15 |+import abc
15 16 | import math
16 |-import abc
::: cell 3
4 4 | %matplotlib \
5 5 | --inline
6 6 |
7 |+import abc
7 8 | import math
8 |-import abc

View File

@@ -14,13 +14,13 @@ F401 [*] `os` imported but unused
help: Remove unused import: `os`
Safe fix
::: cell 1
2 2 |
3 3 | %matplotlib inline
4 4 |
5 |-import os
6 5 |
7 6 | _ = math.pi
8 7 | %%timeit
F401 [*] `sys` imported but unused
--> ipy_escape_command.ipynb:cell 2:2:8
@@ -32,7 +32,6 @@ F401 [*] `sys` imported but unused
help: Remove unused import: `sys`
Safe fix
6 6 |
7 7 | _ = math.pi
8 8 | %%timeit
9 |-import sys
::: cell 2
1 1 | %%timeit
2 |-import sys

View File

@@ -12,12 +12,11 @@ F841 [*] Local variable `foo1` is assigned to but never used
help: Remove assignment to unused variable `foo1`
Unsafe fix
::: cell 1
1 1 | def f():
2 |- foo1 = %matplotlib --list
2 |+ %matplotlib --list
3 3 | foo2: list[str] = %matplotlib --list
4 4 | def f():
5 5 | bar1 = !pwd
F841 [*] Local variable `foo2` is assigned to but never used
--> unused_variable.ipynb:cell 1:3:5
@@ -30,13 +29,11 @@ F841 [*] Local variable `foo2` is assigned to but never used
help: Remove assignment to unused variable `foo2`
Unsafe fix
::: cell 1
1 1 | def f():
2 2 | foo1 = %matplotlib --list
3 |- foo2: list[str] = %matplotlib --list
3 |+ %matplotlib --list
4 4 | def f():
5 5 | bar1 = !pwd
6 6 | bar2: str = !pwd
F841 [*] Local variable `bar1` is assigned to but never used
--> unused_variable.ipynb:cell 2:2:5
@@ -49,12 +46,11 @@ F841 [*] Local variable `bar1` is assigned to but never used
help: Remove assignment to unused variable `bar1`
Unsafe fix
2 2 | foo1 = %matplotlib --list
3 3 | foo2: list[str] = %matplotlib --list
4 4 | def f():
5 |- bar1 = !pwd
5 |+ !pwd
6 6 | bar2: str = !pwd
::: cell 2
1 1 | def f():
2 |- bar1 = !pwd
2 |+ !pwd
3 3 | bar2: str = !pwd
F841 [*] Local variable `bar2` is assigned to but never used
--> unused_variable.ipynb:cell 2:3:5
@@ -67,8 +63,8 @@ F841 [*] Local variable `bar2` is assigned to but never used
help: Remove assignment to unused variable `bar2`
Unsafe fix
3 3 | foo2: list[str] = %matplotlib --list
4 4 | def f():
5 5 | bar1 = !pwd
6 |- bar2: str = !pwd
6 |+ !pwd
::: cell 2
1 1 | def f():
2 2 | bar1 = !pwd
3 |- bar2: str = !pwd
3 |+ !pwd

Some files were not shown because too many files have changed in this diff Show More