[ty] Add support for @warnings.deprecated (#19376)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

* [x] basic handling
  * [x] parse and discover `@warnings.deprecated` attributes
  * [x] associate them with function definitions
  * [x] associate them with class definitions
  * [x] add a new "deprecated" diagnostic
* [x] ensure diagnostic is styled appropriately for LSPs
(DiagnosticTag::Deprecated)

* [x] functions
  * [x] fire on calls
  * [x] fire on arbitrary references 
* [x] classes
  * [x] fire on initializers
  * [x] fire on arbitrary references
* [x] methods
  * [x] fire on calls
  * [x] fire on arbitrary references
* [ ] overloads
  * [ ] fire on calls
  * [ ] fire on arbitrary references(??? maybe not ???)
  * [ ] only fire if the actual selected overload is deprecated 

* [ ] dunder desugarring (warn on deprecated `__add__` if `+` is
invoked)
* [ ] alias supression? (don't warn on uses of variables that deprecated
items were assigned to)

* [ ] import logic
  * [x] fire on imports of deprecated items
* [ ] suppress subsequent diagnostics if the import diagnostic fired (is
this handled by alias supression?)
  * [x] fire on all qualified references (`module.mydeprecated`)
  * [x] fire on all references that depend on a `*` import
    


Fixes https://github.com/astral-sh/ty/issues/153
This commit is contained in:
Aria Desires 2025-07-18 19:50:29 -04:00 committed by GitHub
parent e9a64e5825
commit 06f9f52e59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1000 additions and 73 deletions

View file

@ -527,14 +527,21 @@ impl<'a> ProjectBenchmark<'a> {
#[track_caller] #[track_caller]
fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) { fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) {
fn check_project(db: &mut ProjectDatabase, max_diagnostics: usize) { fn check_project(db: &mut ProjectDatabase, project_name: &str, max_diagnostics: usize) {
let result = db.check(); let result = db.check();
let diagnostics = result.len(); let diagnostics = result.len();
assert!( if diagnostics > max_diagnostics {
diagnostics <= max_diagnostics, let details = result
"Expected <={max_diagnostics} diagnostics but got {diagnostics}" .into_iter()
); .map(|diagnostic| diagnostic.concise_message().to_string())
.collect::<Vec<_>>()
.join("\n ");
assert!(
diagnostics <= max_diagnostics,
"{project_name}: Expected <={max_diagnostics} diagnostics but got {diagnostics}:\n {details}",
);
}
} }
setup_rayon(); setup_rayon();
@ -544,7 +551,7 @@ fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) {
group.bench_function(benchmark.project.config.name, |b| { group.bench_function(benchmark.project.config.name, |b| {
b.iter_batched_ref( b.iter_batched_ref(
|| benchmark.setup_iteration(), || benchmark.setup_iteration(),
|db| check_project(db, benchmark.max_diagnostics), |db| check_project(db, benchmark.project.config.name, benchmark.max_diagnostics),
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
@ -612,7 +619,7 @@ fn datetype(criterion: &mut Criterion) {
max_dep_date: "2025-07-04", max_dep_date: "2025-07-04",
python_version: PythonVersion::PY313, python_version: PythonVersion::PY313,
}, },
0, 2,
); );
bench_project(&benchmark, criterion); bench_project(&benchmark, criterion);

141
crates/ty/docs/rules.md generated
View file

@ -36,7 +36,7 @@ def test(): -> "int":
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L100)
</small> </small>
**What it does** **What it does**
@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L144)
</small> </small>
**What it does** **What it does**
@ -88,7 +88,7 @@ f(int) # error
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L170)
</small> </small>
**What it does** **What it does**
@ -117,7 +117,7 @@ a = 1
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L195)
</small> </small>
**What it does** **What it does**
@ -147,7 +147,7 @@ class C(A, B): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L221)
</small> </small>
**What it does** **What it does**
@ -177,7 +177,7 @@ class B(A): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L286)
</small> </small>
**What it does** **What it does**
@ -202,7 +202,7 @@ class B(A, A): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L307)
</small> </small>
**What it does** **What it does**
@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449)
</small> </small>
**What it does** **What it does**
@ -334,7 +334,7 @@ class C(A, B): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L451) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L473)
</small> </small>
**What it does** **What it does**
@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L317) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L339)
</small> </small>
**What it does** **What it does**
@ -445,7 +445,7 @@ an atypical memory layout.
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L493)
</small> </small>
**What it does** **What it does**
@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L511) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L533)
</small> </small>
**What it does** **What it does**
@ -496,7 +496,7 @@ a: int = ''
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1515) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1537)
</small> </small>
**What it does** **What it does**
@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L533) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L555)
</small> </small>
**What it does** **What it does**
@ -550,7 +550,7 @@ class A(42): ... # error: [invalid-base]
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L584) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L606)
</small> </small>
**What it does** **What it does**
@ -575,7 +575,7 @@ with 1:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L605) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627)
</small> </small>
**What it does** **What it does**
@ -602,7 +602,7 @@ a: str
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L650)
</small> </small>
**What it does** **What it does**
@ -644,7 +644,7 @@ except ZeroDivisionError:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L664) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L686)
</small> </small>
**What it does** **What it does**
@ -675,7 +675,7 @@ class C[U](Generic[T]): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L690) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L712)
</small> </small>
**What it does** **What it does**
@ -708,7 +708,7 @@ def f(t: TypeVar("U")): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L761)
</small> </small>
**What it does** **What it does**
@ -740,7 +740,7 @@ class B(metaclass=f): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L788)
</small> </small>
**What it does** **What it does**
@ -788,7 +788,7 @@ def foo(x: int) -> int: ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L831)
</small> </small>
**What it does** **What it does**
@ -812,7 +812,7 @@ def f(a: int = ''): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L399) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L421)
</small> </small>
**What it does** **What it does**
@ -844,7 +844,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L829) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L851)
</small> </small>
Checks for `raise` statements that raise non-exceptions or use invalid Checks for `raise` statements that raise non-exceptions or use invalid
@ -891,7 +891,7 @@ def g():
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L492) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L514)
</small> </small>
**What it does** **What it does**
@ -914,7 +914,7 @@ def func() -> int:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L894)
</small> </small>
**What it does** **What it does**
@ -968,7 +968,7 @@ TODO #14889
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L718) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L740)
</small> </small>
**What it does** **What it does**
@ -993,7 +993,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L911) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L933)
</small> </small>
**What it does** **What it does**
@ -1021,7 +1021,7 @@ TYPE_CHECKING = ''
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L935) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L957)
</small> </small>
**What it does** **What it does**
@ -1049,7 +1049,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L987) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1009)
</small> </small>
**What it does** **What it does**
@ -1081,7 +1081,7 @@ f(10) # Error
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L981)
</small> </small>
**What it does** **What it does**
@ -1113,7 +1113,7 @@ class C:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1015) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1037)
</small> </small>
**What it does** **What it does**
@ -1146,7 +1146,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1044) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1066)
</small> </small>
**What it does** **What it does**
@ -1169,7 +1169,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085)
</small> </small>
**What it does** **What it does**
@ -1196,7 +1196,7 @@ func("string") # error: [no-matching-overload]
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1108)
</small> </small>
**What it does** **What it does**
@ -1218,7 +1218,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126)
</small> </small>
**What it does** **What it does**
@ -1242,7 +1242,7 @@ for i in 34: # TypeError: 'int' object is not iterable
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1155) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1177)
</small> </small>
**What it does** **What it does**
@ -1296,7 +1296,7 @@ def test(): -> "int":
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1513)
</small> </small>
**What it does** **What it does**
@ -1324,7 +1324,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1246) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1268)
</small> </small>
**What it does** **What it does**
@ -1351,7 +1351,7 @@ class B(A): ... # Error raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1291) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313)
</small> </small>
**What it does** **What it does**
@ -1376,7 +1376,7 @@ f("foo") # Error raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1291)
</small> </small>
**What it does** **What it does**
@ -1402,7 +1402,7 @@ def _(x: int):
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1334)
</small> </small>
**What it does** **What it does**
@ -1446,7 +1446,7 @@ class A:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1369) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1391)
</small> </small>
**What it does** **What it does**
@ -1471,7 +1471,7 @@ f(x=1, y=2) # Error raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1390) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1412)
</small> </small>
**What it does** **What it does**
@ -1497,7 +1497,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1412) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434)
</small> </small>
**What it does** **What it does**
@ -1520,7 +1520,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1431) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1453)
</small> </small>
**What it does** **What it does**
@ -1543,7 +1543,7 @@ print(x) # NameError: name 'x' is not defined
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1124) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1146)
</small> </small>
**What it does** **What it does**
@ -1578,7 +1578,7 @@ b1 < b2 < b1 # exception raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1450) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1472)
</small> </small>
**What it does** **What it does**
@ -1604,7 +1604,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1472) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1494)
</small> </small>
**What it does** **What it does**
@ -1622,6 +1622,31 @@ l = list(range(10))
l[1:10:0] # ValueError: slice step cannot be zero l[1:10:0] # ValueError: slice step cannot be zero
``` ```
## `deprecated`
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L265)
</small>
**What it does**
Checks for uses of deprecated items
**Why is this bad?**
Deprecated items should no longer be used.
**Examples**
```python
@warnings.deprecated("use new_func instead")
def old_func(): ...
old_func() # emits [deprecated] diagnostic
```
## `invalid-ignore-comment` ## `invalid-ignore-comment`
<small> <small>
@ -1655,7 +1680,7 @@ a = 20 / 0 # type: ignore
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1176) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1198)
</small> </small>
**What it does** **What it does**
@ -1681,7 +1706,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L118)
</small> </small>
**What it does** **What it does**
@ -1711,7 +1736,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1198) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1220)
</small> </small>
**What it does** **What it does**
@ -1741,7 +1766,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1543) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1565)
</small> </small>
**What it does** **What it does**
@ -1766,7 +1791,7 @@ cast(int, f()) # Redundant
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1373)
</small> </small>
**What it does** **What it does**
@ -1817,7 +1842,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1564) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1586)
</small> </small>
**What it does** **What it does**
@ -1871,7 +1896,7 @@ def g():
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L551) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L573)
</small> </small>
**What it does** **What it does**
@ -1908,7 +1933,7 @@ class D(C): ... # error: [unsupported-base]
<small> <small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L247)
</small> </small>
**What it does** **What it does**
@ -1930,7 +1955,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
<small> <small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1224) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1246)
</small> </small>
**What it does** **What it does**

