Compare commits

..

2 Commits

Author SHA1 Message Date
Micha Reiser
371e5fc31d Remove mdtest Diagnostic trait, support tests with syntax errors 2024-11-06 09:09:10 +01:00
Micha Reiser
1cefe505ea Use accumulator for diagnostics 2024-11-06 09:06:50 +01:00
127 changed files with 1422 additions and 3893 deletions

View File

@@ -16,7 +16,7 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.12"
PYTHON_VERSION: "3.11"
jobs:
determine_changes:

View File

@@ -1,42 +1,5 @@
# Changelog
## 0.7.3
### Preview features
- Formatter: Disallow single-line implicit concatenated strings ([#13928](https://github.com/astral-sh/ruff/pull/13928))
- \[`flake8-pyi`\] Include all Python file types for `PYI006` and `PYI066` ([#14059](https://github.com/astral-sh/ruff/pull/14059))
- \[`flake8-simplify`\] Implement `split-of-static-string` (`SIM905`) ([#14008](https://github.com/astral-sh/ruff/pull/14008))
- \[`refurb`\] Implement `subclass-builtin` (`FURB189`) ([#14105](https://github.com/astral-sh/ruff/pull/14105))
- \[`ruff`\] Improve diagnostic messages and docs (`RUF031`, `RUF032`, `RUF034`) ([#14068](https://github.com/astral-sh/ruff/pull/14068))
### Rule changes
- Detect items that hash to same value in duplicate sets (`B033`, `PLC0208`) ([#14064](https://github.com/astral-sh/ruff/pull/14064))
- \[`eradicate`\] Better detection of IntelliJ language injection comments (`ERA001`) ([#14094](https://github.com/astral-sh/ruff/pull/14094))
- \[`flake8-pyi`\] Add autofix for `docstring-in-stub` (`PYI021`) ([#14150](https://github.com/astral-sh/ruff/pull/14150))
- \[`flake8-pyi`\] Update `duplicate-literal-member` (`PYI062`) to alawys provide an autofix ([#14188](https://github.com/astral-sh/ruff/pull/14188))
- \[`pyflakes`\] Detect items that hash to same value in duplicate dictionaries (`F601`) ([#14065](https://github.com/astral-sh/ruff/pull/14065))
- \[`ruff`\] Fix false positive for decorators (`RUF028`) ([#14061](https://github.com/astral-sh/ruff/pull/14061))
### Bug fixes
- Avoid parsing joint rule codes as distinct codes in `# noqa` ([#12809](https://github.com/astral-sh/ruff/pull/12809))
- \[`eradicate`\] ignore `# language=` in commented-out-code rule (ERA001) ([#14069](https://github.com/astral-sh/ruff/pull/14069))
- \[`flake8-bugbear`\] - do not run `mutable-argument-default` on stubs (`B006`) ([#14058](https://github.com/astral-sh/ruff/pull/14058))
- \[`flake8-builtins`\] Skip lambda expressions in `builtin-argument-shadowing (A002)` ([#14144](https://github.com/astral-sh/ruff/pull/14144))
- \[`flake8-comprehension`\] Also remove trailing comma while fixing `C409` and `C419` ([#14097](https://github.com/astral-sh/ruff/pull/14097))
- \[`flake8-simplify`\] Allow `open` without context manager in `return` statement (`SIM115`) ([#14066](https://github.com/astral-sh/ruff/pull/14066))
- \[`pylint`\] Respect hash-equivalent literals in `iteration-over-set` (`PLC0208`) ([#14063](https://github.com/astral-sh/ruff/pull/14063))
- \[`pylint`\] Update known dunder methods for Python 3.13 (`PLW3201`) ([#14146](https://github.com/astral-sh/ruff/pull/14146))
- \[`pyupgrade`\] - ignore kwarg unpacking for `UP044` ([#14053](https://github.com/astral-sh/ruff/pull/14053))
- \[`refurb`\] Parse more exotic decimal strings in `verbose-decimal-constructor` (`FURB157`) ([#14098](https://github.com/astral-sh/ruff/pull/14098))
### Documentation
- Add links to missing related options within rule documentations ([#13971](https://github.com/astral-sh/ruff/pull/13971))
- Add rule short code to mkdocs tags to allow searching via rule codes ([#14040](https://github.com/astral-sh/ruff/pull/14040))
## 0.7.2
### Preview features

8
Cargo.lock generated
View File

@@ -2220,7 +2220,6 @@ dependencies = [
"ruff_cache",
"ruff_db",
"ruff_python_ast",
"ruff_text_size",
"rustc-hash 2.0.0",
"salsa",
"tempfile",
@@ -2317,7 +2316,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.7.3"
version = "0.7.2"
dependencies = [
"anyhow",
"argfile",
@@ -2432,7 +2431,6 @@ dependencies = [
"salsa",
"serde",
"tempfile",
"thiserror",
"tracing",
"tracing-subscriber",
"tracing-tree",
@@ -2534,7 +2532,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.7.3"
version = "0.7.2"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2849,7 +2847,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.7.3"
version = "0.7.2"
dependencies = [
"console_error_panic_hook",
"console_log",

View File

@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.7.3/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.7.3/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.7.2/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.7.2/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.7.3
rev: v0.7.2
hooks:
# Run the linter.
- id: ruff

View File

@@ -5,6 +5,9 @@ use anyhow::{anyhow, Context};
use clap::Parser;
use colored::Colorize;
use crossbeam::channel as crossbeam_channel;
use ruff_db::diagnostic::CompileDiagnostic;
use salsa::plumbing::ZalsaDatabase;
use red_knot_python_semantic::SitePackages;
use red_knot_server::run_server;
use red_knot_workspace::db::RootDatabase;
@@ -12,9 +15,7 @@ use red_knot_workspace::watch;
use red_knot_workspace::watch::WorkspaceWatcher;
use red_knot_workspace::workspace::settings::Configuration;
use red_knot_workspace::workspace::WorkspaceMetadata;
use ruff_db::diagnostic::Diagnostic;
use ruff_db::system::{OsSystem, System, SystemPath, SystemPathBuf};
use salsa::plumbing::ZalsaDatabase;
use target_version::TargetVersion;
use crate::logging::{setup_tracing, Verbosity};
@@ -380,7 +381,7 @@ impl MainLoopCancellationToken {
enum MainLoopMessage {
CheckWorkspace,
CheckCompleted {
result: Vec<Box<dyn Diagnostic>>,
result: Vec<CompileDiagnostic>,
revision: u64,
},
ApplyChanges(Vec<watch::ChangeEvent>),

View File

@@ -18,58 +18,3 @@ class Unit: ...
b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
reveal_type(b) # revealed: Unknown
```
## Possibly unbound `__call__` method
```py
def flag() -> bool: ...
class PossiblyNotCallable:
if flag():
def __call__(self) -> int: ...
a = PossiblyNotCallable()
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
reveal_type(result) # revealed: int
```
## Possibly unbound callable
```py
def flag() -> bool: ...
if flag():
class PossiblyUnbound:
def __call__(self) -> int: ...
# error: [possibly-unresolved-reference]
a = PossiblyUnbound()
reveal_type(a()) # revealed: int
```
## Non-callable `__call__`
```py
class NonCallable:
__call__ = 1
a = NonCallable()
# error: "Object of type `NonCallable` is not callable"
reveal_type(a()) # revealed: Unknown
```
## Possibly non-callable `__call__`
```py
def flag() -> bool: ...
class NonCallable:
if flag():
__call__ = 1
else:
def __call__(self) -> int: ...
a = NonCallable()
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
reveal_type(a()) # revealed: Unknown | int
```

View File

@@ -44,16 +44,3 @@ reveal_type(bar()) # revealed: @Todo
nonsense = 123
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
```
## Potentially unbound function
```py
def flag() -> bool: ...
if flag():
def foo() -> int:
return 42
# error: [possibly-unresolved-reference]
reveal_type(foo()) # revealed: int
```

View File

@@ -1,155 +0,0 @@
# Comparison: Intersections
## Positive contributions
If we have an intersection type `A & B` and we get a definitive true/false answer for one of the
types, we can infer that the result for the intersection type is also true/false:
```py
class Base: ...
class Child1(Base):
def __eq__(self, other) -> Literal[True]:
return True
class Child2(Base): ...
def get_base() -> Base: ...
x = get_base()
c1 = Child1()
# Create an intersection type through narrowing:
if isinstance(x, Child1):
if isinstance(x, Child2):
reveal_type(x) # revealed: Child1 & Child2
reveal_type(x == 1) # revealed: Literal[True]
# Other comparison operators fall back to the base type:
reveal_type(x > 1) # revealed: bool
reveal_type(x is c1) # revealed: bool
```
## Negative contributions
Negative contributions to the intersection type only allow simplifications in a few special cases
(equality and identity comparisons).
### Equality comparisons
#### Literal strings
```py
x = "x" * 1_000_000_000
y = "y" * 1_000_000_000
reveal_type(x) # revealed: LiteralString
if x != "abc":
reveal_type(x) # revealed: LiteralString & ~Literal["abc"]
reveal_type(x == "abc") # revealed: Literal[False]
reveal_type("abc" == x) # revealed: Literal[False]
reveal_type(x == "something else") # revealed: bool
reveal_type("something else" == x) # revealed: bool
reveal_type(x != "abc") # revealed: Literal[True]
reveal_type("abc" != x) # revealed: Literal[True]
reveal_type(x != "something else") # revealed: bool
reveal_type("something else" != x) # revealed: bool
reveal_type(x == y) # revealed: bool
reveal_type(y == x) # revealed: bool
reveal_type(x != y) # revealed: bool
reveal_type(y != x) # revealed: bool
reveal_type(x >= "abc") # revealed: bool
reveal_type("abc" >= x) # revealed: bool
reveal_type(x in "abc") # revealed: bool
reveal_type("abc" in x) # revealed: bool
```
#### Integers
```py
def get_int() -> int: ...
x = get_int()
if x != 1:
reveal_type(x) # revealed: int & ~Literal[1]
reveal_type(x != 1) # revealed: Literal[True]
reveal_type(x != 2) # revealed: bool
reveal_type(x == 1) # revealed: Literal[False]
reveal_type(x == 2) # revealed: bool
```
### Identity comparisons
```py
class A: ...
def get_object() -> object: ...
o = object()
a = A()
n = None
if o is not None:
reveal_type(o) # revealed: object & ~None
reveal_type(o is n) # revealed: Literal[False]
reveal_type(o is not n) # revealed: Literal[True]
```
## Diagnostics
### Unsupported operators for positive contributions
Raise an error if any of the positive contributions to the intersection type are unsupported for the
given operator:
```py
class Container:
def __contains__(self, x) -> bool: ...
class NonContainer: ...
def get_object() -> object: ...
x = get_object()
if isinstance(x, Container):
if isinstance(x, NonContainer):
reveal_type(x) # revealed: Container & NonContainer
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`"
reveal_type(2 in x) # revealed: bool
```
### Unsupported operators for negative contributions
Do *not* raise an error if any of the negative contributions to the intersection type are
unsupported for the given operator:
```py
class Container:
def __contains__(self, x) -> bool: ...
class NonContainer: ...
def get_object() -> object: ...
x = get_object()
if isinstance(x, Container):
if not isinstance(x, NonContainer):
reveal_type(x) # revealed: Container & ~NonContainer
# No error here!
reveal_type(2 in x) # revealed: bool
```

View File

@@ -9,5 +9,4 @@ try:
print
except as e: # error: [invalid-syntax]
reveal_type(e) # revealed: Unknown
```

View File

@@ -238,7 +238,7 @@ class Test:
def coinflip() -> bool:
return True
# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound"
# TODO: we should emit a diagnostic here (it might not be iterable)
for x in Test() if coinflip() else 42:
reveal_type(x) # revealed: int
```

View File

@@ -1,196 +0,0 @@
## Default
```py
class M(type): ...
reveal_type(M.__class__) # revealed: Literal[type]
```
## `object`
```py
reveal_type(object.__class__) # revealed: Literal[type]
```
## `type`
```py
reveal_type(type.__class__) # revealed: Literal[type]
```
## Basic
```py
class M(type): ...
class B(metaclass=M): ...
reveal_type(B.__class__) # revealed: Literal[M]
```
## Invalid metaclass
A class which doesn't inherit `type` (and/or doesn't implement a custom `__new__` accepting the same
arguments as `type.__new__`) isn't a valid metaclass.
```py
class M: ...
class A(metaclass=M): ...
# TODO: emit a diagnostic for the invalid metaclass
reveal_type(A.__class__) # revealed: Literal[M]
```
## Linear inheritance
If a class is a subclass of a class with a custom metaclass, then the subclass will also have that
metaclass.
```py
class M(type): ...
class A(metaclass=M): ...
class B(A): ...
reveal_type(B.__class__) # revealed: Literal[M]
```
## Conflict (1)
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a
subclass or the class itself.)
```py
class M1(type): ...
class M2(type): ...
class A(metaclass=M1): ...
class B(metaclass=M2): ...
# error: [conflicting-metaclass] "The metaclass of a derived class (`C`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
class C(A, B): ...
reveal_type(C.__class__) # revealed: Unknown
```
## Conflict (2)
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a
subclass or the class itself.)
```py
class M1(type): ...
class M2(type): ...
class A(metaclass=M1): ...
# error: [conflicting-metaclass] "The metaclass of a derived class (`B`) must be a subclass of the metaclasses of all its bases, but `M2` (metaclass of `B`) and `M1` (metaclass of base class `A`) have no subclass relationship"
class B(A, metaclass=M2): ...
reveal_type(B.__class__) # revealed: Unknown
```
## Common metaclass
A class has two explicit bases, both of which have the same metaclass.
```py
class M(type): ...
class A(metaclass=M): ...
class B(metaclass=M): ...
class C(A, B): ...
reveal_type(C.__class__) # revealed: Literal[M]
```
## Metaclass metaclass
A class has an explicit base with a custom metaclass. That metaclass itself has a custom metaclass.
```py
class M1(type): ...
class M2(type, metaclass=M1): ...
class M3(M2): ...
class A(metaclass=M3): ...
class B(A): ...
reveal_type(A.__class__) # revealed: Literal[M3]
```
## Diamond inheritance
```py
class M(type): ...
class M1(M): ...
class M2(M): ...
class M12(M1, M2): ...
class A(metaclass=M1): ...
class B(metaclass=M2): ...
class C(metaclass=M12): ...
# error: [conflicting-metaclass] "The metaclass of a derived class (`D`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
class D(A, B, C): ...
reveal_type(D.__class__) # revealed: Unknown
```
## Unknown
```py
from nonexistent_module import UnknownClass # error: [unresolved-import]
class C(UnknownClass): ...
# TODO: should be `type[type] & Unknown`
reveal_type(C.__class__) # revealed: Literal[type]
class M(type): ...
class A(metaclass=M): ...
class B(A, UnknownClass): ...
# TODO: should be `type[M] & Unknown`
reveal_type(B.__class__) # revealed: Literal[M]
```
## Duplicate
```py
class M(type): ...
class A(metaclass=M): ...
class B(A, A): ... # error: [duplicate-base] "Duplicate base class `A`"
reveal_type(B.__class__) # revealed: Literal[M]
```
## Non-class
When a class has an explicit `metaclass` that is not a class, but is a callable that accepts
`type.__new__` arguments, we should return the meta type of its return type.
```py
def f(*args, **kwargs) -> int: ...
class A(metaclass=f): ...
# TODO should be `type[int]`
reveal_type(A.__class__) # revealed: @Todo
```
## Cyclic
Retrieving the metaclass of a cyclically defined class should not cause an infinite loop.
```py path=a.pyi
class A(B): ... # error: [cyclic-class-def]
class B(C): ... # error: [cyclic-class-def]
class C(A): ... # error: [cyclic-class-def]
reveal_type(A.__class__) # revealed: Unknown
```
## PEP 695 generic
```py
class M(type): ...
class A[T: str](metaclass=M): ...
reveal_type(A.__class__) # revealed: Literal[M]
```

View File

@@ -1,244 +0,0 @@
# Narrowing for `issubclass` checks
Narrowing for `issubclass(class, classinfo)` expressions.
## `classinfo` is a single type
### Basic example
```py
def flag() -> bool: ...
t = int if flag() else str
if issubclass(t, bytes):
reveal_type(t) # revealed: Never
if issubclass(t, object):
reveal_type(t) # revealed: Literal[int, str]
if issubclass(t, int):
reveal_type(t) # revealed: Literal[int]
else:
reveal_type(t) # revealed: Literal[str]
if issubclass(t, str):
reveal_type(t) # revealed: Literal[str]
if issubclass(t, int):
reveal_type(t) # revealed: Never
```
### Proper narrowing in `elif` and `else` branches
```py
def flag() -> bool: ...
t = int if flag() else str if flag() else bytes
if issubclass(t, int):
reveal_type(t) # revealed: Literal[int]
else:
reveal_type(t) # revealed: Literal[str, bytes]
if issubclass(t, int):
reveal_type(t) # revealed: Literal[int]
elif issubclass(t, str):
reveal_type(t) # revealed: Literal[str]
else:
reveal_type(t) # revealed: Literal[bytes]
```
### Multiple derived classes
```py
class Base: ...
class Derived1(Base): ...
class Derived2(Base): ...
class Unrelated: ...
def flag() -> bool: ...
t1 = Derived1 if flag() else Derived2
if issubclass(t1, Base):
reveal_type(t1) # revealed: Literal[Derived1, Derived2]
if issubclass(t1, Derived1):
reveal_type(t1) # revealed: Literal[Derived1]
else:
reveal_type(t1) # revealed: Literal[Derived2]
t2 = Derived1 if flag() else Base
if issubclass(t2, Base):
reveal_type(t2) # revealed: Literal[Derived1, Base]
t3 = Derived1 if flag() else Unrelated
if issubclass(t3, Base):
reveal_type(t3) # revealed: Literal[Derived1]
else:
reveal_type(t3) # revealed: Literal[Unrelated]
```
### Narrowing for non-literals
```py
class A: ...
class B: ...
def get_class() -> type[object]: ...
t = get_class()
if issubclass(t, A):
reveal_type(t) # revealed: type[A]
if issubclass(t, B):
reveal_type(t) # revealed: type[A] & type[B]
else:
reveal_type(t) # revealed: type[object] & ~type[A]
```
### Handling of `None`
```py
from types import NoneType
def flag() -> bool: ...
t = int if flag() else NoneType
if issubclass(t, NoneType):
reveal_type(t) # revealed: Literal[NoneType]
if issubclass(t, type(None)):
# TODO: this should be just `Literal[NoneType]`
reveal_type(t) # revealed: Literal[int, NoneType]
```
## `classinfo` contains multiple types
### (Nested) tuples of types
```py
class Unrelated: ...
def flag() -> bool: ...
t = int if flag() else str if flag() else bytes
if issubclass(t, (int, (Unrelated, (bytes,)))):
reveal_type(t) # revealed: Literal[int, bytes]
else:
reveal_type(t) # revealed: Literal[str]
```
## Special cases
### Emit a diagnostic if the first argument is of wrong type
#### Too wide
`type[object]` is a subtype of `object`, but not every `object` can be passed as the first argument
to `issubclass`:
```py
class A: ...
def get_object() -> object: ...
t = get_object()
# TODO: we should emit a diagnostic here
if issubclass(t, A):
reveal_type(t) # revealed: type[A]
```
#### Wrong
`Literal[1]` and `type` are entirely disjoint, so the inferred type of `Literal[1] & type[int]` is
eagerly simplified to `Never` as a result of the type narrowing in the `if issubclass(t, int)`
branch:
```py
t = 1
# TODO: we should emit a diagnostic here
if issubclass(t, int):
reveal_type(t) # revealed: Never
```
### Do not use custom `issubclass` for narrowing
```py
def issubclass(c, ci):
return True
def flag() -> bool: ...
t = int if flag() else str
if issubclass(t, int):
reveal_type(t) # revealed: Literal[int, str]
```
### Do support narrowing if `issubclass` is aliased
```py
issubclass_alias = issubclass
def flag() -> bool: ...
t = int if flag() else str
if issubclass_alias(t, int):
reveal_type(t) # revealed: Literal[int]
```
### Do support narrowing if `issubclass` is imported
```py
from builtins import issubclass as imported_issubclass
def flag() -> bool: ...
t = int if flag() else str
if imported_issubclass(t, int):
reveal_type(t) # revealed: Literal[int]
```
### Do not narrow if second argument is not a proper `classinfo` argument
```py
from typing import Any
def flag() -> bool: ...
t = int if flag() else str
# TODO: this should cause us to emit a diagnostic during
# type checking
if issubclass(t, "str"):
reveal_type(t) # revealed: Literal[int, str]
# TODO: this should cause us to emit a diagnostic during
# type checking
if issubclass(t, (bytes, "str")):
reveal_type(t) # revealed: Literal[int, str]
# TODO: this should cause us to emit a diagnostic during
# type checking
if issubclass(t, Any):
reveal_type(t) # revealed: Literal[int, str]
```
### Do not narrow if there are keyword arguments
```py
def flag() -> bool: ...
t = int if flag() else str
# TODO: this should cause us to emit a diagnostic
# (`issubclass` has no `foo` parameter)
if issubclass(t, int, foo="bar"):
reveal_type(t) # revealed: Literal[int, str]
```

View File

@@ -60,7 +60,8 @@ reveal_type(typing.__init__) # revealed: Literal[__init__]
# These come from `builtins.object`, not `types.ModuleType`:
reveal_type(typing.__eq__) # revealed: Literal[__eq__]
reveal_type(typing.__class__) # revealed: Literal[type]
# TODO: understand properties
reveal_type(typing.__class__) # revealed: Literal[__class__]
# TODO: needs support for attribute access on instances, properties and generics;
# should be `dict[str, Any]`

View File

@@ -281,12 +281,8 @@ impl<'db> SemanticIndexBuilder<'db> {
debug_assert!(popped_assignment.is_some());
}
fn current_assignment(&self) -> Option<CurrentAssignment<'db>> {
self.current_assignments.last().copied()
}
fn current_assignment_mut(&mut self) -> Option<&mut CurrentAssignment<'db>> {
self.current_assignments.last_mut()
fn current_assignment(&self) -> Option<&CurrentAssignment<'db>> {
self.current_assignments.last()
}
fn add_pattern_constraint(
@@ -631,7 +627,6 @@ where
ast::Expr::List(_) | ast::Expr::Tuple(_) => {
Some(CurrentAssignment::Assign {
node,
first: true,
unpack: Some(Unpack::new(
self.db,
self.file,
@@ -645,11 +640,9 @@ where
)),
})
}
ast::Expr::Name(_) => Some(CurrentAssignment::Assign {
node,
unpack: None,
first: false,
}),
ast::Expr::Name(_) => {
Some(CurrentAssignment::Assign { node, unpack: None })
}
_ => None,
};
@@ -994,19 +987,14 @@ where
}
if is_definition {
match self.current_assignment() {
Some(CurrentAssignment::Assign {
node,
first,
unpack,
}) => {
match self.current_assignment().copied() {
Some(CurrentAssignment::Assign { node, unpack }) => {
self.add_definition(
symbol,
AssignmentDefinitionNodeRef {
unpack,
value: &node.value,
name: name_node,
first,
},
);
}
@@ -1057,11 +1045,6 @@ where
}
}
if let Some(CurrentAssignment::Assign { first, .. }) = self.current_assignment_mut()
{
*first = false;
}
walk_expr(self, expr);
}
ast::Expr::Named(node) => {
@@ -1262,7 +1245,6 @@ where
enum CurrentAssignment<'a> {
Assign {
node: &'a ast::StmtAssign,
first: bool,
unpack: Option<Unpack<'a>>,
},
AnnAssign(&'a ast::StmtAnnAssign),

View File

@@ -183,7 +183,6 @@ pub(crate) struct AssignmentDefinitionNodeRef<'a> {
pub(crate) unpack: Option<Unpack<'a>>,
pub(crate) value: &'a ast::Expr,
pub(crate) name: &'a ast::ExprName,
pub(crate) first: bool,
}
#[derive(Copy, Clone, Debug)]
@@ -251,12 +250,10 @@ impl<'db> DefinitionNodeRef<'db> {
unpack,
value,
name,
first,
}) => DefinitionKind::Assignment(AssignmentDefinitionKind {
target: TargetKind::from(unpack),
value: AstNodeRef::new(parsed.clone(), value),
name: AstNodeRef::new(parsed, name),
first,
}),
DefinitionNodeRef::AnnotatedAssignment(assign) => {
DefinitionKind::AnnotatedAssignment(AstNodeRef::new(parsed, assign))
@@ -333,7 +330,6 @@ impl<'db> DefinitionNodeRef<'db> {
value: _,
unpack: _,
name,
first: _,
}) => name.into(),
Self::AnnotatedAssignment(node) => node.into(),
Self::AugmentedAssignment(node) => node.into(),
@@ -539,11 +535,10 @@ pub struct AssignmentDefinitionKind<'db> {
target: TargetKind<'db>,
value: AstNodeRef<ast::Expr>,
name: AstNodeRef<ast::ExprName>,
first: bool,
}
impl<'db> AssignmentDefinitionKind<'db> {
pub(crate) fn target(&self) -> TargetKind<'db> {
impl AssignmentDefinitionKind<'_> {
pub(crate) fn target(&self) -> TargetKind {
self.target
}
@@ -554,10 +549,6 @@ impl<'db> AssignmentDefinitionKind<'db> {
pub(crate) fn name(&self) -> &ast::ExprName {
self.name.node()
}
pub(crate) fn is_first(&self) -> bool {
self.first
}
}
#[derive(Clone, Debug)]

View File

@@ -3,7 +3,7 @@ use crate::{
Db,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum Boundness {
Bound,
MayBeUnbound,
@@ -44,13 +44,17 @@ impl<'db> Symbol<'db> {
}
}
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
pub(crate) fn unwrap_or(&self, other: Type<'db>) -> Type<'db> {
match self {
Symbol::Type(ty, _) => *ty,
Symbol::Unbound => Type::Unknown,
Symbol::Unbound => other,
}
}
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
self.unwrap_or(Type::Unknown)
}
pub(crate) fn as_type(&self) -> Option<Type<'db>> {
match self {
Symbol::Type(ty, _) => Some(*ty),

File diff suppressed because it is too large Load Diff

View File

@@ -246,7 +246,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
}
} else {
// ~Literal[True] & bool = Literal[False]
if let Type::Instance(InstanceType { class }) = new_positive {
if let Type::Instance(InstanceType { class, .. }) = new_positive {
if class.is_known(db, KnownClass::Bool) {
if let Some(&Type::BooleanLiteral(value)) = self
.negative
@@ -317,7 +317,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
// Adding any of these types to the negative side of an intersection
// is equivalent to adding it to the positive side. We do this to
// simplify the representation.
self.add_positive(db, ty);
self.positive.insert(ty);
}
// ~Literal[True] & bool = Literal[False]
Type::BooleanLiteral(bool)
@@ -592,22 +592,6 @@ mod tests {
assert_eq!(ta_not_i0.display(&db).to_string(), "int & Any | Literal[1]");
}
#[test]
fn build_intersection_simplify_negative_any() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_negative(Type::Any)
.build();
assert_eq!(ty, Type::Any);
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::Never)
.add_negative(Type::Any)
.build();
assert_eq!(ty, Type::Never);
}
#[test]
fn intersection_distributes_over_union() {
let db = setup_db();

View File

@@ -1,15 +1,199 @@
use crate::types::{ClassLiteralType, Type};
use crate::Db;
use ruff_db::diagnostic::{Diagnostic, Severity};
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic, Severity};
use ruff_db::files::File;
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_text_size::{Ranged, TextRange};
use std::borrow::Cow;
use std::fmt::Formatter;
use std::ops::Deref;
use std::sync::Arc;
#[derive(Debug, Eq, PartialEq, Clone)]
use crate::types::{ClassLiteralType, Type};
use crate::Db;
/// Returns `true` if any diagnostic is enabled for this file.
pub(crate) fn is_any_diagnostic_enabled(db: &dyn Db, file: File) -> bool {
db.is_file_open(file)
}
pub(crate) fn report_type_diagnostic(
db: &dyn Db,
file: File,
node: AnyNodeRef,
rule: &str,
message: std::fmt::Arguments,
) {
if !is_any_diagnostic_enabled(db, file) {
return;
}
// TODO: Don't emit the diagnostic if:
// * The enclosing node contains any syntax errors
// * The rule is disabled for this file. We probably want to introduce a new query that
// returns a rule selector for a given file that respects the package's settings,
// any global pragma comments in the file, and any per-file-ignores.
CompileDiagnostic::report(
db.upcast(),
TypeCheckDiagnostic {
file,
rule: rule.to_string(),
message: message.to_string(),
range: node.range(),
},
);
}
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
pub(super) fn report_not_iterable(
db: &dyn Db,
file: File,
node: AnyNodeRef,
not_iterable_ty: Type,
) {
report_type_diagnostic(
db,
file,
node,
"not-iterable",
format_args!(
"Object of type `{}` is not iterable",
not_iterable_ty.display(db)
),
);
}
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
pub(super) fn report_index_out_of_bounds(
db: &dyn Db,
file: File,
kind: &'static str,
node: AnyNodeRef,
tuple_ty: Type,
length: usize,
index: i64,
) {
report_type_diagnostic(
db,
file,
node,
"index-out-of-bounds",
format_args!(
"Index {index} is out of bounds for {kind} `{}` with length {length}",
tuple_ty.display(db)
),
);
}
/// Emit a diagnostic declaring that a type does not support subscripting.
pub(super) fn report_non_subscriptable(
db: &dyn Db,
file: File,
node: AnyNodeRef,
non_subscriptable_ty: Type,
method: &str,
) {
report_type_diagnostic(
db,
file,
node,
"non-subscriptable",
format_args!(
"Cannot subscript object of type `{}` with no `{method}` method",
non_subscriptable_ty.display(db)
),
);
}
pub(super) fn report_unresolved_module<'a>(
db: &dyn Db,
file: File,
import_node: impl Into<AnyNodeRef<'a>>,
level: u32,
module: Option<&str>,
) {
report_type_diagnostic(
db,
file,
import_node.into(),
"unresolved-import",
format_args!(
"Cannot resolve import `{}{}`",
".".repeat(level as usize),
module.unwrap_or_default()
),
);
}
pub(super) fn report_slice_step_size_zero(db: &dyn Db, file: File, node: AnyNodeRef) {
report_type_diagnostic(
db,
file,
node,
"zero-stepsize-in-slice",
format_args!("Slice step size can not be zero"),
);
}
pub(super) fn report_invalid_assignment(
db: &dyn Db,
file: File,
node: AnyNodeRef,
declared_ty: Type,
assigned_ty: Type,
) {
match declared_ty {
Type::ClassLiteral(ClassLiteralType { class }) => {
report_type_diagnostic(db, file, node, "invalid-assignment", format_args!(
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
class.name(db)));
}
Type::FunctionLiteral(function) => {
report_type_diagnostic(db, file, node, "invalid-assignment", format_args!(
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
function.name(db)));
}
_ => {
report_type_diagnostic(
db,
file,
node,
"invalid-assignment",
format_args!(
"Object of type `{}` is not assignable to `{}`",
assigned_ty.display(db),
declared_ty.display(db),
),
);
}
}
}
pub(super) fn report_possibly_unresolved_reference(
db: &dyn Db,
file: File,
expr_name_node: &ast::ExprName,
) {
let ast::ExprName { id, .. } = expr_name_node;
report_type_diagnostic(
db,
file,
expr_name_node.into(),
"possibly-unresolved-reference",
format_args!("Name `{id}` used when possibly not defined"),
);
}
pub(super) fn report_unresolved_reference(db: &dyn Db, file: File, expr_name_node: &ast::ExprName) {
let ast::ExprName { id, .. } = expr_name_node;
report_type_diagnostic(
db,
file,
expr_name_node.into(),
"unresolved-reference",
format_args!("Name `{id}` used when not defined"),
);
}
#[derive(Debug, Eq, PartialEq)]
pub struct TypeCheckDiagnostic {
// TODO: Don't use string keys for rules
pub(super) rule: String,
@@ -23,304 +207,29 @@ impl TypeCheckDiagnostic {
&self.rule
}
pub fn message(&self) -> &str {
&self.message
}
pub fn file(&self) -> File {
self.file
pub fn range(&self) -> TextRange {
self.range
}
}
impl Diagnostic for TypeCheckDiagnostic {
fn rule(&self) -> &str {
TypeCheckDiagnostic::rule(self)
}
fn message(&self) -> Cow<str> {
TypeCheckDiagnostic::message(self).into()
fn message(&self) -> std::borrow::Cow<str> {
Cow::Borrowed(&self.message)
}
fn file(&self) -> File {
TypeCheckDiagnostic::file(self)
self.file
}
fn range(&self) -> Option<TextRange> {
Some(Ranged::range(self))
Some(self.range)
}
fn severity(&self) -> Severity {
Severity::Error
}
}
impl Ranged for TypeCheckDiagnostic {
fn range(&self) -> TextRange {
self.range
}
}
/// A collection of type check diagnostics.
///
/// The diagnostics are wrapped in an `Arc` because they need to be cloned multiple times
/// when going from `infer_expression` to `check_file`. We could consider
/// making [`TypeCheckDiagnostic`] a Salsa struct to have them Arena-allocated (once the Tables refactor is done).
/// Using Salsa struct does have the downside that it leaks the Salsa dependency into diagnostics and
/// each Salsa-struct comes with an overhead.
#[derive(Default, Eq, PartialEq)]
pub struct TypeCheckDiagnostics {
inner: Vec<std::sync::Arc<TypeCheckDiagnostic>>,
}
impl TypeCheckDiagnostics {
pub fn new() -> Self {
Self { inner: Vec::new() }
}
pub(super) fn push(&mut self, diagnostic: TypeCheckDiagnostic) {
self.inner.push(Arc::new(diagnostic));
}
pub(crate) fn shrink_to_fit(&mut self) {
self.inner.shrink_to_fit();
}
}
impl Extend<TypeCheckDiagnostic> for TypeCheckDiagnostics {
fn extend<T: IntoIterator<Item = TypeCheckDiagnostic>>(&mut self, iter: T) {
self.inner.extend(iter.into_iter().map(std::sync::Arc::new));
}
}
impl Extend<std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
fn extend<T: IntoIterator<Item = Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
self.inner.extend(iter);
}
}
impl<'a> Extend<&'a std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
fn extend<T: IntoIterator<Item = &'a Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
self.inner
.extend(iter.into_iter().map(std::sync::Arc::clone));
}
}
impl std::fmt::Debug for TypeCheckDiagnostics {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl Deref for TypeCheckDiagnostics {
type Target = [std::sync::Arc<TypeCheckDiagnostic>];
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl IntoIterator for TypeCheckDiagnostics {
type Item = Arc<TypeCheckDiagnostic>;
type IntoIter = std::vec::IntoIter<std::sync::Arc<TypeCheckDiagnostic>>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
impl<'a> IntoIterator for &'a TypeCheckDiagnostics {
type Item = &'a Arc<TypeCheckDiagnostic>;
type IntoIter = std::slice::Iter<'a, std::sync::Arc<TypeCheckDiagnostic>>;
fn into_iter(self) -> Self::IntoIter {
self.inner.iter()
}
}
pub(super) struct TypeCheckDiagnosticsBuilder<'db> {
db: &'db dyn Db,
file: File,
diagnostics: TypeCheckDiagnostics,
}
impl<'db> TypeCheckDiagnosticsBuilder<'db> {
pub(super) fn new(db: &'db dyn Db, file: File) -> Self {
Self {
db,
file,
diagnostics: TypeCheckDiagnostics::new(),
}
}
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
pub(super) fn add_not_iterable(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
self.add(
node,
"not-iterable",
format_args!(
"Object of type `{}` is not iterable",
not_iterable_ty.display(self.db)
),
);
}
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
/// because its `__iter__` method is possibly unbound.
pub(super) fn add_not_iterable_possibly_unbound(
&mut self,
node: AnyNodeRef,
element_ty: Type<'db>,
) {
self.add(
node,
"not-iterable",
format_args!(
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
element_ty.display(self.db)
),
);
}
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
pub(super) fn add_index_out_of_bounds(
&mut self,
kind: &'static str,
node: AnyNodeRef,
tuple_ty: Type<'db>,
length: usize,
index: i64,
) {
self.add(
node,
"index-out-of-bounds",
format_args!(
"Index {index} is out of bounds for {kind} `{}` with length {length}",
tuple_ty.display(self.db)
),
);
}
/// Emit a diagnostic declaring that a type does not support subscripting.
pub(super) fn add_non_subscriptable(
&mut self,
node: AnyNodeRef,
non_subscriptable_ty: Type<'db>,
method: &str,
) {
self.add(
node,
"non-subscriptable",
format_args!(
"Cannot subscript object of type `{}` with no `{method}` method",
non_subscriptable_ty.display(self.db)
),
);
}
pub(super) fn add_unresolved_module(
&mut self,
import_node: impl Into<AnyNodeRef<'db>>,
level: u32,
module: Option<&str>,
) {
self.add(
import_node.into(),
"unresolved-import",
format_args!(
"Cannot resolve import `{}{}`",
".".repeat(level as usize),
module.unwrap_or_default()
),
);
}
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
self.add(
node,
"zero-stepsize-in-slice",
format_args!("Slice step size can not be zero"),
);
}
pub(super) fn add_invalid_assignment(
&mut self,
node: AnyNodeRef,
declared_ty: Type<'db>,
assigned_ty: Type<'db>,
) {
match declared_ty {
Type::ClassLiteral(ClassLiteralType { class }) => {
self.add(node, "invalid-assignment", format_args!(
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
class.name(self.db)));
}
Type::FunctionLiteral(function) => {
self.add(node, "invalid-assignment", format_args!(
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
function.name(self.db)));
}
_ => {
self.add(
node,
"invalid-assignment",
format_args!(
"Object of type `{}` is not assignable to `{}`",
assigned_ty.display(self.db),
declared_ty.display(self.db),
),
);
}
}
}
pub(super) fn add_possibly_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
let ast::ExprName { id, .. } = expr_name_node;
self.add(
expr_name_node.into(),
"possibly-unresolved-reference",
format_args!("Name `{id}` used when possibly not defined"),
);
}
pub(super) fn add_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
let ast::ExprName { id, .. } = expr_name_node;
self.add(
expr_name_node.into(),
"unresolved-reference",
format_args!("Name `{id}` used when not defined"),
);
}
/// Adds a new diagnostic.
///
/// The diagnostic does not get added if the rule isn't enabled for this file.
pub(super) fn add(&mut self, node: AnyNodeRef, rule: &str, message: std::fmt::Arguments) {
if !self.db.is_file_open(self.file) {
return;
}
// TODO: Don't emit the diagnostic if:
// * The enclosing node contains any syntax errors
// * The rule is disabled for this file. We probably want to introduce a new query that
// returns a rule selector for a given file that respects the package's settings,
// any global pragma comments in the file, and any per-file-ignores.
self.diagnostics.push(TypeCheckDiagnostic {
file: self.file,
rule: rule.to_string(),
message: message.to_string(),
range: node.range(),
});
}
pub(super) fn extend(&mut self, diagnostics: &TypeCheckDiagnostics) {
self.diagnostics.extend(diagnostics);
}
pub(super) fn finish(mut self) -> TypeCheckDiagnostics {
self.diagnostics.shrink_to_fit();
self.diagnostics
fn rule(&self) -> &str {
&self.rule
}
}

View File

@@ -6,9 +6,7 @@ use ruff_db::display::FormatterJoinExtension;
use ruff_python_ast::str::Quote;
use ruff_python_literal::escape::AsciiEscape;
use crate::types::{
ClassLiteralType, InstanceType, IntersectionType, KnownClass, SubclassOfType, Type, UnionType,
};
use crate::types::{ClassLiteralType, InstanceType, IntersectionType, KnownClass, Type, UnionType};
use crate::Db;
use rustc_hash::FxHashMap;
@@ -66,7 +64,7 @@ impl Display for DisplayRepresentation<'_> {
Type::Any => f.write_str("Any"),
Type::Never => f.write_str("Never"),
Type::Unknown => f.write_str("Unknown"),
Type::Instance(InstanceType { class })
Type::Instance(InstanceType { class, .. })
if class.is_known(self.db, KnownClass::NoneType) =>
{
f.write_str("None")
@@ -79,11 +77,10 @@ impl Display for DisplayRepresentation<'_> {
}
// TODO functions and classes should display using a fully qualified name
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
Type::SubclassOf(SubclassOfType { class }) => {
write!(f, "type[{}]", class.name(self.db))
}
Type::Instance(InstanceType { class }) => f.write_str(class.name(self.db)),
Type::KnownInstance(known_instance) => f.write_str(known_instance.as_str()),
Type::Instance(InstanceType { class, known }) => f.write_str(match known {
Some(super::KnownInstance::Literal) => "Literal",
_ => class.name(self.db),
}),
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
Type::Union(union) => union.display(self.db).fmt(f),
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ use indexmap::IndexSet;
use itertools::Either;
use rustc_hash::FxHashSet;
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type};
use super::{Class, ClassLiteralType, KnownClass, Type};
use crate::Db;
/// The inferred method resolution order of a given class.
@@ -377,11 +377,7 @@ impl<'db> ClassBase<'db> {
| Type::LiteralString
| Type::Tuple(_)
| Type::SliceLiteral(_)
| Type::ModuleLiteral(_)
| Type::SubclassOf(_) => None,
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::Literal => None,
},
| Type::ModuleLiteral(_) => None,
}
}

View File

@@ -5,8 +5,8 @@ use crate::semantic_index::expression::Expression;
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
use crate::semantic_index::symbol_table;
use crate::types::{
infer_expression_types, ClassLiteralType, InstanceType, IntersectionBuilder, KnownClass,
KnownConstraintFunction, KnownFunction, Truthiness, Type, UnionBuilder,
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass, KnownFunction,
Truthiness, Type, UnionBuilder,
};
use crate::Db;
use itertools::Itertools;
@@ -78,27 +78,24 @@ fn all_negative_narrowing_constraints_for_expression<'db>(
NarrowingConstraintsBuilder::new(db, ConstraintNode::Expression(expression), false).finish()
}
/// Generate a constraint from the type of a `classinfo` argument to `isinstance` or `issubclass`.
/// Generate a constraint from the *type* of the second argument of an `isinstance` call.
///
/// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
fn generate_classinfo_constraint<'db, F>(
/// Example: for `isinstance(…, str)`, we would infer `Type::ClassLiteral(str)` from the
/// second argument, but we need to generate a `Type::Instance(str)` constraint that can
/// be used to narrow down the type of the first argument.
fn generate_isinstance_constraint<'db>(
db: &'db dyn Db,
classinfo: &Type<'db>,
to_constraint: F,
) -> Option<Type<'db>>
where
F: Fn(ClassLiteralType<'db>) -> Type<'db> + Copy,
{
) -> Option<Type<'db>> {
match classinfo {
Type::ClassLiteral(ClassLiteralType { class }) => Some(Type::anonymous_instance(*class)),
Type::Tuple(tuple) => {
let mut builder = UnionBuilder::new(db);
for element in tuple.elements(db) {
builder = builder.add(generate_classinfo_constraint(db, element, to_constraint)?);
builder = builder.add(generate_isinstance_constraint(db, element)?);
}
Some(builder.build())
}
Type::ClassLiteral(class_literal_type) => Some(to_constraint(*class_literal_type)),
_ => None,
}
}
@@ -333,51 +330,34 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
let scope = self.scope();
let inference = infer_expression_types(self.db, expression);
// TODO: add support for PEP 604 union types on the right hand side of `isinstance`
// and `issubclass`, for example `isinstance(x, str | (int | float))`.
match inference
if let Some(func_type) = inference
.expression_ty(expr_call.func.scoped_ast_id(self.db, scope))
.into_function_literal()
.and_then(|f| f.known(self.db))
.and_then(KnownFunction::constraint_function)
{
Some(function) if expr_call.arguments.keywords.is_empty() => {
if let [ast::Expr::Name(ast::ExprName { id, .. }), class_info] =
&*expr_call.arguments.args
if func_type.is_known(self.db, KnownFunction::IsInstance)
&& expr_call.arguments.keywords.is_empty()
{
if let [ast::Expr::Name(ast::ExprName { id, .. }), rhs] = &*expr_call.arguments.args
{
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
let class_info_ty =
inference.expression_ty(class_info.scoped_ast_id(self.db, scope));
let rhs_type = inference.expression_ty(rhs.scoped_ast_id(self.db, scope));
let to_constraint = match function {
KnownConstraintFunction::IsInstance => {
|class_literal: ClassLiteralType<'db>| {
Type::Instance(InstanceType {
class: class_literal.class,
})
}
// TODO: add support for PEP 604 union types on the right hand side:
// isinstance(x, str | (int | float))
if let Some(mut constraint) = generate_isinstance_constraint(self.db, &rhs_type)
{
if !is_positive {
constraint = constraint.negate(self.db);
}
KnownConstraintFunction::IsSubclass => {
|class_literal: ClassLiteralType<'db>| {
Type::SubclassOf(class_literal.to_subclass_of_type())
}
}
};
generate_classinfo_constraint(self.db, &class_info_ty, to_constraint).map(
|constraint| {
let mut constraints = NarrowingConstraints::default();
constraints.insert(symbol, constraint.negate_if(self.db, !is_positive));
constraints
},
)
} else {
None
let mut constraints = NarrowingConstraints::default();
constraints.insert(symbol, constraint);
return Some(constraints);
}
}
}
_ => None,
}
None
}
fn evaluate_match_pattern_singleton(

View File

@@ -6,14 +6,14 @@ use rustc_hash::FxHashMap;
use crate::semantic_index::ast_ids::{HasScopedAstId, ScopedExpressionId};
use crate::semantic_index::symbol::ScopeId;
use crate::types::{TupleType, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
use crate::types::{TupleType, Type};
use crate::Db;
/// Unpacks the value expression type to their respective targets.
pub(crate) struct Unpacker<'db> {
db: &'db dyn Db,
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
diagnostics: TypeCheckDiagnosticsBuilder<'db>,
file: File,
}
impl<'db> Unpacker<'db> {
@@ -21,7 +21,7 @@ impl<'db> Unpacker<'db> {
Self {
db,
targets: FxHashMap::default(),
diagnostics: TypeCheckDiagnosticsBuilder::new(db, file),
file,
}
}
@@ -104,9 +104,11 @@ impl<'db> Unpacker<'db> {
let value_ty = if value_ty.is_literal_string() {
Type::LiteralString
} else {
value_ty
.iterate(self.db)
.unwrap_with_diagnostic(AnyNodeRef::from(target), &mut self.diagnostics)
value_ty.iterate(self.db).unwrap_with_diagnostic(
self.db,
AnyNodeRef::from(target),
self.file,
)
};
for element in elts {
self.unpack(element, value_ty, scope);
@@ -120,7 +122,6 @@ impl<'db> Unpacker<'db> {
pub(crate) fn finish(mut self) -> UnpackResult<'db> {
self.targets.shrink_to_fit();
UnpackResult {
diagnostics: self.diagnostics.finish(),
targets: self.targets,
}
}
@@ -129,15 +130,10 @@ impl<'db> Unpacker<'db> {
#[derive(Debug, Default, PartialEq, Eq)]
pub(crate) struct UnpackResult<'db> {
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
diagnostics: TypeCheckDiagnostics,
}
impl<'db> UnpackResult<'db> {
pub(crate) fn get(&self, expr_id: ScopedExpressionId) -> Option<Type<'db>> {
self.targets.get(&expr_id).copied()
}
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {
&self.diagnostics
}
}

View File

@@ -7,13 +7,14 @@ use lsp_types::{
RelatedFullDocumentDiagnosticReport, Url,
};
use crate::edit::ToRangeExt;
use red_knot_workspace::db::{Db, RootDatabase};
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic as _, Severity};
use ruff_db::source::{line_index, source_text};
use crate::edit::ToRangeExt as _;
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
use crate::server::{client::Notifier, Result};
use crate::session::DocumentSnapshot;
use red_knot_workspace::db::{Db, RootDatabase};
use ruff_db::diagnostic::Severity;
use ruff_db::source::{line_index, source_text};
pub(crate) struct DocumentDiagnosticRequestHandler;
@@ -72,7 +73,7 @@ fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &RootDatabase) -> Vec<Di
fn to_lsp_diagnostic(
db: &dyn Db,
diagnostic: &dyn ruff_db::diagnostic::Diagnostic,
diagnostic: &CompileDiagnostic,
encoding: crate::PositionEncoding,
) -> Diagnostic {
let range = if let Some(range) = diagnostic.range() {

View File

@@ -27,9 +27,8 @@ where
.map(|diagnostic| DiagnosticWithLine {
line_number: diagnostic
.range()
.map_or(OneIndexed::from_zero_indexed(0), |range| {
line_index.line_index(range.start())
}),
.map(|range| line_index.line_index(range.start()))
.unwrap_or(OneIndexed::from_zero_indexed(0)),
diagnostic,
})
.collect();
@@ -143,14 +142,13 @@ struct DiagnosticWithLine<T> {
#[cfg(test)]
mod tests {
use crate::db::Db;
use crate::diagnostic::Diagnostic;
use ruff_db::diagnostic::Severity;
use ruff_db::diagnostic::{Diagnostic, Severity};
use ruff_db::files::{system_path_to_file, File};
use ruff_db::source::line_index;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_source_file::OneIndexed;
use ruff_text_size::{TextRange, TextSize};
use std::borrow::Cow;
use std::fmt::Debug;
#[test]
fn sort_and_group() {
@@ -167,7 +165,7 @@ mod tests {
let diagnostics: Vec<_> = ranges
.into_iter()
.map(|range| DummyDiagnostic { range, file })
.map(|range| DummyDiagnostic { file, range })
.collect();
let sorted = super::SortedDiagnostics::new(diagnostics, &lines);
@@ -185,8 +183,8 @@ mod tests {
#[derive(Debug)]
struct DummyDiagnostic {
range: TextRange,
file: File,
range: TextRange,
}
impl Diagnostic for DummyDiagnostic {
@@ -194,8 +192,8 @@ mod tests {
"dummy"
}
fn message(&self) -> Cow<str> {
"dummy".into()
fn message(&self) -> std::borrow::Cow<str> {
"Dummy error".into()
}
fn file(&self) -> File {

View File

@@ -1,9 +1,8 @@
use colored::Colorize;
use parser as test_parser;
use red_knot_python_semantic::types::check_types;
use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic};
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic as _};
use ruff_db::files::{system_path_to_file, File, Files};
use ruff_db::parsed::parsed_module;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_source_file::LineIndex;
use ruff_text_size::TextSize;
@@ -86,24 +85,13 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
let failures: Failures = test_files
.into_iter()
.filter_map(|test_file| {
let parsed = parsed_module(db, test_file.file);
let mut diagnostics: Vec<Box<_>> = parsed
.errors()
.iter()
.cloned()
.map(|error| {
let diagnostic: Box<dyn Diagnostic> =
Box::new(ParseDiagnostic::new(test_file.file, error));
diagnostic
})
.collect();
let type_diagnostics = check_types(db, test_file.file);
diagnostics.extend(type_diagnostics.into_iter().map(|diagnostic| {
let diagnostic: Box<dyn Diagnostic> = Box::new((*diagnostic).clone());
diagnostic
}));
let diagnostics: Vec<_> =
// The accumulator returns all diagnostics from all files. We're only interested in
// diagnostics from this file.
check_types::accumulated::<CompileDiagnostic>(db, test_file.file)
.into_iter()
.filter(|diagnostic| diagnostic.file() == test_file.file)
.collect();
match matcher::match_file(db, test_file.file, diagnostics) {
Ok(()) => None,

View File

@@ -318,6 +318,7 @@ mod tests {
use ruff_text_size::TextRange;
use std::borrow::Cow;
#[derive(Clone, Debug)]
struct ExpectedDiagnostic {
rule: &'static str,
message: &'static str,
@@ -336,20 +337,20 @@ mod tests {
fn into_diagnostic(self, file: File) -> TestDiagnostic {
TestDiagnostic {
file,
rule: self.rule,
message: self.message,
range: self.range,
file,
}
}
}
#[derive(Debug)]
struct TestDiagnostic {
file: File,
rule: &'static str,
message: &'static str,
range: TextRange,
file: File,
}
impl Diagnostic for TestDiagnostic {
@@ -358,7 +359,7 @@ mod tests {
}
fn message(&self) -> Cow<str> {
self.message.into()
Cow::Borrowed(self.message)
}
fn file(&self) -> File {
@@ -384,15 +385,15 @@ mod tests {
db.write_file("/src/test.py", source).unwrap();
let file = system_path_to_file(&db, "/src/test.py").unwrap();
super::match_file(
&db,
file,
diagnostics
.into_iter()
.map(|diagnostic| diagnostic.into_diagnostic(file)),
)
let diagnostics: Vec<_> = diagnostics
.into_iter()
.map(|diagnostic| diagnostic.into_diagnostic(file))
.collect();
super::match_file(&db, file, diagnostics)
}
#[track_caller]
fn assert_fail(result: Result<(), FailuresByLine>, messages: &[(usize, &[&str])]) {
let Err(failures) = result else {
panic!("expected a failure");
@@ -415,6 +416,7 @@ mod tests {
assert_eq!(failures, expected);
}
#[track_caller]
fn assert_ok(result: &Result<(), FailuresByLine>) {
assert!(result.is_ok(), "{result:?}");
}

View File

@@ -1,12 +1,12 @@
use std::any::Any;
use js_sys::Error;
use ruff_db::diagnostic::CompileDiagnostic;
use wasm_bindgen::prelude::*;
use red_knot_workspace::db::RootDatabase;
use red_knot_workspace::db::{Db, RootDatabase};
use red_knot_workspace::workspace::settings::Configuration;
use red_knot_workspace::workspace::WorkspaceMetadata;
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{system_path_to_file, File};
use ruff_db::system::walk_directory::WalkDirectoryBuilder;
use ruff_db::system::{
@@ -111,20 +111,14 @@ impl Workspace {
pub fn check_file(&self, file_id: &FileHandle) -> Result<Vec<String>, Error> {
let result = self.db.check_file(file_id.file).map_err(into_error)?;
Ok(result
.into_iter()
.map(|diagnostic| diagnostic.display(&self.db).to_string())
.collect())
Ok(map_diagnostics(&self.db, result))
}
/// Checks all open files
pub fn check(&self) -> Result<Vec<String>, Error> {
let result = self.db.check().map_err(into_error)?;
Ok(result
.into_iter()
.map(|diagnostic| diagnostic.display(&self.db).to_string())
.collect())
Ok(map_diagnostics(&self.db, result))
}
/// Returns the parsed AST for `path`
@@ -149,6 +143,13 @@ impl Workspace {
}
}
fn map_diagnostics(db: &dyn Db, diagnostics: Vec<CompileDiagnostic>) -> Vec<String> {
diagnostics
.into_iter()
.map(|diagnostic| diagnostic.display(db.upcast()).to_string())
.collect()
}
pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
Error::new(&err.to_string())
}

View File

@@ -17,7 +17,6 @@ red_knot_python_semantic = { workspace = true }
ruff_cache = { workspace = true }
ruff_db = { workspace = true, features = ["os", "cache"] }
ruff_python_ast = { workspace = true }
ruff_text_size = { workspace = true }
red_knot_vendored = { workspace = true }
anyhow = { workspace = true }

View File

@@ -1,17 +1,18 @@
use std::panic::RefUnwindSafe;
use std::sync::Arc;
use ruff_db::diagnostic::CompileDiagnostic;
use salsa::plumbing::ZalsaDatabase;
use salsa::{Cancelled, Event};
use crate::workspace::{check_file, Workspace, WorkspaceMetadata};
use red_knot_python_semantic::{Db as SemanticDb, Program};
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{File, Files};
use ruff_db::system::System;
use ruff_db::vendored::VendoredFileSystem;
use ruff_db::{Db as SourceDb, Upcast};
use crate::workspace::{Workspace, WorkspaceMetadata};
mod changes;
#[salsa::db]
@@ -51,14 +52,14 @@ impl RootDatabase {
}
/// Checks all open files in the workspace and its dependencies.
pub fn check(&self) -> Result<Vec<Box<dyn Diagnostic>>, Cancelled> {
pub fn check(&self) -> Result<Vec<CompileDiagnostic>, Cancelled> {
self.with_db(|db| db.workspace().check(db))
}
pub fn check_file(&self, file: File) -> Result<Vec<Box<dyn Diagnostic>>, Cancelled> {
pub fn check_file(&self, file: File) -> Result<Vec<CompileDiagnostic>, Cancelled> {
let _span = tracing::debug_span!("check_file", file=%file.path(self)).entered();
self.with_db(|db| check_file(db, file))
self.with_db(|db| db.workspace().check_file(db, file))
}
/// Returns a mutable reference to the system.

View File

@@ -1,23 +1,22 @@
use rustc_hash::{FxBuildHasher, FxHashSet};
use salsa::{Durability, Setter as _};
use std::borrow::Cow;
use std::{collections::BTreeMap, sync::Arc};
use crate::db::Db;
use crate::db::RootDatabase;
use crate::workspace::files::{Index, Indexed, IndexedIter, PackageFiles};
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic};
use rustc_hash::{FxBuildHasher, FxHashSet};
use salsa::{Durability, Setter as _};
pub use metadata::{PackageMetadata, WorkspaceMetadata};
use red_knot_python_semantic::types::check_types;
use red_knot_python_semantic::SearchPathSettings;
use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic, Severity};
use ruff_db::parsed::parsed_module;
use ruff_db::source::{source_text, SourceTextError};
use ruff_db::source::source_text;
use ruff_db::{
files::{system_path_to_file, File},
system::{walk_directory::WalkState, SystemPath, SystemPathBuf},
};
use ruff_python_ast::{name::Name, PySourceType};
use ruff_text_size::TextRange;
use crate::db::Db;
use crate::db::RootDatabase;
use crate::workspace::files::{Index, Indexed, IndexedIter, PackageFiles};
mod files;
mod metadata;
@@ -188,7 +187,7 @@ impl Workspace {
}
/// Checks all open files in the workspace and its dependencies.
pub fn check(self, db: &RootDatabase) -> Vec<Box<dyn Diagnostic>> {
pub fn check(self, db: &RootDatabase) -> Vec<CompileDiagnostic> {
let workspace_span = tracing::debug_span!("check_workspace");
let _span = workspace_span.enter();
@@ -200,6 +199,13 @@ impl Workspace {
let db = db.snapshot();
let workspace_span = workspace_span.clone();
// TODO: Checking per-file and then filtering out the diagnostics from other files
// isn't the ideal solution but doing it "properly" requires support for
// ["parallel Salsa"](https://github.com/salsa-rs/salsa/pull/568).
//
// The solution with parallel Salsa is:
// * Create a new `check_workspace_impl` query similar to `check_file_impl`
// * Collect the workspace diagnostics using `check_workspace_impl::accumulated::<CompileDiagnostic>(&db, file)`
rayon::scope(move |scope| {
for file in &files {
let result = inner_result.clone();
@@ -210,8 +216,17 @@ impl Workspace {
let check_file_span = tracing::debug_span!(parent: &workspace_span, "check_file", file=%file.path(&db));
let _entered = check_file_span.entered();
let file_diagnostics = check_file(&db, file);
result.lock().unwrap().extend(file_diagnostics);
// Filter out the diagnostics from other files to avoid duplicates.
// This should no longer be necessary with parallel-salsa where
// it's possible to call the query for the entire workspace.
let mut file_diagnostics =
check_file_impl::accumulated::<CompileDiagnostic>(&db, file);
file_diagnostics.sort_unstable_by_key(|a| {
a.range().unwrap_or_default().start()
});
result.lock().unwrap().extend(file_diagnostics.into_iter().filter(|diagnostic| diagnostic.file() == file));
});
}
});
@@ -219,6 +234,20 @@ impl Workspace {
Arc::into_inner(result).unwrap().into_inner().unwrap()
}
pub fn check_file(self, db: &dyn Db, file: File) -> Vec<CompileDiagnostic> {
let diagnostics = check_file_impl::accumulated::<CompileDiagnostic>(db, file);
let mut file_diagnostics: Vec<_> = diagnostics
.into_iter()
.filter(|diagnostic| diagnostic.file() == file)
.collect();
file_diagnostics
.sort_unstable_by_key(|diagnostic| diagnostic.range().unwrap_or_default().start());
file_diagnostics
}
/// Opens a file in the workspace.
///
/// This changes the behavior of `check` to only check the open files rather than all files in the workspace.
@@ -378,33 +407,28 @@ impl Package {
}
}
pub(super) fn check_file(db: &dyn Db, file: File) -> Vec<Box<dyn Diagnostic>> {
let mut diagnostics: Vec<Box<dyn Diagnostic>> = Vec::new();
// Abort checking if there are IO errors.
let source = source_text(db.upcast(), file);
/// Checks a single file
///
/// This is a Salsa query so that [`Workspace::check_file`] can retrieve the accumulated diagnostics.
#[salsa::tracked]
pub(super) fn check_file_impl(db: &dyn Db, file: File) {
tracing::debug!("Checking file '{path}'", path = file.path(db));
if let Some(read_error) = source.read_error() {
diagnostics.push(Box::new(IOErrorDiagnostic {
file,
error: read_error.clone(),
}));
return diagnostics;
// Abort checking if there are IO errors.
if source_text(db.upcast(), file).has_read_error() {
return;
}
let parsed = parsed_module(db.upcast(), file);
diagnostics.extend(parsed.errors().iter().map(|error| {
let diagnostic: Box<dyn Diagnostic> = Box::new(ParseDiagnostic::new(file, error.clone()));
diagnostic
}));
check_types(db.upcast(), file);
}
diagnostics.extend(check_types(db.upcast(), file).iter().map(|diagnostic| {
let boxed: Box<dyn Diagnostic> = Box::new(diagnostic.clone());
boxed
}));
#[salsa::tracked]
fn check_workspace_sync(db: &dyn Db, workspace: Workspace) {
let files = WorkspaceFiles::new(db, workspace);
diagnostics.sort_unstable_by_key(|diagnostic| diagnostic.range().unwrap_or_default().start());
diagnostics
for file in &files {
check_file_impl(db, file);
}
}
fn discover_package_files(db: &dyn Db, path: &SystemPath) -> FxHashSet<File> {
@@ -517,48 +541,46 @@ impl Iterator for WorkspaceFilesIter<'_> {
}
}
#[derive(Debug)]
pub struct IOErrorDiagnostic {
file: File,
error: SourceTextError,
}
impl Diagnostic for IOErrorDiagnostic {
fn rule(&self) -> &str {
"io"
}
fn message(&self) -> Cow<str> {
self.error.to_string().into()
}
fn file(&self) -> File {
self.file
}
fn range(&self) -> Option<TextRange> {
None
}
fn severity(&self) -> Severity {
Severity::Error
}
}
#[cfg(test)]
mod tests {
use crate::db::tests::TestDb;
use crate::workspace::check_file;
use red_knot_python_semantic::types::check_types;
use red_knot_python_semantic::{
ProgramSettings, PythonVersion, SearchPathSettings, SitePackages,
};
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::system_path_to_file;
use ruff_db::source::source_text;
use ruff_db::system::{DbWithTestSystem, SystemPath};
use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};
use ruff_db::testing::assert_function_query_was_not_run;
use crate::db::tests::TestDb;
use crate::workspace::settings::WorkspaceSettings;
use crate::workspace::{Workspace, WorkspaceMetadata};
#[test]
fn check_file_skips_type_checking_when_file_cant_be_read() -> ruff_db::system::Result<()> {
let mut db = TestDb::new();
let root = SystemPathBuf::from("src");
let workspace = Workspace::from_metadata(
&db,
WorkspaceMetadata {
root: root.clone(),
packages: vec![],
settings: WorkspaceSettings {
program: ProgramSettings {
target_version: PythonVersion::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_root: root,
custom_typeshed: None,
site_packages: SitePackages::Known(vec![]),
},
},
},
},
);
let path = SystemPath::new("test.py");
db.write_file(path, "x = 10")?;
@@ -568,12 +590,16 @@ mod tests {
db.memory_file_system().remove_file(path)?;
file.sync(&mut db);
let diagnostics: Vec<_> = workspace
.check_file(&db, file)
.into_iter()
.map(|diagnostic| diagnostic.message().to_string())
.collect();
assert_eq!(source_text(&db, file).as_str(), "");
assert_eq!(
check_file(&db, file)
.into_iter()
.map(|diagnostic| diagnostic.message().into_owned())
.collect::<Vec<_>>(),
diagnostics,
vec!["Failed to read file: No such file or directory".to_string()]
);
@@ -584,14 +610,14 @@ mod tests {
// content returned by `source_text` remains unchanged, but the diagnostics should get updated.
db.write_file(path, "").unwrap();
let diagnostics: Vec<_> = workspace
.check_file(&db, file)
.into_iter()
.map(|diagnostic| diagnostic.message().to_string())
.collect();
assert_eq!(source_text(&db, file).as_str(), "");
assert_eq!(
check_file(&db, file)
.into_iter()
.map(|diagnostic| diagnostic.message().into_owned())
.collect::<Vec<_>>(),
vec![] as Vec<String>
);
assert_eq!(diagnostics, vec![] as Vec<String>);
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.7.3"
version = "0.7.2"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -8,7 +8,6 @@ use red_knot_workspace::workspace::settings::Configuration;
use red_knot_workspace::workspace::WorkspaceMetadata;
use ruff_benchmark::criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use ruff_benchmark::TestFile;
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{system_path_to_file, File};
use ruff_db::source::source_text;
use ruff_db::system::{MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem};
@@ -124,7 +123,6 @@ fn setup_rayon() {
fn benchmark_incremental(criterion: &mut Criterion) {
fn setup() -> Case {
let case = setup_case();
let result: Vec<_> = case
.db
.check()

View File

@@ -28,7 +28,6 @@ matchit = { workspace = true }
salsa = { workspace = true }
serde = { workspace = true, optional = true }
path-slash = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
tracing-tree = { workspace = true, optional = true }

View File

@@ -1,11 +1,11 @@
use ruff_text_size::TextRange;
use salsa::Accumulator as _;
use crate::{
files::File,
source::{line_index, source_text},
Db,
};
use ruff_python_parser::ParseError;
use ruff_text_size::TextRange;
use std::borrow::Cow;
pub trait Diagnostic: Send + Sync + std::fmt::Debug {
fn rule(&self) -> &str;
@@ -17,16 +17,6 @@ pub trait Diagnostic: Send + Sync + std::fmt::Debug {
fn range(&self) -> Option<TextRange>;
fn severity(&self) -> Severity;
fn display<'a>(&'a self, db: &'a dyn Db) -> DisplayDiagnostic<'a>
where
Self: Sized,
{
DisplayDiagnostic {
db,
diagnostic: self,
}
}
}
#[derive(Debug, Clone, Copy)]
@@ -35,6 +25,47 @@ pub enum Severity {
Error,
}
#[salsa::accumulator]
pub struct CompileDiagnostic(std::sync::Arc<dyn Diagnostic>);
impl CompileDiagnostic {
pub fn report<T>(db: &dyn Db, diagnostic: T)
where
T: Diagnostic + 'static,
{
Self(std::sync::Arc::new(diagnostic)).accumulate(db);
}
pub fn display<'a>(&'a self, db: &'a dyn Db) -> DisplayDiagnostic<'a> {
DisplayDiagnostic {
db,
diagnostic: &*self.0,
}
}
}
impl Diagnostic for CompileDiagnostic {
fn rule(&self) -> &str {
self.0.rule()
}
fn message(&self) -> std::borrow::Cow<str> {
self.0.message()
}
fn file(&self) -> File {
self.0.file()
}
fn range(&self) -> Option<TextRange> {
self.0.range()
}
fn severity(&self) -> Severity {
self.0.severity()
}
}
pub struct DisplayDiagnostic<'db> {
db: &'db dyn Db,
diagnostic: &'db dyn Diagnostic,
@@ -81,7 +112,7 @@ where
(**self).rule()
}
fn message(&self) -> Cow<str> {
fn message(&self) -> std::borrow::Cow<str> {
(**self).message()
}
@@ -122,59 +153,3 @@ where
(**self).severity()
}
}
impl Diagnostic for Box<dyn Diagnostic> {
fn rule(&self) -> &str {
(**self).rule()
}
fn message(&self) -> Cow<str> {
(**self).message()
}
fn file(&self) -> File {
(**self).file()
}
fn range(&self) -> Option<TextRange> {
(**self).range()
}
fn severity(&self) -> Severity {
(**self).severity()
}
}
#[derive(Debug)]
pub struct ParseDiagnostic {
file: File,
error: ParseError,
}
impl ParseDiagnostic {
pub fn new(file: File, error: ParseError) -> Self {
Self { file, error }
}
}
impl Diagnostic for ParseDiagnostic {
fn rule(&self) -> &str {
"invalid-syntax"
}
fn message(&self) -> Cow<str> {
self.error.error.to_string().into()
}
fn file(&self) -> File {
self.file
}
fn range(&self) -> Option<TextRange> {
Some(self.error.location)
}
fn severity(&self) -> Severity {
Severity::Error
}
}

View File

@@ -1,10 +1,13 @@
use std::borrow::Cow;
use std::fmt::Formatter;
use std::ops::Deref;
use std::sync::Arc;
use ruff_python_ast::{ModModule, PySourceType};
use ruff_python_parser::{parse_unchecked_source, Parsed};
use ruff_text_size::TextRange;
use crate::diagnostic::{CompileDiagnostic, Diagnostic};
use crate::files::{File, FilePath};
use crate::source::source_text;
use crate::Db;
@@ -37,7 +40,20 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
.map_or(PySourceType::Python, PySourceType::from_extension),
};
ParsedModule::new(parse_unchecked_source(&source, ty))
let parsed = parse_unchecked_source(&source, ty);
for error in parsed.errors() {
CompileDiagnostic::report(
db,
ParseDiagnostic {
error: error.error.to_string(),
range: error.location,
file,
},
);
}
ParsedModule::new(parsed)
}
/// Cheap cloneable wrapper around the parsed module.
@@ -73,6 +89,35 @@ impl std::fmt::Debug for ParsedModule {
}
}
#[derive(Debug)]
pub struct ParseDiagnostic {
error: String,
range: TextRange,
file: File,
}
impl Diagnostic for ParseDiagnostic {
fn rule(&self) -> &str {
"invalid-syntax"
}
fn message(&self) -> std::borrow::Cow<str> {
Cow::Borrowed(&self.error)
}
fn file(&self) -> File {
self.file
}
fn range(&self) -> Option<ruff_text_size::TextRange> {
Some(self.range)
}
fn severity(&self) -> crate::diagnostic::Severity {
crate::diagnostic::Severity::Error
}
}
#[cfg(test)]
mod tests {
use crate::files::{system_path_to_file, vendored_path_to_file};

View File

@@ -7,6 +7,7 @@ use ruff_notebook::Notebook;
use ruff_python_ast::PySourceType;
use ruff_source_file::LineIndex;
use crate::diagnostic::{CompileDiagnostic, Diagnostic};
use crate::files::{File, FilePath};
use crate::Db;
@@ -15,14 +16,22 @@ use crate::Db;
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
let path = file.path(db);
let _span = tracing::trace_span!("source_text", file = %path).entered();
let mut read_error = None;
let mut has_read_error = false;
let kind = if is_notebook(file.path(db)) {
file.read_to_notebook(db)
.unwrap_or_else(|error| {
tracing::debug!("Failed to read notebook '{path}': {error}");
read_error = Some(SourceTextError::FailedToReadNotebook(error.to_string()));
has_read_error = true;
CompileDiagnostic::report(
db,
SourceTextDiagnostic {
error: SourceTextError::FailedToReadNotebook(error),
file,
},
);
Notebook::empty()
})
.into()
@@ -31,7 +40,15 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
.unwrap_or_else(|error| {
tracing::debug!("Failed to read file '{path}': {error}");
read_error = Some(SourceTextError::FailedToReadFile(error.to_string()));
has_read_error = true;
CompileDiagnostic::report(
db,
SourceTextDiagnostic {
error: SourceTextError::FailedToReadFile(error),
file,
},
);
String::new()
})
.into()
@@ -40,7 +57,7 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
SourceText {
inner: Arc::new(SourceTextInner {
kind,
read_error,
has_read_error,
count: Count::new(),
}),
}
@@ -93,8 +110,8 @@ impl SourceText {
}
/// Returns `true` if there was an error when reading the content of the file.
pub fn read_error(&self) -> Option<&SourceTextError> {
self.inner.read_error.as_ref()
pub fn has_read_error(&self) -> bool {
self.inner.has_read_error
}
}
@@ -127,7 +144,7 @@ impl std::fmt::Debug for SourceText {
struct SourceTextInner {
count: Count<SourceText>,
kind: SourceTextKind,
read_error: Option<SourceTextError>,
has_read_error: bool,
}
#[derive(Eq, PartialEq)]
@@ -148,12 +165,45 @@ impl From<Notebook> for SourceTextKind {
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
#[derive(Debug)]
pub struct SourceTextDiagnostic {
error: SourceTextError,
file: File,
}
impl Diagnostic for SourceTextDiagnostic {
fn message(&self) -> std::borrow::Cow<str> {
match &self.error {
SourceTextError::FailedToReadNotebook(notebook_error) => {
format!("Failed to read notebook: {notebook_error}").into()
}
SourceTextError::FailedToReadFile(error) => {
format!("Failed to read file: {error}").into()
}
}
}
fn rule(&self) -> &str {
"io-error"
}
fn file(&self) -> File {
self.file
}
fn severity(&self) -> crate::diagnostic::Severity {
crate::diagnostic::Severity::Error
}
fn range(&self) -> Option<ruff_text_size::TextRange> {
None
}
}
#[derive(Debug)]
pub enum SourceTextError {
#[error("Failed to read notebook: {0}`")]
FailedToReadNotebook(String),
#[error("Failed to read file: {0}")]
FailedToReadFile(String),
FailedToReadNotebook(ruff_notebook::NotebookError),
FailedToReadFile(std::io::Error),
}
/// Computes the [`LineIndex`] for `file`.

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.7.3"
version = "0.7.2"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -10,8 +10,6 @@ async def func3(id, dir):
pass
# this is Ok for A002 (trigger A005 instead)
# https://github.com/astral-sh/ruff/issues/14135
map([], lambda float: ...)
from typing import override, overload

View File

@@ -3,8 +3,3 @@ lambda x, float, y: x + y
lambda min, max: min
lambda id: id
lambda dir: dir
# Ok for A006 - should trigger A002 instead
# https://github.com/astral-sh/ruff/issues/14135
def func1(str, /, type, *complex, Exception, **getattr):
pass

View File

@@ -6,19 +6,6 @@ def foo():
class Bar:
"""bar""" # ERROR PYI021
class Qux:
"""qux""" # ERROR PYI021
def __init__(self) -> None: ...
class Baz:
"""Multiline docstring
Lorem ipsum dolor sit amet
"""
def __init__(self) -> None: ...
def bar():
x = 1
"""foo""" # OK, not a doc string

View File

@@ -1,42 +0,0 @@
# setup
from enum import Enum, EnumMeta
from collections import UserList as UL
class SetOnceMappingMixin:
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key) + ' already set')
return super().__setitem__(key, value)
class CaseInsensitiveEnumMeta(EnumMeta):
pass
# positives
class D(dict):
pass
class L(list):
pass
class S(str):
pass
# currently not detected
class SetOnceDict(SetOnceMappingMixin, dict):
pass
# negatives
class C:
pass
class I(int):
pass
class ActivityState(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""Activity state. This is an optional property and if not provided, the state will be Active by
default.
"""
ACTIVE = "Active"
INACTIVE = "Inactive"

View File

@@ -155,7 +155,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
// flake8-pyi
if enforce_stubs {
flake8_pyi::rules::docstring_in_stubs(checker, definition, docstring);
flake8_pyi::rules::docstring_in_stubs(checker, docstring);
}
if enforce_stubs_and_runtime {
flake8_pyi::rules::iter_method_return_iterable(checker, definition);

View File

@@ -549,9 +549,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::WhitespaceAfterDecorator) {
pycodestyle::rules::whitespace_after_decorator(checker, decorator_list);
}
if checker.enabled(Rule::SubclassBuiltin) {
refurb::rules::subclass_builtin(checker, class_def);
}
}
Stmt::Import(ast::StmtImport { names, range: _ }) => {
if checker.enabled(Rule::MultipleImportsOnOneLine) {

View File

@@ -1072,7 +1072,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "181") => (RuleGroup::Stable, rules::refurb::rules::HashlibDigestHex),
(Refurb, "187") => (RuleGroup::Stable, rules::refurb::rules::ListReverseCopy),
(Refurb, "188") => (RuleGroup::Preview, rules::refurb::rules::SliceToRemovePrefixOrSuffix),
(Refurb, "189") => (RuleGroup::Preview, rules::refurb::rules::SubclassBuiltin),
(Refurb, "192") => (RuleGroup::Preview, rules::refurb::rules::SortedMinMax),
// flake8-logging

View File

@@ -16,43 +16,8 @@ static CODE_INDICATORS: LazyLock<AhoCorasick> = LazyLock::new(|| {
static ALLOWLIST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"(?x)
^
(?:
# Case-sensitive
pyright
| mypy:
| type:\s*ignore
| SPDX-License-Identifier:
| fmt:\s*(on|off|skip)
| region|endregion
# Case-insensitive
| (?i:
noqa
)
# Unknown case sensitivity
| (?i:
pylint
| nosec
| isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)
| (?:en)?coding[:=][\x20\t]*([-_.A-Z0-9]+)
)
# IntelliJ language injection comments:
# * `language` must be lowercase.
# * No spaces around `=`.
# * Language IDs as used in comments must have no spaces,
# though to IntelliJ they can be anything.
# * May optionally contain `prefix=` and/or `suffix=`,
# not declared here since we use `.is_match()`.
| language=[-_.a-zA-Z0-9]+
)
",
)
.unwrap()
r"^(?i)(?:pylint|pyright|noqa|nosec|region|endregion|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|mypy:|SPDX-License-Identifier:|language=[a-zA-Z](?: ?[-_.a-zA-Z0-9]+)+(?:\s+prefix=\S+)?(?:\s+suffix=\S+)?|(?:en)?coding[:=][ \t]*([-_.a-zA-Z0-9]+))",
).unwrap()
});
static HASH_NUMBER: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"#\d").unwrap());
@@ -334,42 +299,17 @@ mod tests {
#[test]
fn comment_contains_language_injection() {
// `language` with bad casing
assert!(comment_contains_code("# Language=C#", &[]));
assert!(comment_contains_code("# lAngUAgE=inI", &[]));
// Unreasonable language IDs, possibly literals
assert!(comment_contains_code("# language=123", &[]));
assert!(comment_contains_code("# language=\"pt\"", &[]));
assert!(comment_contains_code("# language='en'", &[]));
// Spaces around equal sign
assert!(comment_contains_code("# language =xml", &[]));
assert!(comment_contains_code("# language= html", &[]));
assert!(comment_contains_code("# language = RegExp", &[]));
// Leading whitespace
assert!(!comment_contains_code("#language=CSS", &[]));
assert!(!comment_contains_code("# \t language=C++", &[]));
// Human language false negatives
assert!(!comment_contains_code("# language=en", &[]));
assert!(!comment_contains_code("# language=en-US", &[]));
// Casing (fine because such IDs cannot be validated)
assert!(!comment_contains_code("# language=PytHoN", &[]));
assert!(!comment_contains_code("# language=jaVaScrIpt", &[]));
// Space within ID (fine because `Shell` is considered the ID)
assert!(!comment_contains_code("# language=Shell Script", &[]));
// With prefix and/or suffix
assert!(!comment_contains_code("# language=HTML prefix=<body>", &[]));
assert!(!comment_contains_code("# language=xml", &[]));
assert!(!comment_contains_code(
r"# language=Requirements suffix=\n",
"# language=HTML prefix=<body> suffix=</body>",
&[]
));
assert!(!comment_contains_code(
"language=javascript prefix=(function(){ suffix=})()",
"# language=ecma script level 4",
&[]
));
}

View File

@@ -469,7 +469,7 @@ impl Violation for MissingReturnTypeClassMethod {
/// ```
///
/// ## References
/// - [Typing spec: `Any`](https://typing.readthedocs.io/en/latest/spec/special-types.html#any)
/// - [PEP 484](https://www.python.org/dev/peps/pep-0484/#the-any-type)
/// - [Python documentation: `typing.Any`](https://docs.python.org/3/library/typing.html#typing.Any)
/// - [Mypy documentation: The Any type](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-any-type)
#[violation]

View File

@@ -10,21 +10,16 @@ use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for imports of the `telnetlib` module.
/// Checks for imports of the`telnetlib` module.
///
/// ## Why is this bad?
/// Telnet is considered insecure. It is deprecated since version 3.11, and
/// was removed in version 3.13. Instead, use SSH or another encrypted
/// Telnet is considered insecure. Instead, use SSH or another encrypted
/// protocol.
///
/// ## Example
/// ```python
/// import telnetlib
/// ```
///
/// ## References
/// - [Python documentation: `telnetlib` - Telnet client](https://docs.python.org/3.12/library/telnetlib.html#module-telnetlib)
/// - [PEP 594: `telnetlib`](https://peps.python.org/pep-0594/#telnetlib)
#[violation]
pub struct SuspiciousTelnetlibImport;
@@ -46,9 +41,6 @@ impl Violation for SuspiciousTelnetlibImport {
/// ```python
/// import ftplib
/// ```
///
/// ## References
/// - [Python documentation: `ftplib` - FTP protocol client](https://docs.python.org/3/library/ftplib.html)
#[violation]
pub struct SuspiciousFtplibImport;
@@ -71,9 +63,8 @@ impl Violation for SuspiciousFtplibImport {
/// ```python
/// import pickle
/// ```
///
/// ## References
/// - [Python documentation: `pickle` — Python object serialization](https://docs.python.org/3/library/pickle.html)
/// /// ## References
/// - [Python Docs](https://docs.python.org/3/library/pickle.html)
#[violation]
pub struct SuspiciousPickleImport;

View File

@@ -33,8 +33,8 @@ use ruff_text_size::Ranged;
///
/// ## References
/// - [Common Weakness Enumeration: CWE-22](https://cwe.mitre.org/data/definitions/22.html)
/// - [Python documentation: `TarFile.extractall`](https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall)
/// - [Python documentation: Extraction filters](https://docs.python.org/3/library/tarfile.html#tarfile-extraction-filter)
/// - [Python Documentation: `TarFile.extractall`](https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall)
/// - [Python Documentation: Extraction filters](https://docs.python.org/3/library/tarfile.html#tarfile-extraction-filter)
///
/// [PEP 706]: https://peps.python.org/pep-0706/#backporting-forward-compatibility
#[violation]

View File

@@ -60,7 +60,7 @@ use crate::checkers::ast::Checker;
/// ## References
/// - [Python documentation: The `try` statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement)
/// - [Python documentation: Exception hierarchy](https://docs.python.org/3/library/exceptions.html#exception-hierarchy)
/// - [PEP 8: Programming Recommendations on bare `except`](https://peps.python.org/pep-0008/#programming-recommendations)
/// - [PEP8 Programming Recommendations on bare `except`](https://peps.python.org/pep-0008/#programming-recommendations)
#[violation]
pub struct BlindExcept {
name: String,

View File

@@ -88,7 +88,7 @@ impl Violation for AbstractBaseClassWithoutAbstractMethod {
/// ```
///
/// ## References
/// - [Python documentation: `abc`](https://docs.python.org/3/library/abc.html)
/// - [Python documentation: abc](https://docs.python.org/3/library/abc.html)
#[violation]
pub struct EmptyMethodWithoutAbstractDecorator {
name: String,

View File

@@ -28,7 +28,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 257](https://peps.python.org/pep-0257/)
/// - [Python documentation: Formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)
#[violation]
pub struct FStringDocstring;

View File

@@ -40,7 +40,7 @@ use crate::checkers::ast::Checker;
///
/// ## References
/// - [The Hitchhiker's Guide to Python: Late Binding Closures](https://docs.python-guide.org/writing/gotchas/#late-binding-closures)
/// - [Python documentation: `functools.partial`](https://docs.python.org/3/library/functools.html#functools.partial)
/// - [Python documentation: functools.partial](https://docs.python.org/3/library/functools.html#functools.partial)
#[violation]
pub struct FunctionUsesLoopVariable {
name: String,

View File

@@ -26,9 +26,6 @@ use crate::checkers::ast::Checker;
/// ```python
/// warnings.warn("This is a warning", stacklevel=2)
/// ```
///
/// ## References
/// - [Python documentation: `warnings.warn`](https://docs.python.org/3/library/warnings.html#warnings.warn)
#[violation]
pub struct NoExplicitStacklevel;

View File

@@ -35,7 +35,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Python documentation: `contextlib.suppress`](https://docs.python.org/3/library/contextlib.html#contextlib.suppress)
/// - [Python documentation: contextlib.suppress](https://docs.python.org/3/library/contextlib.html#contextlib.suppress)
#[violation]
pub struct UselessContextlibSuppress;

View File

@@ -1,7 +1,7 @@
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Expr, Parameter};
use ruff_python_ast::Parameter;
use ruff_python_semantic::analyze::visibility::{is_overload, is_override};
use ruff_text_size::Ranged;
@@ -58,7 +58,7 @@ impl Violation for BuiltinArgumentShadowing {
#[derive_message_formats]
fn message(&self) -> String {
let BuiltinArgumentShadowing { name } = self;
format!("Function argument `{name}` is shadowing a Python builtin")
format!("Argument `{name}` is shadowing a Python builtin")
}
}
@@ -70,15 +70,6 @@ pub(crate) fn builtin_argument_shadowing(checker: &mut Checker, parameter: &Para
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,
) {
// Ignore parameters in lambda expressions.
// (That is the domain of A006.)
if checker
.semantic()
.current_expression()
.is_some_and(Expr::is_lambda_expr)
{
return;
}
// Ignore `@override` and `@overload` decorated functions.
if checker
.semantic()

View File

@@ -1,58 +1,66 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
---
A002.py:1:11: A002 Function argument `str` is shadowing a Python builtin
A002.py:1:11: A002 Argument `str` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^ A002
2 | pass
|
A002.py:1:19: A002 Function argument `type` is shadowing a Python builtin
A002.py:1:19: A002 Argument `type` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^ A002
2 | pass
|
A002.py:1:26: A002 Function argument `complex` is shadowing a Python builtin
A002.py:1:26: A002 Argument `complex` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^^^^ A002
2 | pass
|
A002.py:1:35: A002 Function argument `Exception` is shadowing a Python builtin
A002.py:1:35: A002 Argument `Exception` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^^^^^^ A002
2 | pass
|
A002.py:1:48: A002 Function argument `getattr` is shadowing a Python builtin
A002.py:1:48: A002 Argument `getattr` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^^^^ A002
2 | pass
|
A002.py:5:17: A002 Function argument `bytes` is shadowing a Python builtin
A002.py:5:17: A002 Argument `bytes` is shadowing a Python builtin
|
5 | async def func2(bytes):
| ^^^^^ A002
6 | pass
|
A002.py:9:17: A002 Function argument `id` is shadowing a Python builtin
A002.py:9:17: A002 Argument `id` is shadowing a Python builtin
|
9 | async def func3(id, dir):
| ^^ A002
10 | pass
|
A002.py:9:21: A002 Function argument `dir` is shadowing a Python builtin
A002.py:9:21: A002 Argument `dir` is shadowing a Python builtin
|
9 | async def func3(id, dir):
| ^^^ A002
10 | pass
|
A002.py:13:16: A002 Argument `float` is shadowing a Python builtin
|
13 | map([], lambda float: ...)
| ^^^^^ A002
14 |
15 | from typing import override, overload
|

View File

@@ -1,44 +1,52 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
---
A002.py:1:11: A002 Function argument `str` is shadowing a Python builtin
A002.py:1:11: A002 Argument `str` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^ A002
2 | pass
|
A002.py:1:19: A002 Function argument `type` is shadowing a Python builtin
A002.py:1:19: A002 Argument `type` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^ A002
2 | pass
|
A002.py:1:26: A002 Function argument `complex` is shadowing a Python builtin
A002.py:1:26: A002 Argument `complex` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^^^^ A002
2 | pass
|
A002.py:1:35: A002 Function argument `Exception` is shadowing a Python builtin
A002.py:1:35: A002 Argument `Exception` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^^^^^^ A002
2 | pass
|
A002.py:1:48: A002 Function argument `getattr` is shadowing a Python builtin
A002.py:1:48: A002 Argument `getattr` is shadowing a Python builtin
|
1 | def func1(str, /, type, *complex, Exception, **getattr):
| ^^^^^^^ A002
2 | pass
|
A002.py:5:17: A002 Function argument `bytes` is shadowing a Python builtin
A002.py:5:17: A002 Argument `bytes` is shadowing a Python builtin
|
5 | async def func2(bytes):
| ^^^^^ A002
6 | pass
|
A002.py:13:16: A002 Argument `float` is shadowing a Python builtin
|
13 | map([], lambda float: ...)
| ^^^^^ A002
14 |
15 | from typing import override, overload
|

View File

@@ -61,6 +61,4 @@ A006.py:5:8: A006 Lambda argument `dir` is shadowing a Python builtin
4 | lambda id: id
5 | lambda dir: dir
| ^^^ A006
6 |
7 | # Ok for A006 - should trigger A002 instead
|

View File

@@ -30,9 +30,6 @@ use crate::checkers::ast::Checker;
/// dict.fromkeys(iterable)
/// dict.fromkeys(iterable, 1)
/// ```
///
/// ## References
/// - [Python documentation: `dict.fromkeys`](https://docs.python.org/3/library/stdtypes.html#dict.fromkeys)
#[violation]
pub struct UnnecessaryDictComprehensionForIterable {
is_value_none_literal: bool,
@@ -56,7 +53,7 @@ impl Violation for UnnecessaryDictComprehensionForIterable {
}
}
/// C420
/// RUF025
pub(crate) fn unnecessary_dict_comprehension_for_iterable(
checker: &mut Checker,
dict_comp: &ast::ExprDictComp,

View File

@@ -39,7 +39,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
#[violation]
pub struct FStringInGetTextFuncCall;

View File

@@ -39,7 +39,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
#[violation]
pub struct FormatInGetTextFuncCall;

View File

@@ -38,7 +38,7 @@ use ruff_text_size::Ranged;
/// ```
///
/// ## References
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
#[violation]
pub struct PrintfInGetTextFuncCall;

View File

@@ -432,7 +432,7 @@ impl AlwaysFixableViolation for LoggingWarn {
///
/// username = "Maria"
///
/// logging.info("Something happened", extra=dict(user_id=username))
/// logging.info("Something happened", extra=dict(user=username))
/// ```
///
/// ## Options

View File

@@ -22,7 +22,7 @@ use crate::Locator;
///
/// Directories that lack an `__init__.py` file can still be imported, but
/// they're indicative of a special kind of package, known as a "namespace
/// package" (see: [PEP 420](https://peps.python.org/pep-0420/)).
/// package" (see: [PEP 420](https://www.python.org/dev/peps/pep-0420/)).
/// Namespace packages are less widely used, so a package that lacks an
/// `__init__.py` file is typically meant to be a regular package, and
/// the absence of the `__init__.py` file is probably an oversight.

View File

@@ -30,7 +30,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Typing documentation: Version and platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
/// The [typing documentation on stub files](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
#[violation]
pub struct ComplexIfStatementInStub;

View File

@@ -1,11 +1,9 @@
use ruff_python_ast::ExprStringLiteral;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use ruff_python_semantic::Definition;
use crate::checkers::ast::Checker;
/// ## What it does
@@ -31,40 +29,18 @@ use crate::checkers::ast::Checker;
#[violation]
pub struct DocstringInStub;
impl AlwaysFixableViolation for DocstringInStub {
impl Violation for DocstringInStub {
#[derive_message_formats]
fn message(&self) -> String {
"Docstrings should not be included in stubs".to_string()
}
fn fix_title(&self) -> String {
"Remove docstring".to_string()
}
}
/// PYI021
pub(crate) fn docstring_in_stubs(
checker: &mut Checker,
definition: &Definition,
docstring: Option<&ExprStringLiteral>,
) {
let Some(docstring_range) = docstring.map(ExprStringLiteral::range) else {
return;
};
let statements = match definition {
Definition::Module(module) => module.python_ast,
Definition::Member(member) => member.body(),
};
let edit = if statements.len() == 1 {
Edit::range_replacement("...".to_string(), docstring_range)
} else {
Edit::range_deletion(docstring_range)
};
let fix = Fix::unsafe_edit(edit);
let diagnostic = Diagnostic::new(DocstringInStub, docstring_range).with_fix(fix);
checker.diagnostics.push(diagnostic);
pub(crate) fn docstring_in_stubs(checker: &mut Checker, docstring: Option<&ExprStringLiteral>) {
if let Some(docstr) = docstring {
checker
.diagnostics
.push(Diagnostic::new(DocstringInStub, docstr.range()));
}
}

View File

@@ -2,12 +2,12 @@ use std::collections::HashSet;
use rustc_hash::FxHashSet;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_diagnostics::{Diagnostic, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::{self as ast, Expr, ExprContext};
use ruff_python_ast::Expr;
use ruff_python_semantic::analyze::typing::traverse_literal;
use ruff_text_size::{Ranged, TextRange};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -27,10 +27,6 @@ use crate::checkers::ast::Checker;
/// foo: Literal["a", "b"]
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as safe; however, the fix will flatten nested
/// literals into a single top-level literal.
///
/// ## References
/// - [Python documentation: `typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal)
#[violation]
@@ -38,29 +34,24 @@ pub struct DuplicateLiteralMember {
duplicate_name: String,
}
impl AlwaysFixableViolation for DuplicateLiteralMember {
impl Violation for DuplicateLiteralMember {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("Duplicate literal member `{}`", self.duplicate_name)
}
fn fix_title(&self) -> String {
"Remove duplicates".to_string()
}
}
/// PYI062
pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr) {
let mut seen_nodes: HashSet<ComparableExpr<'_>, _> = FxHashSet::default();
let mut unique_nodes: Vec<&Expr> = Vec::new();
let mut diagnostics: Vec<Diagnostic> = Vec::new();
// Adds a member to `literal_exprs` if it is a `Literal` annotation
let mut check_for_duplicate_members = |expr: &'a Expr, _: &'a Expr| {
// If we've already seen this literal member, raise a violation.
if seen_nodes.insert(expr.into()) {
unique_nodes.push(expr);
} else {
if !seen_nodes.insert(expr.into()) {
diagnostics.push(Diagnostic::new(
DuplicateLiteralMember {
duplicate_name: checker.generator().expr(expr),
@@ -70,36 +61,7 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr
}
};
// Traverse the literal, collect all diagnostic members.
// Traverse the literal, collect all diagnostic members
traverse_literal(&mut check_for_duplicate_members, checker.semantic(), expr);
// If there's at least one diagnostic, create a fix to remove the duplicate members.
if !diagnostics.is_empty() {
if let Expr::Subscript(subscript) = expr {
let subscript = Expr::Subscript(ast::ExprSubscript {
slice: Box::new(if let [elt] = unique_nodes.as_slice() {
(*elt).clone()
} else {
Expr::Tuple(ast::ExprTuple {
elts: unique_nodes.into_iter().cloned().collect(),
range: TextRange::default(),
ctx: ExprContext::Load,
parenthesized: false,
})
}),
value: subscript.value.clone(),
range: TextRange::default(),
ctx: ExprContext::Load,
});
let fix = Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&subscript),
expr.range(),
));
for diagnostic in &mut diagnostics {
diagnostic.set_fix(fix.clone());
}
}
}
checker.diagnostics.append(&mut diagnostics);
}

View File

@@ -26,7 +26,8 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Typing documentation - Writing and Maintaining Stub Files](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html)
/// - [The recommended style for stub functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#id6)
/// in the typing docs.
#[violation]
pub struct NonEmptyStubBody;

View File

@@ -1,15 +1,14 @@
use ruff_python_ast::{self as ast, Decorator, Expr, Parameters, Stmt};
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::analyze;
use ruff_python_semantic::analyze::visibility::{is_abstract, is_final, is_overload};
use ruff_python_semantic::{ScopeKind, SemanticModel};
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for methods that are annotated with a fixed return type which
@@ -72,7 +71,7 @@ use ruff_text_size::{Ranged, TextRange};
/// def __iadd__(self, other: Foo) -> Self: ...
/// ```
/// ## References
/// - [Python documentation: `typing.Self`](https://docs.python.org/3/library/typing.html#typing.Self)
/// - [`typing.Self` documentation](https://docs.python.org/3/library/typing.html#typing.Self)
#[violation]
pub struct NonSelfReturnType {
class_name: String,
@@ -80,15 +79,12 @@ pub struct NonSelfReturnType {
}
impl Violation for NonSelfReturnType {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let NonSelfReturnType {
class_name,
method_name,
} = self;
if matches!(class_name.as_str(), "__new__") {
"`__new__` methods usually return `self` at runtime".to_string()
} else {
@@ -97,7 +93,7 @@ impl Violation for NonSelfReturnType {
}
fn fix_title(&self) -> Option<String> {
Some("Use `Self` as return type".to_string())
Some("Consider using `typing_extensions.Self` as return type".to_string())
}
}
@@ -140,7 +136,13 @@ pub(crate) fn non_self_return_type(
&& is_name(returns, &class_def.name)
&& !is_final(&class_def.decorator_list, semantic)
{
add_diagnostic(checker, stmt, returns, class_def, name);
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
class_name: class_def.name.to_string(),
method_name: name.to_string(),
},
stmt.identifier(),
));
}
return;
}
@@ -148,7 +150,13 @@ pub(crate) fn non_self_return_type(
// In-place methods that are expected to return `Self`.
if is_inplace_bin_op(name) {
if !is_self(returns, checker) {
add_diagnostic(checker, stmt, returns, class_def, name);
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
class_name: class_def.name.to_string(),
method_name: name.to_string(),
},
stmt.identifier(),
));
}
return;
}
@@ -156,7 +164,13 @@ pub(crate) fn non_self_return_type(
if is_name(returns, &class_def.name) {
if matches!(name, "__enter__" | "__new__") && !is_final(&class_def.decorator_list, semantic)
{
add_diagnostic(checker, stmt, returns, class_def, name);
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
class_name: class_def.name.to_string(),
method_name: name.to_string(),
},
stmt.identifier(),
));
}
return;
}
@@ -166,68 +180,32 @@ pub(crate) fn non_self_return_type(
if is_iterable_or_iterator(returns, semantic)
&& subclasses_iterator(class_def, semantic)
{
add_diagnostic(checker, stmt, returns, class_def, name);
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
class_name: class_def.name.to_string(),
method_name: name.to_string(),
},
stmt.identifier(),
));
}
}
"__aiter__" => {
if is_async_iterable_or_iterator(returns, semantic)
&& subclasses_async_iterator(class_def, semantic)
{
add_diagnostic(checker, stmt, returns, class_def, name);
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
class_name: class_def.name.to_string(),
method_name: name.to_string(),
},
stmt.identifier(),
));
}
}
_ => {}
}
}
/// Add a diagnostic for the given method.
fn add_diagnostic(
checker: &mut Checker,
stmt: &Stmt,
returns: &Expr,
class_def: &ast::StmtClassDef,
method_name: &str,
) {
/// Return an [`Edit`] that imports `typing.Self` from `typing` or `typing_extensions`.
fn import_self(checker: &Checker, range: TextRange) -> Option<Edit> {
let target_version = checker.settings.target_version.as_tuple();
let source_module = if checker.source_type.is_stub() || target_version >= (3, 11) {
"typing"
} else {
"typing_extensions"
};
let (importer, semantic) = (checker.importer(), checker.semantic());
let request = ImportRequest::import_from(source_module, "Self");
let Ok((edit, ..)) = importer.get_or_import_symbol(&request, range.start(), semantic)
else {
return None;
};
Some(edit)
}
/// Generate a [`Fix`] that replaces the return type with `Self`.
fn replace_with_self(checker: &mut Checker, range: TextRange) -> Option<Fix> {
let import_self = import_self(checker, range)?;
let replace_with_self = Edit::range_replacement("Self".to_string(), range);
Some(Fix::unsafe_edits(import_self, [replace_with_self]))
}
let mut diagnostic = Diagnostic::new(
NonSelfReturnType {
class_name: class_def.name.to_string(),
method_name: method_name.to_string(),
},
stmt.identifier(),
);
if let Some(fix) = replace_with_self(checker, returns.range()) {
diagnostic.set_fix(fix);
}
checker.diagnostics.push(diagnostic);
}
/// Returns `true` if the method is an in-place binary operator.
fn is_inplace_bin_op(name: &str) -> bool {
matches!(

View File

@@ -23,7 +23,8 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Typing documentation - Writing and Maintaining Stub Files](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html)
/// The [recommended style for functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#functions-and-methods)
/// in the typing docs.
#[violation]
pub struct PassStatementStubBody;

View File

@@ -27,7 +27,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Typing documentation - Writing and Maintaining Stub Files](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html)
/// - [Static Typing with Python: Type Stubs](https://typing.readthedocs.io/en/latest/source/stubs.html)
#[violation]
pub struct QuotedAnnotationInStub;

View File

@@ -38,7 +38,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [Python documentation: The numeric tower](https://docs.python.org/3/library/numbers.html#the-numeric-tower)
/// - [The typing specification](https://docs.python.org/3/library/numbers.html#the-numeric-tower)
/// - [PEP 484: The numeric tower](https://peps.python.org/pep-0484/#the-numeric-tower)
///
/// [typing specification]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex

View File

@@ -40,7 +40,7 @@ use crate::registry::Rule;
/// ```
///
/// ## References
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
#[violation]
pub struct UnrecognizedPlatformCheck;
@@ -74,7 +74,7 @@ impl Violation for UnrecognizedPlatformCheck {
/// ```
///
/// ## References
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
#[violation]
pub struct UnrecognizedPlatformName {
platform: String,

View File

@@ -31,7 +31,7 @@ use crate::registry::Rule;
/// ```
///
/// ## References
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
#[violation]
pub struct UnrecognizedVersionInfoCheck;
@@ -70,7 +70,7 @@ impl Violation for UnrecognizedVersionInfoCheck {
/// ```
///
/// ## References
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
#[violation]
pub struct PatchVersionComparison;
@@ -106,7 +106,7 @@ impl Violation for PatchVersionComparison {
/// ```
///
/// ## References
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
#[violation]
pub struct WrongTupleLengthVersionComparison {
expected_length: usize,

View File

@@ -1,24 +1,15 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
snapshot_kind: text
---
PYI021.pyi:1:1: PYI021 [*] Docstrings should not be included in stubs
PYI021.pyi:1:1: PYI021 Docstrings should not be included in stubs
|
1 | """foo""" # ERROR PYI021
| ^^^^^^^^^ PYI021
2 |
3 | def foo():
|
= help: Remove docstring
Unsafe fix
1 |-"""foo""" # ERROR PYI021
1 |+ # ERROR PYI021
2 2 |
3 3 | def foo():
4 4 | """foo""" # ERROR PYI021
PYI021.pyi:4:5: PYI021 [*] Docstrings should not be included in stubs
PYI021.pyi:4:5: PYI021 Docstrings should not be included in stubs
|
3 | def foo():
4 | """foo""" # ERROR PYI021
@@ -26,81 +17,14 @@ PYI021.pyi:4:5: PYI021 [*] Docstrings should not be included in stubs
5 |
6 | class Bar:
|
= help: Remove docstring
Unsafe fix
1 1 | """foo""" # ERROR PYI021
2 2 |
3 3 | def foo():
4 |- """foo""" # ERROR PYI021
4 |+ ... # ERROR PYI021
5 5 |
6 6 | class Bar:
7 7 | """bar""" # ERROR PYI021
PYI021.pyi:7:5: PYI021 [*] Docstrings should not be included in stubs
PYI021.pyi:7:5: PYI021 Docstrings should not be included in stubs
|
6 | class Bar:
7 | """bar""" # ERROR PYI021
| ^^^^^^^^^ PYI021
8 |
9 | class Qux:
9 | def bar():
|
= help: Remove docstring
Unsafe fix
4 4 | """foo""" # ERROR PYI021
5 5 |
6 6 | class Bar:
7 |- """bar""" # ERROR PYI021
7 |+ ... # ERROR PYI021
8 8 |
9 9 | class Qux:
10 10 | """qux""" # ERROR PYI021
PYI021.pyi:10:5: PYI021 [*] Docstrings should not be included in stubs
|
9 | class Qux:
10 | """qux""" # ERROR PYI021
| ^^^^^^^^^ PYI021
11 |
12 | def __init__(self) -> None: ...
|
= help: Remove docstring
Unsafe fix
7 7 | """bar""" # ERROR PYI021
8 8 |
9 9 | class Qux:
10 |- """qux""" # ERROR PYI021
10 |+ # ERROR PYI021
11 11 |
12 12 | def __init__(self) -> None: ...
13 13 |
PYI021.pyi:15:5: PYI021 [*] Docstrings should not be included in stubs
|
14 | class Baz:
15 | """Multiline docstring
| _____^
16 | |
17 | | Lorem ipsum dolor sit amet
18 | | """
| |_______^ PYI021
19 |
20 | def __init__(self) -> None: ...
|
= help: Remove docstring
Unsafe fix
12 12 | def __init__(self) -> None: ...
13 13 |
14 14 | class Baz:
15 |- """Multiline docstring
16 |-
17 |- Lorem ipsum dolor sit amet
18 |- """
15 |+
19 16 |
20 17 | def __init__(self) -> None: ...
21 18 |

View File

@@ -1,8 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
snapshot_kind: text
---
PYI034.py:21:9: PYI034 [*] `__new__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:21:9: PYI034 `__new__` methods in classes like `Bad` usually return `self` at runtime
|
19 | object
20 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
@@ -10,19 +9,9 @@ PYI034.py:21:9: PYI034 [*] `__new__` methods in classes like `Bad` usually retur
| ^^^^^^^ PYI034
22 | ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
18 18 | class Bad(
19 19 | object
20 20 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
21 |- def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
21 |+ def __new__(cls, *args: Any, **kwargs: Any) -> Self:
22 22 | ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
23 23 |
24 24 | def __repr__(self) -> str:
PYI034.py:36:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:36:9: PYI034 `__enter__` methods in classes like `Bad` usually return `self` at runtime
|
34 | ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
35 |
@@ -30,19 +19,9 @@ PYI034.py:36:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually ret
| ^^^^^^^^^ PYI034
37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
33 33 | def __ne__(self, other: typing.Any) -> typing.Any:
34 34 | ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
35 35 |
36 |- def __enter__(self) -> Bad:
36 |+ def __enter__(self) -> Self:
37 37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
38 38 |
39 39 | async def __aenter__(self) -> Bad:
PYI034.py:39:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:39:15: PYI034 `__aenter__` methods in classes like `Bad` usually return `self` at runtime
|
37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
38 |
@@ -50,19 +29,9 @@ PYI034.py:39:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually r
| ^^^^^^^^^^ PYI034
40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
36 36 | def __enter__(self) -> Bad:
37 37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
38 38 |
39 |- async def __aenter__(self) -> Bad:
39 |+ async def __aenter__(self) -> Self:
40 40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
41 41 |
42 42 | def __iadd__(self, other: Bad) -> Bad:
PYI034.py:42:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:42:9: PYI034 `__iadd__` methods in classes like `Bad` usually return `self` at runtime
|
40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
41 |
@@ -70,38 +39,18 @@ PYI034.py:42:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually retu
| ^^^^^^^^ PYI034
43 | ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
39 39 | async def __aenter__(self) -> Bad:
40 40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
41 41 |
42 |- def __iadd__(self, other: Bad) -> Bad:
42 |+ def __iadd__(self, other: Bad) -> Self:
43 43 | ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
44 44 |
45 45 |
PYI034.py:165:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
PYI034.py:165:9: PYI034 `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
|
164 | class BadIterator1(Iterator[int]):
165 | def __iter__(self) -> Iterator[int]:
| ^^^^^^^^ PYI034
166 | ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
162 162 |
163 163 |
164 164 | class BadIterator1(Iterator[int]):
165 |- def __iter__(self) -> Iterator[int]:
165 |+ def __iter__(self) -> Self:
166 166 | ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
167 167 |
168 168 |
PYI034.py:172:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
PYI034.py:172:9: PYI034 `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
|
170 | typing.Iterator[int]
171 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
@@ -109,19 +58,9 @@ PYI034.py:172:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` us
| ^^^^^^^^ PYI034
173 | ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
169 169 | class BadIterator2(
170 170 | typing.Iterator[int]
171 171 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
172 |- def __iter__(self) -> Iterator[int]:
172 |+ def __iter__(self) -> Self:
173 173 | ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
174 174 |
175 175 |
PYI034.py:179:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
PYI034.py:179:9: PYI034 `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
|
177 | typing.Iterator[int]
178 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
@@ -129,19 +68,9 @@ PYI034.py:179:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` us
| ^^^^^^^^ PYI034
180 | ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
176 176 | class BadIterator3(
177 177 | typing.Iterator[int]
178 178 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
179 |- def __iter__(self) -> collections.abc.Iterator[int]:
179 |+ def __iter__(self) -> Self:
180 180 | ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
181 181 |
182 182 |
PYI034.py:185:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
PYI034.py:185:9: PYI034 `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
|
183 | class BadIterator4(Iterator[int]):
184 | # Note: *Iterable*, not *Iterator*, returned!
@@ -149,71 +78,31 @@ PYI034.py:185:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` us
| ^^^^^^^^ PYI034
186 | ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
182 182 |
183 183 | class BadIterator4(Iterator[int]):
184 184 | # Note: *Iterable*, not *Iterator*, returned!
185 |- def __iter__(self) -> Iterable[int]:
185 |+ def __iter__(self) -> Self:
186 186 | ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
187 187 |
188 188 |
PYI034.py:195:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
PYI034.py:195:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
|
194 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
195 | def __aiter__(self) -> typing.AsyncIterator[str]:
| ^^^^^^^^^ PYI034
196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
192 192 |
193 193 |
194 194 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
195 |- def __aiter__(self) -> typing.AsyncIterator[str]:
195 |+ def __aiter__(self) -> Self:
196 196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
197 197 |
198 198 | class SubclassOfBadIterator3(BadIterator3):
PYI034.py:199:9: PYI034 [*] `__iter__` methods in classes like `SubclassOfBadIterator3` usually return `self` at runtime
PYI034.py:199:9: PYI034 `__iter__` methods in classes like `SubclassOfBadIterator3` usually return `self` at runtime
|
198 | class SubclassOfBadIterator3(BadIterator3):
199 | def __iter__(self) -> Iterator[int]: # Y034
| ^^^^^^^^ PYI034
200 | ...
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
196 196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
197 197 |
198 198 | class SubclassOfBadIterator3(BadIterator3):
199 |- def __iter__(self) -> Iterator[int]: # Y034
199 |+ def __iter__(self) -> Self: # Y034
200 200 | ...
201 201 |
202 202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
PYI034.py:203:9: PYI034 [*] `__aiter__` methods in classes like `SubclassOfBadAsyncIterator` usually return `self` at runtime
PYI034.py:203:9: PYI034 `__aiter__` methods in classes like `SubclassOfBadAsyncIterator` usually return `self` at runtime
|
202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
203 | def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
| ^^^^^^^^^ PYI034
204 | ...
|
= help: Use `Self` as return type
Unsafe fix
200 200 | ...
201 201 |
202 202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
203 |- def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
203 |+ def __aiter__(self) -> Self: # Y034
204 204 | ...
205 205 |
206 206 | class AsyncIteratorReturningAsyncIterable:
= help: Consider using `typing_extensions.Self` as return type

View File

@@ -1,8 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
snapshot_kind: text
---
PYI034.pyi:20:9: PYI034 [*] `__new__` methods in classes like `Bad` usually return `self` at runtime
PYI034.pyi:20:9: PYI034 `__new__` methods in classes like `Bad` usually return `self` at runtime
|
18 | object
19 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
@@ -11,19 +10,9 @@ PYI034.pyi:20:9: PYI034 [*] `__new__` methods in classes like `Bad` usually retu
21 | cls, *args: Any, **kwargs: Any
22 | ) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
19 19 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
20 20 | def __new__(
21 21 | cls, *args: Any, **kwargs: Any
22 |- ) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
22 |+ ) -> Self: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
23 23 | def __repr__(
24 24 | self,
25 25 | ) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
PYI034.pyi:35:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually return `self` at runtime
PYI034.pyi:35:9: PYI034 `__enter__` methods in classes like `Bad` usually return `self` at runtime
|
33 | self, other: typing.Any
34 | ) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
@@ -32,19 +21,9 @@ PYI034.pyi:35:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually re
36 | self,
37 | ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
34 34 | ) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
35 35 | def __enter__(
36 36 | self,
37 |- ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
37 |+ ) -> Self: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
38 38 | async def __aenter__(
39 39 | self,
40 40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
PYI034.pyi:38:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually return `self` at runtime
PYI034.pyi:38:15: PYI034 `__aenter__` methods in classes like `Bad` usually return `self` at runtime
|
36 | self,
37 | ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
@@ -53,19 +32,9 @@ PYI034.pyi:38:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually
39 | self,
40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
37 37 | ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
38 38 | async def __aenter__(
39 39 | self,
40 |- ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
40 |+ ) -> Self: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
41 41 | def __iadd__(
42 42 | self, other: Bad
43 43 | ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
PYI034.pyi:41:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually return `self` at runtime
PYI034.pyi:41:9: PYI034 `__iadd__` methods in classes like `Bad` usually return `self` at runtime
|
39 | self,
40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
@@ -74,19 +43,9 @@ PYI034.pyi:41:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually ret
42 | self, other: Bad
43 | ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
40 40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
41 41 | def __iadd__(
42 42 | self, other: Bad
43 |- ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
43 |+ ) -> Self: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
44 44 |
45 45 | class AlsoBad(
46 46 | int, builtins.object
PYI034.pyi:104:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
PYI034.pyi:104:9: PYI034 `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
|
103 | class BadIterator1(Iterator[int]):
104 | def __iter__(
@@ -94,21 +53,9 @@ PYI034.pyi:104:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` u
105 | self,
106 | ) -> Iterator[
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
103 103 | class BadIterator1(Iterator[int]):
104 104 | def __iter__(
105 105 | self,
106 |- ) -> Iterator[
107 |- int
108 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
106 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
109 107 |
110 108 | class BadIterator2(
111 109 | typing.Iterator[int]
PYI034.pyi:113:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
PYI034.pyi:113:9: PYI034 `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
|
111 | typing.Iterator[int]
112 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
@@ -117,21 +64,9 @@ PYI034.pyi:113:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` u
114 | self,
115 | ) -> Iterator[
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
112 112 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
113 113 | def __iter__(
114 114 | self,
115 |- ) -> Iterator[
116 |- int
117 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
115 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
118 116 |
119 117 | class BadIterator3(
120 118 | typing.Iterator[int]
PYI034.pyi:122:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
PYI034.pyi:122:9: PYI034 `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
|
120 | typing.Iterator[int]
121 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
@@ -140,21 +75,9 @@ PYI034.pyi:122:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` u
123 | self,
124 | ) -> collections.abc.Iterator[
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
121 121 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
122 122 | def __iter__(
123 123 | self,
124 |- ) -> collections.abc.Iterator[
125 |- int
126 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
124 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
127 125 |
128 126 | class BadIterator4(Iterator[int]):
129 127 | # Note: *Iterable*, not *Iterator*, returned!
PYI034.pyi:130:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
PYI034.pyi:130:9: PYI034 `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
|
128 | class BadIterator4(Iterator[int]):
129 | # Note: *Iterable*, not *Iterator*, returned!
@@ -163,21 +86,9 @@ PYI034.pyi:130:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` u
131 | self,
132 | ) -> Iterable[
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
129 129 | # Note: *Iterable*, not *Iterator*, returned!
130 130 | def __iter__(
131 131 | self,
132 |- ) -> Iterable[
133 |- int
134 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
132 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
135 133 |
136 134 | class IteratorReturningIterable:
137 135 | def __iter__(
PYI034.pyi:144:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
PYI034.pyi:144:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
|
143 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
144 | def __aiter__(
@@ -185,16 +96,6 @@ PYI034.pyi:144:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterat
145 | self,
146 | ) -> typing.AsyncIterator[
|
= help: Use `Self` as return type
= help: Consider using `typing_extensions.Self` as return type
Unsafe fix
143 143 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
144 144 | def __aiter__(
145 145 | self,
146 |- ) -> typing.AsyncIterator[
147 |- str
148 |- ]: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
146 |+ ) -> Self: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
149 147 |
150 148 | class AsyncIteratorReturningAsyncIterable:
151 149 | def __aiter__(

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI062.py:5:25: PYI062 [*] Duplicate literal member `True`
PYI062.py:5:25: PYI062 Duplicate literal member `True`
|
3 | import typing_extensions
4 |
@@ -10,19 +10,8 @@ PYI062.py:5:25: PYI062 [*] Duplicate literal member `True`
6 |
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
= help: Remove duplicates
Safe fix
2 2 | import typing as t
3 3 | import typing_extensions
4 4 |
5 |-x: Literal[True, False, True, False] # PYI062 twice here
5 |+x: Literal[True, False] # PYI062 twice here
6 6 |
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
8 8 |
PYI062.py:5:31: PYI062 [*] Duplicate literal member `False`
PYI062.py:5:31: PYI062 Duplicate literal member `False`
|
3 | import typing_extensions
4 |
@@ -31,19 +20,8 @@ PYI062.py:5:31: PYI062 [*] Duplicate literal member `False`
6 |
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
= help: Remove duplicates
Safe fix
2 2 | import typing as t
3 3 | import typing_extensions
4 4 |
5 |-x: Literal[True, False, True, False] # PYI062 twice here
5 |+x: Literal[True, False] # PYI062 twice here
6 6 |
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
8 8 |
PYI062.py:7:45: PYI062 [*] Duplicate literal member `1`
PYI062.py:7:45: PYI062 Duplicate literal member `1`
|
5 | x: Literal[True, False, True, False] # PYI062 twice here
6 |
@@ -52,19 +30,8 @@ PYI062.py:7:45: PYI062 [*] Duplicate literal member `1`
8 |
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
= help: Remove duplicates
Safe fix
4 4 |
5 5 | x: Literal[True, False, True, False] # PYI062 twice here
6 6 |
7 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
7 |+y: Literal[1, print("hello"), 3, 4] # PYI062 on the last 1
8 8 |
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
10 10 |
PYI062.py:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
PYI062.py:9:33: PYI062 Duplicate literal member `{1, 3, 5}`
|
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
8 |
@@ -73,19 +40,8 @@ PYI062.py:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
10 |
11 | Literal[1, Literal[1]] # once
|
= help: Remove duplicates
Safe fix
6 6 |
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
8 8 |
9 |-z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
9 |+z: Literal[{1, 3, 5}, "foobar"] # PYI062 on the set literal
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
PYI062.py:11:20: PYI062 [*] Duplicate literal member `1`
PYI062.py:11:20: PYI062 Duplicate literal member `1`
|
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
10 |
@@ -94,19 +50,8 @@ PYI062.py:11:20: PYI062 [*] Duplicate literal member `1`
12 | Literal[1, 2, Literal[1, 2]] # twice
13 | Literal[1, Literal[1], Literal[1]] # twice
|
= help: Remove duplicates
Safe fix
8 8 |
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
10 10 |
11 |-Literal[1, Literal[1]] # once
11 |+Literal[1] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
PYI062.py:12:23: PYI062 [*] Duplicate literal member `1`
PYI062.py:12:23: PYI062 Duplicate literal member `1`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -114,19 +59,8 @@ PYI062.py:12:23: PYI062 [*] Duplicate literal member `1`
13 | Literal[1, Literal[1], Literal[1]] # twice
14 | Literal[1, Literal[2], Literal[2]] # once
|
= help: Remove duplicates
Safe fix
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 |-Literal[1, 2, Literal[1, 2]] # twice
12 |+Literal[1, 2] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
PYI062.py:12:26: PYI062 [*] Duplicate literal member `2`
PYI062.py:12:26: PYI062 Duplicate literal member `2`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -134,19 +68,8 @@ PYI062.py:12:26: PYI062 [*] Duplicate literal member `2`
13 | Literal[1, Literal[1], Literal[1]] # twice
14 | Literal[1, Literal[2], Literal[2]] # once
|
= help: Remove duplicates
Safe fix
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 |-Literal[1, 2, Literal[1, 2]] # twice
12 |+Literal[1, 2] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
PYI062.py:13:20: PYI062 [*] Duplicate literal member `1`
PYI062.py:13:20: PYI062 Duplicate literal member `1`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -155,19 +78,8 @@ PYI062.py:13:20: PYI062 [*] Duplicate literal member `1`
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
= help: Remove duplicates
Safe fix
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 |-Literal[1, Literal[1], Literal[1]] # twice
13 |+Literal[1] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
PYI062.py:13:32: PYI062 [*] Duplicate literal member `1`
PYI062.py:13:32: PYI062 Duplicate literal member `1`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -176,19 +88,8 @@ PYI062.py:13:32: PYI062 [*] Duplicate literal member `1`
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
= help: Remove duplicates
Safe fix
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 |-Literal[1, Literal[1], Literal[1]] # twice
13 |+Literal[1] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
PYI062.py:14:32: PYI062 [*] Duplicate literal member `2`
PYI062.py:14:32: PYI062 Duplicate literal member `2`
|
12 | Literal[1, 2, Literal[1, 2]] # twice
13 | Literal[1, Literal[1], Literal[1]] # twice
@@ -197,19 +98,8 @@ PYI062.py:14:32: PYI062 [*] Duplicate literal member `2`
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 | typing_extensions.Literal[1, 1, 1] # twice
|
= help: Remove duplicates
Safe fix
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 |-Literal[1, Literal[2], Literal[2]] # once
14 |+Literal[1, 2] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
17 17 |
PYI062.py:15:37: PYI062 [*] Duplicate literal member `1`
PYI062.py:15:37: PYI062 Duplicate literal member `1`
|
13 | Literal[1, Literal[1], Literal[1]] # twice
14 | Literal[1, Literal[2], Literal[2]] # once
@@ -217,19 +107,8 @@ PYI062.py:15:37: PYI062 [*] Duplicate literal member `1`
| ^ PYI062
16 | typing_extensions.Literal[1, 1, 1] # twice
|
= help: Remove duplicates
Safe fix
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 |-t.Literal[1, t.Literal[2, t.Literal[1]]] # once
15 |+t.Literal[1, 2] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
PYI062.py:16:30: PYI062 [*] Duplicate literal member `1`
PYI062.py:16:30: PYI062 Duplicate literal member `1`
|
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
@@ -238,19 +117,8 @@ PYI062.py:16:30: PYI062 [*] Duplicate literal member `1`
17 |
18 | # Ensure issue is only raised once, even on nested literals
|
= help: Remove duplicates
Safe fix
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 |-typing_extensions.Literal[1, 1, 1] # twice
16 |+typing_extensions.Literal[1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
PYI062.py:16:33: PYI062 [*] Duplicate literal member `1`
PYI062.py:16:33: PYI062 Duplicate literal member `1`
|
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
@@ -259,19 +127,8 @@ PYI062.py:16:33: PYI062 [*] Duplicate literal member `1`
17 |
18 | # Ensure issue is only raised once, even on nested literals
|
= help: Remove duplicates
Safe fix
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 |-typing_extensions.Literal[1, 1, 1] # twice
16 |+typing_extensions.Literal[1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
PYI062.py:19:46: PYI062 [*] Duplicate literal member `True`
PYI062.py:19:46: PYI062 Duplicate literal member `True`
|
18 | # Ensure issue is only raised once, even on nested literals
19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
@@ -279,13 +136,3 @@ PYI062.py:19:46: PYI062 [*] Duplicate literal member `True`
20 |
21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
= help: Remove duplicates
Safe fix
16 16 | typing_extensions.Literal[1, 1, 1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
19 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
19 |+MyType = Literal["foo", True, False, "bar"] # PYI062
20 20 |
21 21 | n: Literal["No", "duplicates", "here", 1, "1"]

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI062.pyi:5:25: PYI062 [*] Duplicate literal member `True`
PYI062.pyi:5:25: PYI062 Duplicate literal member `True`
|
3 | import typing_extensions
4 |
@@ -10,19 +10,8 @@ PYI062.pyi:5:25: PYI062 [*] Duplicate literal member `True`
6 |
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
= help: Remove duplicates
Safe fix
2 2 | import typing as t
3 3 | import typing_extensions
4 4 |
5 |-x: Literal[True, False, True, False] # PY062 twice here
5 |+x: Literal[True, False] # PY062 twice here
6 6 |
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
8 8 |
PYI062.pyi:5:31: PYI062 [*] Duplicate literal member `False`
PYI062.pyi:5:31: PYI062 Duplicate literal member `False`
|
3 | import typing_extensions
4 |
@@ -31,19 +20,8 @@ PYI062.pyi:5:31: PYI062 [*] Duplicate literal member `False`
6 |
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
= help: Remove duplicates
Safe fix
2 2 | import typing as t
3 3 | import typing_extensions
4 4 |
5 |-x: Literal[True, False, True, False] # PY062 twice here
5 |+x: Literal[True, False] # PY062 twice here
6 6 |
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
8 8 |
PYI062.pyi:7:45: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:7:45: PYI062 Duplicate literal member `1`
|
5 | x: Literal[True, False, True, False] # PY062 twice here
6 |
@@ -52,19 +30,8 @@ PYI062.pyi:7:45: PYI062 [*] Duplicate literal member `1`
8 |
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
= help: Remove duplicates
Safe fix
4 4 |
5 5 | x: Literal[True, False, True, False] # PY062 twice here
6 6 |
7 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
7 |+y: Literal[1, print("hello"), 3, 4] # PY062 on the last 1
8 8 |
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
10 10 |
PYI062.pyi:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
PYI062.pyi:9:33: PYI062 Duplicate literal member `{1, 3, 5}`
|
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
8 |
@@ -73,19 +40,8 @@ PYI062.pyi:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
10 |
11 | Literal[1, Literal[1]] # once
|
= help: Remove duplicates
Safe fix
6 6 |
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
8 8 |
9 |-z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
9 |+z: Literal[{1, 3, 5}, "foobar"] # PY062 on the set literal
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
PYI062.pyi:11:20: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:11:20: PYI062 Duplicate literal member `1`
|
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
10 |
@@ -94,19 +50,8 @@ PYI062.pyi:11:20: PYI062 [*] Duplicate literal member `1`
12 | Literal[1, 2, Literal[1, 2]] # twice
13 | Literal[1, Literal[1], Literal[1]] # twice
|
= help: Remove duplicates
Safe fix
8 8 |
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
10 10 |
11 |-Literal[1, Literal[1]] # once
11 |+Literal[1] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
PYI062.pyi:12:23: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:12:23: PYI062 Duplicate literal member `1`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -114,19 +59,8 @@ PYI062.pyi:12:23: PYI062 [*] Duplicate literal member `1`
13 | Literal[1, Literal[1], Literal[1]] # twice
14 | Literal[1, Literal[2], Literal[2]] # once
|
= help: Remove duplicates
Safe fix
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 |-Literal[1, 2, Literal[1, 2]] # twice
12 |+Literal[1, 2] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
PYI062.pyi:12:26: PYI062 [*] Duplicate literal member `2`
PYI062.pyi:12:26: PYI062 Duplicate literal member `2`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -134,19 +68,8 @@ PYI062.pyi:12:26: PYI062 [*] Duplicate literal member `2`
13 | Literal[1, Literal[1], Literal[1]] # twice
14 | Literal[1, Literal[2], Literal[2]] # once
|
= help: Remove duplicates
Safe fix
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 |-Literal[1, 2, Literal[1, 2]] # twice
12 |+Literal[1, 2] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
PYI062.pyi:13:20: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:13:20: PYI062 Duplicate literal member `1`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -155,19 +78,8 @@ PYI062.pyi:13:20: PYI062 [*] Duplicate literal member `1`
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
= help: Remove duplicates
Safe fix
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 |-Literal[1, Literal[1], Literal[1]] # twice
13 |+Literal[1] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
PYI062.pyi:13:32: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:13:32: PYI062 Duplicate literal member `1`
|
11 | Literal[1, Literal[1]] # once
12 | Literal[1, 2, Literal[1, 2]] # twice
@@ -176,19 +88,8 @@ PYI062.pyi:13:32: PYI062 [*] Duplicate literal member `1`
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
= help: Remove duplicates
Safe fix
10 10 |
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 |-Literal[1, Literal[1], Literal[1]] # twice
13 |+Literal[1] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
PYI062.pyi:14:32: PYI062 [*] Duplicate literal member `2`
PYI062.pyi:14:32: PYI062 Duplicate literal member `2`
|
12 | Literal[1, 2, Literal[1, 2]] # twice
13 | Literal[1, Literal[1], Literal[1]] # twice
@@ -197,19 +98,8 @@ PYI062.pyi:14:32: PYI062 [*] Duplicate literal member `2`
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 | typing_extensions.Literal[1, 1, 1] # twice
|
= help: Remove duplicates
Safe fix
11 11 | Literal[1, Literal[1]] # once
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 |-Literal[1, Literal[2], Literal[2]] # once
14 |+Literal[1, 2] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
17 17 |
PYI062.pyi:15:37: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:15:37: PYI062 Duplicate literal member `1`
|
13 | Literal[1, Literal[1], Literal[1]] # twice
14 | Literal[1, Literal[2], Literal[2]] # once
@@ -217,19 +107,8 @@ PYI062.pyi:15:37: PYI062 [*] Duplicate literal member `1`
| ^ PYI062
16 | typing_extensions.Literal[1, 1, 1] # twice
|
= help: Remove duplicates
Safe fix
12 12 | Literal[1, 2, Literal[1, 2]] # twice
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 |-t.Literal[1, t.Literal[2, t.Literal[1]]] # once
15 |+t.Literal[1, 2] # once
16 16 | typing_extensions.Literal[1, 1, 1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
PYI062.pyi:16:30: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:16:30: PYI062 Duplicate literal member `1`
|
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
@@ -238,19 +117,8 @@ PYI062.pyi:16:30: PYI062 [*] Duplicate literal member `1`
17 |
18 | # Ensure issue is only raised once, even on nested literals
|
= help: Remove duplicates
Safe fix
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 |-typing_extensions.Literal[1, 1, 1] # twice
16 |+typing_extensions.Literal[1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
PYI062.pyi:16:33: PYI062 [*] Duplicate literal member `1`
PYI062.pyi:16:33: PYI062 Duplicate literal member `1`
|
14 | Literal[1, Literal[2], Literal[2]] # once
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
@@ -259,19 +127,8 @@ PYI062.pyi:16:33: PYI062 [*] Duplicate literal member `1`
17 |
18 | # Ensure issue is only raised once, even on nested literals
|
= help: Remove duplicates
Safe fix
13 13 | Literal[1, Literal[1], Literal[1]] # twice
14 14 | Literal[1, Literal[2], Literal[2]] # once
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
16 |-typing_extensions.Literal[1, 1, 1] # twice
16 |+typing_extensions.Literal[1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
PYI062.pyi:19:46: PYI062 [*] Duplicate literal member `True`
PYI062.pyi:19:46: PYI062 Duplicate literal member `True`
|
18 | # Ensure issue is only raised once, even on nested literals
19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
@@ -279,13 +136,3 @@ PYI062.pyi:19:46: PYI062 [*] Duplicate literal member `True`
20 |
21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
= help: Remove duplicates
Safe fix
16 16 | typing_extensions.Literal[1, 1, 1] # twice
17 17 |
18 18 | # Ensure issue is only raised once, even on nested literals
19 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
19 |+MyType = Literal["foo", True, False, "bar"] # PYI062
20 20 |
21 21 | n: Literal["No", "duplicates", "here", 1, "1"]

View File

@@ -590,7 +590,7 @@ impl AlwaysFixableViolation for PytestErroneousUseFixturesOnFixture {
/// ```
///
/// ## References
/// - [PyPI: `pytest-asyncio`](https://pypi.org/project/pytest-asyncio/)
/// - [`pytest-asyncio`](https://pypi.org/project/pytest-asyncio/)
#[violation]
pub struct PytestUnnecessaryAsyncioMarkOnFixture;

View File

@@ -38,7 +38,7 @@ use ruff_text_size::Ranged;
///
/// ## References
/// - [Python documentation: `unittest.mock.patch`](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch)
/// - [PyPI: `pytest-mock`](https://pypi.org/project/pytest-mock/)
/// - [`pytest-mock`](https://pypi.org/project/pytest-mock/)
#[violation]
pub struct PytestPatchWithLambda;

View File

@@ -31,7 +31,7 @@ use crate::fix;
/// ```
///
/// ## References
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
/// - [PEP 535](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
#[violation]
pub struct EmptyTypeCheckingBlock;

View File

@@ -51,7 +51,7 @@ use crate::rules::flake8_type_checking::imports::ImportBinding;
/// - `lint.flake8-type-checking.quote-annotations`
///
/// ## References
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
/// - [PEP 535](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
#[violation]
pub struct RuntimeImportInTypeCheckingBlock {
qualified_name: String,

View File

@@ -37,8 +37,8 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [PEP 563 - Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/)
/// - [PEP 604 Allow writing union types as `X | Y`](https://peps.python.org/pep-0604/)
/// - [PEP 535](https://peps.python.org/pep-0563/)
/// - [PEP 604](https://peps.python.org/pep-0604/)
///
/// [PEP 604]: https://peps.python.org/pep-0604/
#[violation]

View File

@@ -71,7 +71,7 @@ use crate::rules::isort::{categorize, ImportSection, ImportType};
/// - `lint.typing-modules`
///
/// ## References
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
/// - [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
#[violation]
pub struct TypingOnlyFirstPartyImport {
qualified_name: String,
@@ -146,7 +146,7 @@ impl Violation for TypingOnlyFirstPartyImport {
/// - `lint.typing-modules`
///
/// ## References
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
/// - [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
#[violation]
pub struct TypingOnlyThirdPartyImport {
qualified_name: String,
@@ -221,7 +221,7 @@ impl Violation for TypingOnlyThirdPartyImport {
/// - `lint.typing-modules`
///
/// ## References
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
/// - [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
#[violation]
pub struct TypingOnlyStandardLibraryImport {
qualified_name: String,

View File

@@ -19,10 +19,6 @@ use crate::registry::Rule;
/// An argument that is defined but not used is likely a mistake, and should
/// be removed to avoid confusion.
///
/// If a variable is intentionally defined-but-not-used, it should be
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// ## Example
/// ```python
/// def foo(bar, baz):
@@ -34,9 +30,6 @@ use crate::registry::Rule;
/// def foo(bar):
/// return bar * 2
/// ```
///
/// ## Options
/// - `lint.dummy-variable-rgx`
#[violation]
pub struct UnusedFunctionArgument {
name: String,
@@ -57,10 +50,6 @@ impl Violation for UnusedFunctionArgument {
/// An argument that is defined but not used is likely a mistake, and should
/// be removed to avoid confusion.
///
/// If a variable is intentionally defined-but-not-used, it should be
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// ## Example
/// ```python
/// class Class:
@@ -74,9 +63,6 @@ impl Violation for UnusedFunctionArgument {
/// def foo(self, arg1):
/// print(arg1)
/// ```
///
/// ## Options
/// - `lint.dummy-variable-rgx`
#[violation]
pub struct UnusedMethodArgument {
name: String,
@@ -97,10 +83,6 @@ impl Violation for UnusedMethodArgument {
/// An argument that is defined but not used is likely a mistake, and should
/// be removed to avoid confusion.
///
/// If a variable is intentionally defined-but-not-used, it should be
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// ## Example
/// ```python
/// class Class:
@@ -116,9 +98,6 @@ impl Violation for UnusedMethodArgument {
/// def foo(cls, arg1):
/// print(arg1)
/// ```
///
/// ## Options
/// - `lint.dummy-variable-rgx`
#[violation]
pub struct UnusedClassMethodArgument {
name: String,
@@ -139,10 +118,6 @@ impl Violation for UnusedClassMethodArgument {
/// An argument that is defined but not used is likely a mistake, and should
/// be removed to avoid confusion.
///
/// If a variable is intentionally defined-but-not-used, it should be
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// ## Example
/// ```python
/// class Class:
@@ -158,9 +133,6 @@ impl Violation for UnusedClassMethodArgument {
/// def foo(arg1):
/// print(arg1)
/// ```
///
/// ## Options
/// - `lint.dummy-variable-rgx`
#[violation]
pub struct UnusedStaticMethodArgument {
name: String,
@@ -182,10 +154,6 @@ impl Violation for UnusedStaticMethodArgument {
/// An argument that is defined but not used is likely a mistake, and should
/// be removed to avoid confusion.
///
/// If a variable is intentionally defined-but-not-used, it should be
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// ## Example
/// ```python
/// my_list = [1, 2, 3, 4, 5]
@@ -197,9 +165,6 @@ impl Violation for UnusedStaticMethodArgument {
/// my_list = [1, 2, 3, 4, 5]
/// squares = map(lambda x: x**2, my_list)
/// ```
///
/// ## Options
/// - `lint.dummy-variable-rgx`
#[violation]
pub struct UnusedLambdaArgument {
name: String,

View File

@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
/// ## References
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
/// - [Python documentation: `os.path.getatime`](https://docs.python.org/3/library/os.path.html#os.path.getatime)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)

View File

@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
/// ## References
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
/// - [Python documentation: `os.path.getctime`](https://docs.python.org/3/library/os.path.html#os.path.getctime)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)

View File

@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
/// ## References
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
/// - [Python documentation: `os.path.getmtime`](https://docs.python.org/3/library/os.path.html#os.path.getmtime)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)

View File

@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
/// ## References
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)

View File

@@ -30,7 +30,7 @@ use ruff_macros::{derive_message_formats, violation};
/// ## References
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -73,7 +73,7 @@ impl Violation for OsPathAbspath {
/// ## References
/// - [Python documentation: `Path.chmod`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.chmod)
/// - [Python documentation: `os.chmod`](https://docs.python.org/3/library/os.html#os.chmod)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -116,7 +116,7 @@ impl Violation for OsChmod {
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.makedirs`](https://docs.python.org/3/library/os.html#os.makedirs)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -159,7 +159,7 @@ impl Violation for OsMakedirs {
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.mkdir`](https://docs.python.org/3/library/os.html#os.mkdir)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -202,7 +202,7 @@ impl Violation for OsMkdir {
/// ## References
/// - [Python documentation: `Path.rename`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename)
/// - [Python documentation: `os.rename`](https://docs.python.org/3/library/os.html#os.rename)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -245,7 +245,7 @@ impl Violation for OsRename {
/// ## References
/// - [Python documentation: `Path.replace`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace)
/// - [Python documentation: `os.replace`](https://docs.python.org/3/library/os.html#os.replace)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -288,7 +288,7 @@ impl Violation for OsReplace {
/// ## References
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -331,7 +331,7 @@ impl Violation for OsRmdir {
/// ## References
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -374,7 +374,7 @@ impl Violation for OsRemove {
/// ## References
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -418,7 +418,7 @@ impl Violation for OsUnlink {
/// - [Python documentation: `Path.cwd`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.cwd)
/// - [Python documentation: `os.getcwd`](https://docs.python.org/3/library/os.html#os.getcwd)
/// - [Python documentation: `os.getcwdb`](https://docs.python.org/3/library/os.html#os.getcwdb)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -461,7 +461,7 @@ impl Violation for OsGetcwd {
/// ## References
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -504,7 +504,7 @@ impl Violation for OsPathExists {
/// ## References
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -547,7 +547,7 @@ impl Violation for OsPathExpanduser {
/// ## References
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -590,7 +590,7 @@ impl Violation for OsPathIsdir {
/// ## References
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -633,7 +633,7 @@ impl Violation for OsPathIsfile {
/// ## References
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -676,7 +676,7 @@ impl Violation for OsPathIslink {
/// ## References
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -728,7 +728,7 @@ impl Violation for OsReadlink {
/// - [Python documentation: `Path.group`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.group)
/// - [Python documentation: `Path.owner`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.owner)
/// - [Python documentation: `os.stat`](https://docs.python.org/3/library/os.html#os.stat)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -774,7 +774,7 @@ impl Violation for OsStat {
/// ## References
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -817,7 +817,7 @@ impl Violation for OsPathIsabs {
/// ## References
/// - [Python documentation: `PurePath.joinpath`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.joinpath)
/// - [Python documentation: `os.path.join`](https://docs.python.org/3/library/os.path.html#os.path.join)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -877,7 +877,7 @@ pub(crate) enum Joiner {
/// ## References
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -920,7 +920,7 @@ impl Violation for OsPathBasename {
/// ## References
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -963,7 +963,7 @@ impl Violation for OsPathDirname {
/// ## References
/// - [Python documentation: `Path.samefile`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.samefile)
/// - [Python documentation: `os.path.samefile`](https://docs.python.org/3/library/os.path.html#os.path.samefile)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -1015,7 +1015,7 @@ impl Violation for OsPathSamefile {
/// - [Python documentation: `Path.suffix`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffix)
/// - [Python documentation: `Path.suffixes`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffixes)
/// - [Python documentation: `os.path.splitext`](https://docs.python.org/3/library/os.path.html#os.path.splitext)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)
@@ -1055,7 +1055,7 @@ impl Violation for OsPathSplitext {
/// ## References
/// - [Python documentation: `Path.open`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.open)
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [PEP 428](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/)

View File

@@ -33,7 +33,7 @@ use crate::Locator;
/// ```
///
/// ## References
/// - [_Why You Should Probably Never Use pandas `inplace=True`_](https://towardsdatascience.com/why-you-should-probably-never-use-pandas-inplace-true-9f9f211849e4)
/// - [_Why You Should Probably Never Use pandas inplace=True_](https://towardsdatascience.com/why-you-should-probably-never-use-pandas-inplace-true-9f9f211849e4)
#[violation]
pub struct PandasUseOfInplaceArgument;

View File

@@ -35,10 +35,10 @@ use crate::rules::pep8_naming::helpers;
/// from example import MyClassName
/// ```
///
/// [PEP 8]: https://peps.python.org/pep-0008/
///
/// ## Options
/// - `lint.flake8-import-conventions.aliases`
///
/// [PEP 8]: https://peps.python.org/pep-0008/
#[violation]
pub struct CamelcaseImportedAsAcronym {
name: String,

View File

@@ -61,7 +61,7 @@ const BLANK_LINES_NESTED_LEVEL: u32 = 1;
/// them. That's why this rule is not enabled in typing stub files.
///
/// ## References
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E301.html)
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
#[violation]
@@ -114,7 +114,7 @@ impl AlwaysFixableViolation for BlankLineBetweenMethods {
/// - `lint.isort.lines-after-imports`
///
/// ## References
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E302.html)
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
#[violation]
@@ -181,7 +181,7 @@ impl AlwaysFixableViolation for BlankLinesTopLevel {
/// - `lint.isort.lines-between-types`
///
/// ## References
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E303.html)
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
#[violation]
@@ -228,7 +228,7 @@ impl AlwaysFixableViolation for TooManyBlankLines {
/// ```
///
/// ## References
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E304.html)
#[violation]
pub struct BlankLineAfterDecorator {
@@ -278,7 +278,7 @@ impl AlwaysFixableViolation for BlankLineAfterDecorator {
/// them. That's why this rule is not enabled in typing stub files.
///
/// ## References
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E305.html)
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
#[violation]
@@ -332,7 +332,7 @@ impl AlwaysFixableViolation for BlankLinesAfterFunctionOrClass {
/// them. That's why this rule is not enabled in typing stub files.
///
/// ## References
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E306.html)
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
#[violation]

View File

@@ -52,7 +52,7 @@ use crate::Locator;
/// See [#10885](https://github.com/astral-sh/ruff/issues/10885) for more.
///
/// ## References
/// - [PEP 498 Literal String Interpolation](https://peps.python.org/pep-0498/)
/// - [PEP 498](https://www.python.org/dev/peps/pep-0498/)
#[violation]
pub struct FStringMissingPlaceholders;

View File

@@ -21,7 +21,7 @@ use ruff_macros::{derive_message_formats, violation};
/// ```
///
/// ## References
/// - [PEP 563 Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/)
/// - [PEP 563](https://www.python.org/dev/peps/pep-0563/)
#[violation]
pub struct ForwardAnnotationSyntaxError {
pub body: String,

View File

@@ -8,7 +8,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `if` statements that use non-empty tuples as test conditions.
/// Checks for `if statements that use non-empty tuples as test conditions.
///
/// ## Why is this bad?
/// Non-empty tuples are always `True`, so an `if` statement with a non-empty

View File

@@ -13,9 +13,6 @@ use ruff_macros::{derive_message_formats, violation};
/// In Python 3, no more than 1 << 8 assignments are allowed before a starred
/// expression, and no more than 1 << 24 expressions are allowed after a starred
/// expression.
///
/// ## References
/// - [PEP 3132 Extended Iterable Unpacking](https://peps.python.org/pep-3132/)
#[violation]
pub struct ExpressionsInStarAssignment;
@@ -41,7 +38,7 @@ impl Violation for ExpressionsInStarAssignment {
/// ```
///
/// ## References
/// - [PEP 3132 Extended Iterable Unpacking](https://peps.python.org/pep-3132/)
/// - [PEP 3132](https://peps.python.org/pep-3132/)
#[violation]
pub struct MultipleStarredExpressions;

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