View file

@ -0,0 +1,355 @@
# Tests for the `@deprecated` decorator
## Introduction
<!-- snapshot-diagnostics -->
The decorator `@deprecated("some message")` can be applied to functions, methods, overloads, and
classes. Uses of these items should subsequently produce a warning.
```py
from typing_extensions import deprecated
@deprecated("use OtherClass")
def myfunc(): ...
myfunc() # error: [deprecated] "use OtherClass"
```
```py
from typing_extensions import deprecated
@deprecated("use BetterClass")
class MyClass: ...
MyClass() # error: [deprecated] "use BetterClass"
```
```py
from typing_extensions import deprecated
class MyClass:
@deprecated("use something else")
def afunc(): ...
@deprecated("don't use this!")
def amethod(self): ...
MyClass.afunc() # error: [deprecated] "use something else"
MyClass().amethod() # error: [deprecated] "don't use this!"
```
## Syntax
<!-- snapshot-diagnostics -->
The typeshed declaration of the decorator is as follows:
```ignore
class deprecated:
message: LiteralString
category: type[Warning] | None
stacklevel: int
def __init__(self, message: LiteralString, /, *, category: type[Warning] | None = ..., stacklevel: int = 1) -> None: ...
def __call__(self, arg: _T, /) -> _T: ...
```
Only the mandatory message string is of interest to static analysis, the other two affect only
runtime behaviour.
```py
from typing_extensions import deprecated
@deprecated # error: [invalid-argument-type] "LiteralString"
def invalid_deco(): ...
invalid_deco() # error: [missing-argument]
```
```py
from typing_extensions import deprecated
@deprecated() # error: [missing-argument] "message"
def invalid_deco(): ...
invalid_deco()
```
The argument is supposed to be a LiteralString, and we can handle simple constant propagations like
this:
```py
from typing_extensions import deprecated
x = "message"
@deprecated(x)
def invalid_deco(): ...
invalid_deco() # error: [deprecated] "message"
```
However sufficiently opaque LiteralStrings we can't resolve, and so we lose the message:
```py
from typing_extensions import deprecated, LiteralString
def opaque() -> LiteralString:
return "message"
@deprecated(opaque())
def valid_deco(): ...
valid_deco() # error: [deprecated]
```
Fully dynamic strings are technically allowed at runtime, but typeshed mandates that the input is a
LiteralString, so we can/should emit a diagnostic for this:
```py
from typing_extensions import deprecated
def opaque() -> str:
return "message"
@deprecated(opaque()) # error: [invalid-argument-type] "LiteralString"
def dubious_deco(): ...
dubious_deco()
```
Although we have no use for the other arguments, we should still error if they're wrong.
```py
from typing_extensions import deprecated
@deprecated("some message", dsfsdf="whatever") # error: [unknown-argument] "dsfsdf"
def invalid_deco(): ...
invalid_deco()
```
And we should always handle correct ones fine.
```py
from typing_extensions import deprecated
@deprecated("some message", category=DeprecationWarning, stacklevel=1)
def valid_deco(): ...
valid_deco() # error: [deprecated] "some message"
```
## Different Versions
There are 2 different sources of `@deprecated`: `warnings` and `typing_extensions`. The version in
`warnings` was added in 3.13, the version in `typing_extensions` is a compatibility shim.
```toml
[environment]
python-version = "3.13"
```
`main.py`:
```py
import warnings
import typing_extensions
@warnings.deprecated("nope")
def func1(): ...
@typing_extensions.deprecated("nada")
def func2(): ...
func1() # error: [deprecated] "nope"
func2() # error: [deprecated] "nada"
```
## Imports
### Direct Import Deprecated
Importing a deprecated item should produce a warning. Subsequent uses of the deprecated item
shouldn't produce a warning.
`module.py`:
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
```
`main.py`:
```py
# error: [deprecated] "Use OtherType instead"
# error: [deprecated] "Use other_func instead"
from module import DeprType, depr_func
# TODO: these diagnostics ideally shouldn't fire since we warn on the import
DeprType() # error: [deprecated] "Use OtherType instead"
depr_func() # error: [deprecated] "Use other_func instead"
def higher_order(x): ...
# TODO: these diagnostics ideally shouldn't fire since we warn on the import
higher_order(DeprType) # error: [deprecated] "Use OtherType instead"
higher_order(depr_func) # error: [deprecated] "Use other_func instead"
# TODO: these diagnostics ideally shouldn't fire since we warn on the import
DeprType.__str__ # error: [deprecated] "Use OtherType instead"
depr_func.__str__ # error: [deprecated] "Use other_func instead"
```
### Non-Import Deprecated
If the items aren't imported and instead referenced using `module.item` then each use should produce
a warning.
`module.py`:
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
```
`main.py`:
```py
import module
module.DeprType() # error: [deprecated] "Use OtherType instead"
module.depr_func() # error: [deprecated] "Use other_func instead"
def higher_order(x): ...
higher_order(module.DeprType) # error: [deprecated] "Use OtherType instead"
higher_order(module.depr_func) # error: [deprecated] "Use other_func instead"
module.DeprType.__str__ # error: [deprecated] "Use OtherType instead"
module.depr_func.__str__ # error: [deprecated] "Use other_func instead"
```
### Star Import Deprecated
If the items are instead star-imported, then the actual uses should warn.
`module.py`:
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
```
`main.py`:
```py
from module import *
DeprType() # error: [deprecated] "Use OtherType instead"
depr_func() # error: [deprecated] "Use other_func instead"
def higher_order(x): ...
higher_order(DeprType) # error: [deprecated] "Use OtherType instead"
higher_order(depr_func) # error: [deprecated] "Use other_func instead"
DeprType.__str__ # error: [deprecated] "Use OtherType instead"
depr_func.__str__ # error: [deprecated] "Use other_func instead"
```
## Aliases
Ideally a deprecated warning shouldn't transitively follow assignments, as you already had to "name"
the deprecated symbol to assign it to something else. These kinds of diagnostics would therefore be
redundant and annoying.
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
alias_func = depr_func # error: [deprecated] "Use other_func instead"
AliasClass = DeprType # error: [deprecated] "Use OtherType instead"
# TODO: these diagnostics ideally shouldn't fire
alias_func() # error: [deprecated] "Use other_func instead"
AliasClass() # error: [deprecated] "Use OtherType instead"
```
## Dunders
If a dunder like `__add__` is deprecated, then the equivalent syntactic sugar like `+` should fire a
diagnostic.
```py
from typing_extensions import deprecated
class MyInt:
def __init__(self, val):
self.val = val
@deprecated("MyInt `+` support is broken")
def __add__(self, other):
return MyInt(self.val + other.val)
x = MyInt(1)
y = MyInt(2)
z = x + y # TODO error: [deprecated] "MyInt `+` support is broken"
```
## Overloads
Overloads can be deprecated, but only trigger warnings when invoked.
```py
from typing_extensions import deprecated
from typing_extensions import overload
@overload
@deprecated("strings are no longer supported")
def f(x: str): ...
@overload
def f(x: int): ...
def f(x):
print(x)
f(1)
f("hello") # TODO: error: [deprecated] "strings are no longer supported"
```
If the actual impl is deprecated, the deprecation always fires.
```py
from typing_extensions import deprecated
from typing_extensions import overload
@overload
def f(x: str): ...
@overload
def f(x: int): ...
@deprecated("unusable")
def f(x):
print(x)
f(1) # error: [deprecated] "unusable"
f("hello") # error: [deprecated] "unusable"
```

View file

@ -0,0 +1,93 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: deprecated.md - Tests for the `@deprecated` decorator - Introduction
mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing_extensions import deprecated
2 |
3 | @deprecated("use OtherClass")
4 | def myfunc(): ...
5 |
6 | myfunc() # error: [deprecated] "use OtherClass"
7 | from typing_extensions import deprecated
8 |
9 | @deprecated("use BetterClass")
10 | class MyClass: ...
11 |
12 | MyClass() # error: [deprecated] "use BetterClass"
13 | from typing_extensions import deprecated
14 |
15 | class MyClass:
16 | @deprecated("use something else")
17 | def afunc(): ...
18 | @deprecated("don't use this!")
19 | def amethod(self): ...
20 |
21 | MyClass.afunc() # error: [deprecated] "use something else"
22 | MyClass().amethod() # error: [deprecated] "don't use this!"
```
# Diagnostics
```
warning[deprecated]: The function `myfunc` is deprecated
--> src/mdtest_snippet.py:6:1
|
4 | def myfunc(): ...
5 |
6 | myfunc() # error: [deprecated] "use OtherClass"
| ^^^^^^ use OtherClass
7 | from typing_extensions import deprecated
|
info: rule `deprecated` is enabled by default
```
```
warning[deprecated]: The class `MyClass` is deprecated
--> src/mdtest_snippet.py:12:1
|
10 | class MyClass: ...
11 |
12 | MyClass() # error: [deprecated] "use BetterClass"
| ^^^^^^^ use BetterClass
13 | from typing_extensions import deprecated
|
info: rule `deprecated` is enabled by default
```
```
warning[deprecated]: The function `afunc` is deprecated
--> src/mdtest_snippet.py:21:9
|
19 | def amethod(self): ...
20 |
21 | MyClass.afunc() # error: [deprecated] "use something else"
| ^^^^^ use something else
22 | MyClass().amethod() # error: [deprecated] "don't use this!"
|
info: rule `deprecated` is enabled by default
```
```
warning[deprecated]: The function `amethod` is deprecated
--> src/mdtest_snippet.py:22:11
|
21 | MyClass.afunc() # error: [deprecated] "use something else"
22 | MyClass().amethod() # error: [deprecated] "don't use this!"
| ^^^^^^^ don't use this!
|
info: rule `deprecated` is enabled by default
```

View file

@ -0,0 +1,178 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: deprecated.md - Tests for the `@deprecated` decorator - Syntax
mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing_extensions import deprecated
2 |
3 | @deprecated # error: [invalid-argument-type] "LiteralString"
4 | def invalid_deco(): ...
5 |
6 | invalid_deco() # error: [missing-argument]
7 | from typing_extensions import deprecated
8 |
9 | @deprecated() # error: [missing-argument] "message"
10 | def invalid_deco(): ...
11 |
12 | invalid_deco()
13 | from typing_extensions import deprecated
14 |
15 | x = "message"
16 |
17 | @deprecated(x)
18 | def invalid_deco(): ...
19 |
20 | invalid_deco() # error: [deprecated] "message"
21 | from typing_extensions import deprecated, LiteralString
22 |
23 | def opaque() -> LiteralString:
24 | return "message"
25 |
26 | @deprecated(opaque())
27 | def valid_deco(): ...
28 |
29 | valid_deco() # error: [deprecated]
30 | from typing_extensions import deprecated
31 |
32 | def opaque() -> str:
33 | return "message"
34 |
35 | @deprecated(opaque()) # error: [invalid-argument-type] "LiteralString"
36 | def dubious_deco(): ...
37 |
38 | dubious_deco()
39 | from typing_extensions import deprecated
40 |
41 | @deprecated("some message", dsfsdf="whatever") # error: [unknown-argument] "dsfsdf"
42 | def invalid_deco(): ...
43 |
44 | invalid_deco()
45 | from typing_extensions import deprecated
46 |
47 | @deprecated("some message", category=DeprecationWarning, stacklevel=1)
48 | def valid_deco(): ...
49 |
50 | valid_deco() # error: [deprecated] "some message"
```
# Diagnostics
```
error[invalid-argument-type]: Argument to class `deprecated` is incorrect
--> src/mdtest_snippet.py:3:1
|
1 | from typing_extensions import deprecated
2 |
3 | @deprecated # error: [invalid-argument-type] "LiteralString"
| ^^^^^^^^^^^ Expected `LiteralString`, found `def invalid_deco() -> Unknown`
4 | def invalid_deco(): ...
|
info: rule `invalid-argument-type` is enabled by default
```
```
error[missing-argument]: No argument provided for required parameter `arg` of bound method `__call__`
--> src/mdtest_snippet.py:6:1
|
4 | def invalid_deco(): ...
5 |
6 | invalid_deco() # error: [missing-argument]
| ^^^^^^^^^^^^^^
7 | from typing_extensions import deprecated
|
info: rule `missing-argument` is enabled by default
```
```
error[missing-argument]: No argument provided for required parameter `message` of class `deprecated`
--> src/mdtest_snippet.py:9:2
|
7 | from typing_extensions import deprecated
8 |
9 | @deprecated() # error: [missing-argument] "message"
| ^^^^^^^^^^^^
10 | def invalid_deco(): ...
|
info: rule `missing-argument` is enabled by default
```
```
warning[deprecated]: The function `invalid_deco` is deprecated
--> src/mdtest_snippet.py:20:1
|
18 | def invalid_deco(): ...
19 |
20 | invalid_deco() # error: [deprecated] "message"
| ^^^^^^^^^^^^ message
21 | from typing_extensions import deprecated, LiteralString
|
info: rule `deprecated` is enabled by default
```
```
warning[deprecated]: The function `valid_deco` is deprecated
--> src/mdtest_snippet.py:29:1
|
27 | def valid_deco(): ...
28 |
29 | valid_deco() # error: [deprecated]
| ^^^^^^^^^^
30 | from typing_extensions import deprecated
|
info: rule `deprecated` is enabled by default
```
```
error[invalid-argument-type]: Argument to class `deprecated` is incorrect
--> src/mdtest_snippet.py:35:13
|
33 | return "message"
34 |
35 | @deprecated(opaque()) # error: [invalid-argument-type] "LiteralString"
| ^^^^^^^^ Expected `LiteralString`, found `str`
36 | def dubious_deco(): ...
|
info: rule `invalid-argument-type` is enabled by default
```
```
error[unknown-argument]: Argument `dsfsdf` does not match any known parameter of class `deprecated`
--> src/mdtest_snippet.py:41:29
|
39 | from typing_extensions import deprecated
40 |
41 | @deprecated("some message", dsfsdf="whatever") # error: [unknown-argument] "dsfsdf"
| ^^^^^^^^^^^^^^^^^
42 | def invalid_deco(): ...
|
info: rule `unknown-argument` is enabled by default
```
```
warning[deprecated]: The function `valid_deco` is deprecated
--> src/mdtest_snippet.py:50:1
|
48 | def valid_deco(): ...
49 |
50 | valid_deco() # error: [deprecated] "some message"
| ^^^^^^^^^^ some message
|
info: rule `deprecated` is enabled by default
```

View file

@ -275,6 +275,7 @@ pub enum KnownModule {
UnittestMock, UnittestMock,
#[cfg(test)] #[cfg(test)]
Uuid, Uuid,
Warnings,
} }
impl KnownModule { impl KnownModule {
@ -294,6 +295,7 @@ impl KnownModule {
Self::TypeCheckerInternals => "_typeshed._type_checker_internals", Self::TypeCheckerInternals => "_typeshed._type_checker_internals",
Self::TyExtensions => "ty_extensions", Self::TyExtensions => "ty_extensions",
Self::ImportLib => "importlib", Self::ImportLib => "importlib",
Self::Warnings => "warnings",
#[cfg(test)] #[cfg(test)]
Self::UnittestMock => "unittest.mock", Self::UnittestMock => "unittest.mock",
#[cfg(test)] #[cfg(test)]

View file

@ -4187,6 +4187,45 @@ impl<'db> Type<'db> {
.into() .into()
} }
Some(KnownClass::Deprecated) => {
// ```py
// class deprecated:
// def __new__(
// cls,
// message: LiteralString,
// /,
// *,
// category: type[Warning] | None = ...,
// stacklevel: int = 1
// ) -> Self: ...
// ```
Binding::single(
self,
Signature::new(
Parameters::new([
Parameter::positional_only(Some(Name::new_static("message")))
.with_annotated_type(Type::LiteralString),
Parameter::keyword_only(Name::new_static("category"))
.with_annotated_type(UnionType::from_elements(
db,
[
// TODO: should be `type[Warning]`
Type::any(),
KnownClass::NoneType.to_instance(db),
],
))
// TODO: should be `type[Warning]`
.with_default_type(Type::any()),
Parameter::keyword_only(Name::new_static("stacklevel"))
.with_annotated_type(KnownClass::Int.to_instance(db))
.with_default_type(Type::IntLiteral(1)),
]),
Some(KnownClass::Deprecated.to_instance(db)),
),
)
.into()
}
Some(KnownClass::TypeAliasType) => { Some(KnownClass::TypeAliasType) => {
// ```py // ```py
// def __new__( // def __new__(
@ -4450,8 +4489,11 @@ impl<'db> Type<'db> {
Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db), Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db),
Type::KnownInstance(known_instance) => {
known_instance.instance_fallback(db).bindings(db)
}
Type::PropertyInstance(_) Type::PropertyInstance(_)
| Type::KnownInstance(_)
| Type::AlwaysFalsy | Type::AlwaysFalsy
| Type::AlwaysTruthy | Type::AlwaysTruthy
| Type::IntLiteral(_) | Type::IntLiteral(_)
@ -5016,6 +5058,10 @@ impl<'db> Type<'db> {
Type::KnownInstance(known_instance) => match known_instance { Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeAliasType(alias) => Ok(alias.value_type(db)), KnownInstanceType::TypeAliasType(alias) => Ok(alias.value_type(db)),
KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)), KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)),
KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Deprecated],
fallback_type: Type::unknown(),
}),
KnownInstanceType::SubscriptedProtocol(_) => Err(InvalidTypeExpressionError { KnownInstanceType::SubscriptedProtocol(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![ invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::Protocol InvalidTypeExpression::Protocol
@ -5861,6 +5907,9 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias) /// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
TypeAliasType(TypeAliasType<'db>), TypeAliasType(TypeAliasType<'db>),
/// A single instance of `warnings.deprecated` or `typing_extensions.deprecated`
Deprecated(DeprecatedInstance<'db>),
} }
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -5879,6 +5928,9 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
KnownInstanceType::TypeAliasType(type_alias) => { KnownInstanceType::TypeAliasType(type_alias) => {
visitor.visit_type_alias_type(db, type_alias); visitor.visit_type_alias_type(db, type_alias);
} }
KnownInstanceType::Deprecated(_) => {
// Nothing to visit
}
} }
} }
@ -5895,6 +5947,10 @@ impl<'db> KnownInstanceType<'db> {
Self::TypeAliasType(type_alias) => { Self::TypeAliasType(type_alias) => {
Self::TypeAliasType(type_alias.normalized_impl(db, visitor)) Self::TypeAliasType(type_alias.normalized_impl(db, visitor))
} }
Self::Deprecated(deprecated) => {
// Nothing to normalize
Self::Deprecated(deprecated)
}
} }
} }
@ -5903,6 +5959,7 @@ impl<'db> KnownInstanceType<'db> {
Self::SubscriptedProtocol(_) | Self::SubscriptedGeneric(_) => KnownClass::SpecialForm, Self::SubscriptedProtocol(_) | Self::SubscriptedGeneric(_) => KnownClass::SpecialForm,
Self::TypeVar(_) => KnownClass::TypeVar, Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType, Self::TypeAliasType(_) => KnownClass::TypeAliasType,
Self::Deprecated(_) => KnownClass::Deprecated,
} }
} }
@ -5947,6 +6004,7 @@ impl<'db> KnownInstanceType<'db> {
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll // it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name. // have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"), KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"),
KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"),
} }
} }
} }
@ -6135,6 +6193,8 @@ enum InvalidTypeExpression<'db> {
Protocol, Protocol,
/// Same for `Generic` /// Same for `Generic`
Generic, Generic,
/// Same for `@deprecated`
Deprecated,
/// Type qualifiers are always invalid in *type expressions*, /// Type qualifiers are always invalid in *type expressions*,
/// but these ones are okay with 0 arguments in *annotation expressions* /// but these ones are okay with 0 arguments in *annotation expressions*
TypeQualifier(SpecialFormType), TypeQualifier(SpecialFormType),
@ -6176,6 +6236,9 @@ impl<'db> InvalidTypeExpression<'db> {
InvalidTypeExpression::Generic => { InvalidTypeExpression::Generic => {
f.write_str("`typing.Generic` is not allowed in type expressions") f.write_str("`typing.Generic` is not allowed in type expressions")
} }
InvalidTypeExpression::Deprecated => {
f.write_str("`warnings.deprecated` is not allowed in type expressions")
}
InvalidTypeExpression::TypeQualifier(qualifier) => write!( InvalidTypeExpression::TypeQualifier(qualifier) => write!(
f, f,
"Type qualifier `{qualifier}` is not allowed in type expressions \ "Type qualifier `{qualifier}` is not allowed in type expressions \
@ -6231,6 +6294,17 @@ impl<'db> InvalidTypeExpression<'db> {
} }
} }
/// Data regarding a `warnings.deprecated` or `typing_extensions.deprecated` decorator.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct DeprecatedInstance<'db> {
/// The message for the deprecation
pub message: Option<StringLiteralType<'db>>,
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for DeprecatedInstance<'_> {}
/// Whether this typecar was created via the legacy `TypeVar` constructor, or using PEP 695 syntax. /// Whether this typecar was created via the legacy `TypeVar` constructor, or using PEP 695 syntax.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum TypeVarKind { pub enum TypeVarKind {

View file

@ -848,6 +848,7 @@ impl<'db> Bindings<'db> {
class_literal.name(db), class_literal.name(db),
class_literal.body_scope(db), class_literal.body_scope(db),
class_literal.known(db), class_literal.known(db),
class_literal.deprecated(db),
Some(params), Some(params),
class_literal.dataclass_transformer_params(db), class_literal.dataclass_transformer_params(db),
))); )));

View file

@ -22,8 +22,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
use crate::types::tuple::TupleType; use crate::types::tuple::TupleType;
use crate::types::{ use crate::types::{
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams, BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
DynamicType, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeTransformer, DeprecatedInstance, DynamicType, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation,
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, infer_definition_types, TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
infer_definition_types,
}; };
use crate::{ use crate::{
Db, FxOrderSet, KnownModule, Program, Db, FxOrderSet, KnownModule, Program,
@ -799,6 +800,9 @@ pub struct ClassLiteral<'db> {
pub(crate) known: Option<KnownClass>, pub(crate) known: Option<KnownClass>,
/// If this class is deprecated, this holds the deprecation message.
pub(crate) deprecated: Option<DeprecatedInstance<'db>>,
pub(crate) dataclass_params: Option<DataclassParams>, pub(crate) dataclass_params: Option<DataclassParams>,
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>, pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>,
} }
@ -2418,6 +2422,7 @@ pub enum KnownClass {
NoneType, // Part of `types` for Python >= 3.10 NoneType, // Part of `types` for Python >= 3.10
// Typing // Typing
Any, Any,
Deprecated,
StdlibAlias, StdlibAlias,
SpecialForm, SpecialForm,
TypeVar, TypeVar,
@ -2535,6 +2540,7 @@ impl KnownClass {
| Self::NotImplementedType | Self::NotImplementedType
| Self::Staticmethod | Self::Staticmethod
| Self::Classmethod | Self::Classmethod
| Self::Deprecated
| Self::Field | Self::Field
| Self::KwOnly | Self::KwOnly
| Self::NamedTupleFallback => Truthiness::Ambiguous, | Self::NamedTupleFallback => Truthiness::Ambiguous,
@ -2562,6 +2568,7 @@ impl KnownClass {
| Self::Property | Self::Property
| Self::Staticmethod | Self::Staticmethod
| Self::Classmethod | Self::Classmethod
| Self::Deprecated
| Self::Type | Self::Type
| Self::ModuleType | Self::ModuleType
| Self::Super | Self::Super
@ -2648,6 +2655,7 @@ impl KnownClass {
| KnownClass::ExceptionGroup | KnownClass::ExceptionGroup
| KnownClass::Staticmethod | KnownClass::Staticmethod
| KnownClass::Classmethod | KnownClass::Classmethod
| KnownClass::Deprecated
| KnownClass::Super | KnownClass::Super
| KnownClass::Enum | KnownClass::Enum
| KnownClass::Auto | KnownClass::Auto
@ -2731,6 +2739,7 @@ impl KnownClass {
| Self::ExceptionGroup | Self::ExceptionGroup
| Self::Staticmethod | Self::Staticmethod
| Self::Classmethod | Self::Classmethod
| Self::Deprecated
| Self::GenericAlias | Self::GenericAlias
| Self::GeneratorType | Self::GeneratorType
| Self::AsyncGeneratorType | Self::AsyncGeneratorType
@ -2797,6 +2806,7 @@ impl KnownClass {
Self::ExceptionGroup => "ExceptionGroup", Self::ExceptionGroup => "ExceptionGroup",
Self::Staticmethod => "staticmethod", Self::Staticmethod => "staticmethod",
Self::Classmethod => "classmethod", Self::Classmethod => "classmethod",
Self::Deprecated => "deprecated",
Self::GenericAlias => "GenericAlias", Self::GenericAlias => "GenericAlias",
Self::ModuleType => "ModuleType", Self::ModuleType => "ModuleType",
Self::FunctionType => "FunctionType", Self::FunctionType => "FunctionType",
@ -3071,6 +3081,7 @@ impl KnownClass {
| Self::ParamSpec | Self::ParamSpec
| Self::ParamSpecArgs | Self::ParamSpecArgs
| Self::ParamSpecKwargs | Self::ParamSpecKwargs
| Self::Deprecated
| Self::NewType => KnownModule::TypingExtensions, | Self::NewType => KnownModule::TypingExtensions,
Self::NoDefaultType => { Self::NoDefaultType => {
let python_version = Program::get(db).python_version(db); let python_version = Program::get(db).python_version(db);
@ -3139,6 +3150,7 @@ impl KnownClass {
| Self::ExceptionGroup | Self::ExceptionGroup
| Self::Staticmethod | Self::Staticmethod
| Self::Classmethod | Self::Classmethod
| Self::Deprecated
| Self::GenericAlias | Self::GenericAlias
| Self::ModuleType | Self::ModuleType
| Self::FunctionType | Self::FunctionType
@ -3226,6 +3238,7 @@ impl KnownClass {
| Self::ExceptionGroup | Self::ExceptionGroup
| Self::Staticmethod | Self::Staticmethod
| Self::Classmethod | Self::Classmethod
| Self::Deprecated
| Self::TypeVar | Self::TypeVar
| Self::ParamSpec | Self::ParamSpec
| Self::ParamSpecArgs | Self::ParamSpecArgs
@ -3278,6 +3291,7 @@ impl KnownClass {
"ExceptionGroup" => Self::ExceptionGroup, "ExceptionGroup" => Self::ExceptionGroup,
"staticmethod" => Self::Staticmethod, "staticmethod" => Self::Staticmethod,
"classmethod" => Self::Classmethod, "classmethod" => Self::Classmethod,
"deprecated" => Self::Deprecated,
"GenericAlias" => Self::GenericAlias, "GenericAlias" => Self::GenericAlias,
"NoneType" => Self::NoneType, "NoneType" => Self::NoneType,
"ModuleType" => Self::ModuleType, "ModuleType" => Self::ModuleType,
@ -3397,6 +3411,8 @@ impl KnownClass {
| Self::NamedTuple | Self::NamedTuple
| Self::Iterable | Self::Iterable
| Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions), | Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions),
Self::Deprecated => matches!(module, KnownModule::Warnings | KnownModule::TypingExtensions),
} }
} }
@ -3481,7 +3497,32 @@ impl KnownClass {
_ => {} _ => {}
} }
} }
KnownClass::Deprecated => {
// Parsing something of the form:
//
// @deprecated("message")
// @deprecated("message", caregory = DeprecationWarning, stacklevel = 1)
//
// "Static type checker behavior is not affected by the category and stacklevel arguments"
// so we only need the message and can ignore everything else. The message is mandatory,
// must be a LiteralString, and always comes first.
//
// We aren't guaranteed to know the static value of a LiteralString, so we need to
// accept that sometimes we will fail to include the message.
//
// We don't do any serious validation/diagnostics here, as the signature for this
// is included in `Type::bindings`.
//
// See: <https://typing.python.org/en/latest/spec/directives.html#deprecated>
let [Some(message), ..] = overload.parameter_types() else {
// Checking in Type::bindings will complain about this for us
return;
};
overload.set_return_type(Type::KnownInstance(KnownInstanceType::Deprecated(
DeprecatedInstance::new(db, message.into_string_literal()),
)));
}
KnownClass::TypeVar => { KnownClass::TypeVar => {
let assigned_to = index let assigned_to = index
.try_expression(ast::ExprRef::from(call_expression)) .try_expression(ast::ExprRef::from(call_expression))

View file

@ -163,7 +163,9 @@ impl<'db> ClassBase<'db> {
Type::KnownInstance(known_instance) => match known_instance { Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::SubscriptedGeneric(_) => Some(Self::Generic), KnownInstanceType::SubscriptedGeneric(_) => Some(Self::Generic),
KnownInstanceType::SubscriptedProtocol(_) => Some(Self::Protocol), KnownInstanceType::SubscriptedProtocol(_) => Some(Self::Protocol),
KnownInstanceType::TypeAliasType(_) | KnownInstanceType::TypeVar(_) => None, KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::TypeVar(_)
| KnownInstanceType::Deprecated(_) => None,
}, },
Type::SpecialForm(special_form) => match special_form { Type::SpecialForm(special_form) => match special_form {

View file

@ -288,7 +288,6 @@ impl LintDiagnosticGuard<'_, '_> {
/// ///
/// Callers can add additional primary or secondary annotations via the /// Callers can add additional primary or secondary annotations via the
/// `DerefMut` trait implementation to a `Diagnostic`. /// `DerefMut` trait implementation to a `Diagnostic`.
#[expect(dead_code)]
pub(super) fn add_primary_tag(&mut self, tag: DiagnosticTag) { pub(super) fn add_primary_tag(&mut self, tag: DiagnosticTag) {
let ann = self.primary_annotation_mut().unwrap(); let ann = self.primary_annotation_mut().unwrap();
ann.push_tag(tag); ann.push_tag(tag);

View file

@ -34,6 +34,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&CONFLICTING_DECLARATIONS); registry.register_lint(&CONFLICTING_DECLARATIONS);
registry.register_lint(&CONFLICTING_METACLASS); registry.register_lint(&CONFLICTING_METACLASS);
registry.register_lint(&CYCLIC_CLASS_DEFINITION); registry.register_lint(&CYCLIC_CLASS_DEFINITION);
registry.register_lint(&DEPRECATED);
registry.register_lint(&DIVISION_BY_ZERO); registry.register_lint(&DIVISION_BY_ZERO);
registry.register_lint(&DUPLICATE_BASE); registry.register_lint(&DUPLICATE_BASE);
registry.register_lint(&DUPLICATE_KW_ONLY); registry.register_lint(&DUPLICATE_KW_ONLY);
@ -261,6 +262,27 @@ declare_lint! {
} }
} }
declare_lint! {
/// ## What it does
/// Checks for uses of deprecated items
///
/// ## Why is this bad?
/// Deprecated items should no longer be used.
///
/// ## Examples
/// ```python
/// @warnings.deprecated("use new_func instead")
/// def old_func(): ...
///
/// old_func() # emits [deprecated] diagnostic
/// ```
pub(crate) static DEPRECATED = {
summary: "detects uses of deprecated items",
status: LintStatus::preview("1.0.0"),
default_level: Level::Warn,
}
}
declare_lint! { declare_lint! {
/// ## What it does /// ## What it does
/// Checks for class definitions with duplicate bases. /// Checks for class definitions with duplicate bases.

View file

@ -76,8 +76,8 @@ use crate::types::narrow::ClassInfoConstraintFunction;
use crate::types::signatures::{CallableSignature, Signature}; use crate::types::signatures::{CallableSignature, Signature};
use crate::types::visitor::any_over_type; use crate::types::visitor::any_over_type;
use crate::types::{ use crate::types::{
BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation, BoundMethodType, CallableType, DeprecatedInstance, DynamicType, KnownClass, Type, TypeMapping,
TypeTransformer, TypeVarInstance, UnionBuilder, walk_type_mapping, TypeRelation, TypeTransformer, TypeVarInstance, UnionBuilder, walk_type_mapping,
}; };
use crate::{Db, FxOrderSet, ModuleName, resolve_module}; use crate::{Db, FxOrderSet, ModuleName, resolve_module};
@ -199,6 +199,9 @@ pub struct OverloadLiteral<'db> {
/// A set of special decorators that were applied to this function /// A set of special decorators that were applied to this function
pub(crate) decorators: FunctionDecorators, pub(crate) decorators: FunctionDecorators,
/// If `Some` then contains the `@warnings.deprecated`
pub(crate) deprecated: Option<DeprecatedInstance<'db>>,
/// The arguments to `dataclass_transformer`, if this function was annotated /// The arguments to `dataclass_transformer`, if this function was annotated
/// with `@dataclass_transformer(...)`. /// with `@dataclass_transformer(...)`.
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>, pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>,
@ -220,6 +223,7 @@ impl<'db> OverloadLiteral<'db> {
self.known(db), self.known(db),
self.body_scope(db), self.body_scope(db),
self.decorators(db), self.decorators(db),
self.deprecated(db),
Some(params), Some(params),
) )
} }
@ -465,6 +469,14 @@ impl<'db> FunctionLiteral<'db> {
.any(|overload| overload.decorators(db).contains(decorator)) .any(|overload| overload.decorators(db).contains(decorator))
} }
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
///
/// Checking if an overload is deprecated requires deeper call analysis.
fn implementation_deprecated(self, db: &'db dyn Db) -> Option<DeprecatedInstance<'db>> {
let (_overloads, implementation) = self.overloads_and_implementation(db);
implementation.and_then(|overload| overload.deprecated(db))
}
fn definition(self, db: &'db dyn Db) -> Definition<'db> { fn definition(self, db: &'db dyn Db) -> Definition<'db> {
self.last_definition(db).definition(db) self.last_definition(db).definition(db)
} }
@ -672,6 +684,16 @@ impl<'db> FunctionType<'db> {
self.literal(db).has_known_decorator(db, decorator) self.literal(db).has_known_decorator(db, decorator)
} }
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
///
/// Checking if an overload is deprecated requires deeper call analysis.
pub(crate) fn implementation_deprecated(
self,
db: &'db dyn Db,
) -> Option<DeprecatedInstance<'db>> {
self.literal(db).implementation_deprecated(db)
}
/// Returns the [`Definition`] of the implementation or first overload of this function. /// Returns the [`Definition`] of the implementation or first overload of this function.
/// ///
/// ## Warning /// ## Warning

View file

@ -2298,6 +2298,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let mut decorator_types_and_nodes = Vec::with_capacity(decorator_list.len()); let mut decorator_types_and_nodes = Vec::with_capacity(decorator_list.len());
let mut function_decorators = FunctionDecorators::empty(); let mut function_decorators = FunctionDecorators::empty();
let mut deprecated = None;
let mut dataclass_transformer_params = None; let mut dataclass_transformer_params = None;
for decorator in decorator_list { for decorator in decorator_list {
@ -2315,6 +2316,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
continue; continue;
} }
} }
Type::KnownInstance(KnownInstanceType::Deprecated(deprecated_inst)) => {
deprecated = Some(deprecated_inst);
}
Type::DataclassTransformer(params) => { Type::DataclassTransformer(params) => {
dataclass_transformer_params = Some(params); dataclass_transformer_params = Some(params);
} }
@ -2362,6 +2366,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
known_function, known_function,
body_scope, body_scope,
function_decorators, function_decorators,
deprecated,
dataclass_transformer_params, dataclass_transformer_params,
); );
@ -2624,6 +2629,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
body: _, body: _,
} = class_node; } = class_node;
let mut deprecated = None;
let mut dataclass_params = None; let mut dataclass_params = None;
let mut dataclass_transformer_params = None; let mut dataclass_transformer_params = None;
for decorator in decorator_list { for decorator in decorator_list {
@ -2641,6 +2647,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
continue; continue;
} }
if let Type::KnownInstance(KnownInstanceType::Deprecated(deprecated_inst)) =
decorator_ty
{
deprecated = Some(deprecated_inst);
continue;
}
if let Type::FunctionLiteral(f) = decorator_ty { if let Type::FunctionLiteral(f) = decorator_ty {
// We do not yet detect or flag `@dataclass_transform` applied to more than one // We do not yet detect or flag `@dataclass_transform` applied to more than one
// overload, or an overload and the implementation both. Nevertheless, this is not // overload, or an overload and the implementation both. Nevertheless, this is not
@ -2673,6 +2686,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
name.id.clone(), name.id.clone(),
body_scope, body_scope,
maybe_known_class, maybe_known_class,
deprecated,
dataclass_params, dataclass_params,
dataclass_transformer_params, dataclass_transformer_params,
)); ));
@ -4358,7 +4372,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
for alias in names { for alias in names {
for definition in self.index.definitions(alias) { for definition in self.index.definitions(alias) {
self.extend(infer_definition_types(self.db(), *definition)); let inferred = infer_definition_types(self.db(), *definition);
// Check non-star imports for deprecations
if definition.kind(self.db()).as_star_import().is_none() {
for ty in inferred.declarations.values() {
self.check_deprecated(alias, ty.inner);
}
}
self.extend(inferred);
} }
} }
} }
@ -5562,6 +5583,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| KnownClass::NamedTuple | KnownClass::NamedTuple
| KnownClass::TypeAliasType | KnownClass::TypeAliasType
| KnownClass::Tuple | KnownClass::Tuple
| KnownClass::Deprecated
) )
) )
// temporary special-casing for all subclasses of `enum.Enum` // temporary special-casing for all subclasses of `enum.Enum`
@ -5799,6 +5821,62 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
ty ty
} }
/// Check if the given ty is `@deprecated` or not
fn check_deprecated<T: Ranged>(&self, ranged: T, ty: Type) {
// First handle classes
if let Type::ClassLiteral(class_literal) = ty {
let Some(deprecated) = class_literal.deprecated(self.db()) else {
return;
};
let Some(builder) = self
.context
.report_lint(&crate::types::diagnostic::DEPRECATED, ranged)
else {
return;
};
let class_name = class_literal.name(self.db());
let mut diag =
builder.into_diagnostic(format_args!(r#"The class `{class_name}` is deprecated"#));
if let Some(message) = deprecated.message(self.db()) {
diag.set_primary_message(message.value(self.db()));
}
diag.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
return;
}
// Next handle functions
let function = match ty {
Type::FunctionLiteral(function) => function,
Type::BoundMethod(bound) => bound.function(self.db()),
_ => return,
};
// Currently we only check the final implementation for deprecation, as
// that check can be done on any reference to the function. Analysis of
// deprecated overloads needs to be done in places where we resolve the
// actual overloads being used.
let Some(deprecated) = function.implementation_deprecated(self.db()) else {
return;
};
let Some(builder) = self
.context
.report_lint(&crate::types::diagnostic::DEPRECATED, ranged)
else {
return;
};
let func_name = function.name(self.db());
let mut diag =
builder.into_diagnostic(format_args!(r#"The function `{func_name}` is deprecated"#));
if let Some(message) = deprecated.message(self.db()) {
diag.set_primary_message(message.value(self.db()));
}
diag.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
}
fn infer_name_load(&mut self, name_node: &ast::ExprName) -> Type<'db> { fn infer_name_load(&mut self, name_node: &ast::ExprName) -> Type<'db> {
let ast::ExprName { let ast::ExprName {
range: _, range: _,
@ -5811,6 +5889,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let (resolved, constraint_keys) = let (resolved, constraint_keys) =
self.infer_place_load(&expr, ast::ExprRef::Name(name_node)); self.infer_place_load(&expr, ast::ExprRef::Name(name_node));
resolved resolved
// Not found in the module's explicitly declared global symbols? // Not found in the module's explicitly declared global symbols?
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc. // Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
@ -5892,6 +5971,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let use_id = expr_ref.scoped_use_id(db, scope); let use_id = expr_ref.scoped_use_id(db, scope);
let place = place_from_bindings(db, use_def.bindings_at_use(use_id)); let place = place_from_bindings(db, use_def.bindings_at_use(use_id));
(place, Some(use_id)) (place, Some(use_id))
} }
} }
@ -6165,6 +6245,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}) })
}); });
if let Some(ty) = place.place.ignore_possibly_unbound() {
self.check_deprecated(expr_ref, ty);
}
(place, constraint_keys) (place, constraint_keys)
} }
@ -6368,6 +6452,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
}) })
.inner_type(); .inner_type();
self.check_deprecated(attr, resolved_type);
// Even if we can obtain the attribute type based on the assignments, we still perform default type inference // Even if we can obtain the attribute type based on the assignments, we still perform default type inference
// (to report errors). // (to report errors).
assigned_type.unwrap_or(resolved_type) assigned_type.unwrap_or(resolved_type)
@ -9294,6 +9381,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
} }
Type::unknown() Type::unknown()
} }
KnownInstanceType::Deprecated(_) => {
self.infer_type_expression(&subscript.slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
"`warnings.deprecated` is not allowed in type expressions",
));
}
Type::unknown()
}
KnownInstanceType::TypeVar(_) => { KnownInstanceType::TypeVar(_) => {
self.infer_type_expression(&subscript.slice); self.infer_type_expression(&subscript.slice);
todo_type!("TypeVar annotations") todo_type!("TypeVar annotations")

10
ty.schema.json generated
View file

@ -331,6 +331,16 @@
} }
] ]
}, },
"deprecated": {
"title": "detects uses of deprecated items",
"description": "## What it does\nChecks for uses of deprecated items\n\n## Why is this bad?\nDeprecated items should no longer be used.\n\n## Examples\n```python\n@warnings.deprecated(\"use new_func instead\")\ndef old_func(): ...\n\nold_func() # emits [deprecated] diagnostic\n```",
"default": "warn",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"division-by-zero": { "division-by-zero": {
"title": "detects division by zero", "title": "detects division by zero",
"description": "## What it does\nIt detects division by zero.\n\n## Why is this bad?\nDividing by zero raises a `ZeroDivisionError` at runtime.\n\n## Examples\n```python\n5 / 0\n```", "description": "## What it does\nIt detects division by zero.\n\n## Why is this bad?\nDividing by zero raises a `ZeroDivisionError` at runtime.\n\n## Examples\n```python\n5 / 0\n```",