Compare commits
23 Commits
alex/proto
...
david/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e52fa2fab | ||
|
|
bbfcf6e111 | ||
|
|
5518c84ab3 | ||
|
|
c71ce006c4 | ||
|
|
2f3bf03d82 | ||
|
|
24a6d247f1 | ||
|
|
88f7644551 | ||
|
|
6be46033b9 | ||
|
|
9e292f1522 | ||
|
|
74b60212dc | ||
|
|
bfe501b802 | ||
|
|
6f2b874d6c | ||
|
|
17dc2e4d80 | ||
|
|
694e7ed52e | ||
|
|
fe953e5c5c | ||
|
|
0bf5d2a204 | ||
|
|
8eb8d25565 | ||
|
|
9b1b58a451 | ||
|
|
fa7798ddd9 | ||
|
|
8223fea062 | ||
|
|
5a608f7366 | ||
|
|
c2d7c673ca | ||
|
|
8a6db4f257 |
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@@ -250,7 +250,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
with:
|
||||
@@ -308,7 +308,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
with:
|
||||
@@ -393,7 +393,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Build"
|
||||
run: cargo build --release --locked
|
||||
|
||||
@@ -418,7 +418,7 @@ jobs:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: rustup default "${MSRV}"
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Build tests"
|
||||
shell: bash
|
||||
env:
|
||||
@@ -441,7 +441,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
|
||||
uses: cargo-bins/cargo-binstall@2bb61346d075e720d4c3da92f23b6d612d5a7543 # v1.15.3
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -694,7 +694,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
|
||||
- uses: cargo-bins/cargo-binstall@2bb61346d075e720d4c3da92f23b6d612d5a7543 # v1.15.3
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -923,7 +923,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
uses: CodSpeedHQ/action@76578c2a7ddd928664caa737f0e962e3085d4e7c # v3.8.1
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
@@ -956,7 +956,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
uses: CodSpeedHQ/action@76578c2a7ddd928664caa737f0e962e3085d4e7c # v3.8.1
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- name: Build ruff
|
||||
# A debug build means the script runs slower once it gets started,
|
||||
|
||||
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -322,9 +322,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0"
|
||||
checksum = "dd0b03af37dad7a14518b7691d81acb0f8222604ad3d1b02f6b4bed5188c0cd5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -408,9 +408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.45"
|
||||
version = "4.5.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
|
||||
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -418,9 +418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.44"
|
||||
version = "4.5.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
|
||||
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -955,7 +955,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1035,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1795,9 +1795,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.43"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d"
|
||||
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -1949,9 +1949,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.47"
|
||||
version = "0.1.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40"
|
||||
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8"
|
||||
dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
@@ -3434,7 +3434,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -30,8 +30,6 @@ bitflags! {
|
||||
const SHOW_VIOLATIONS = 1 << 0;
|
||||
/// Whether to show a summary of the fixed violations when emitting diagnostics.
|
||||
const SHOW_FIX_SUMMARY = 1 << 1;
|
||||
/// Whether to show a diff of each fixed violation when emitting diagnostics.
|
||||
const SHOW_FIX_DIFF = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,9 +258,9 @@ impl Printer {
|
||||
OutputFormat::Concise | OutputFormat::Full => {
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
||||
.with_show_fix_diff(self.format == OutputFormat::Full && preview)
|
||||
.with_show_source(self.format == OutputFormat::Full)
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability())
|
||||
.with_preview(preview)
|
||||
.emit(writer, &diagnostics.inner, &context)?;
|
||||
|
||||
@@ -464,7 +462,7 @@ impl Printer {
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_show_source(preview)
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability())
|
||||
.emit(writer, &diagnostics.inner, &context)?;
|
||||
}
|
||||
writer.flush()?;
|
||||
|
||||
@@ -5830,3 +5830,33 @@ nested_optional: Optional[Optional[Optional[str]]] = None
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_fixes_in_full_output_with_preview_enabled() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--no-cache", "--output-format", "full"])
|
||||
.args(["--select", "F401"])
|
||||
.arg("--preview")
|
||||
.arg("-")
|
||||
.pass_stdin("import math"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
F401 [*] `math` imported but unused
|
||||
--> -:1:8
|
||||
|
|
||||
1 | import math
|
||||
| ^^^^
|
||||
|
|
||||
help: Remove unused import: `math`
|
||||
- import math
|
||||
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -349,6 +349,13 @@ impl Diagnostic {
|
||||
self.fix().is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the diagnostic is [`fixable`](Diagnostic::fixable) and applies at the
|
||||
/// configured applicability level.
|
||||
pub fn has_applicable_fix(&self, config: &DisplayDiagnosticConfig) -> bool {
|
||||
self.fix()
|
||||
.is_some_and(|fix| fix.applies(config.fix_applicability))
|
||||
}
|
||||
|
||||
/// Returns the offset of the parent statement for this diagnostic if it exists.
|
||||
///
|
||||
/// This is primarily used for checking noqa/secondary code suppressions.
|
||||
|
||||
@@ -254,9 +254,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
id,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
is_fixable: diag
|
||||
.fix()
|
||||
.is_some_and(|fix| fix.applies(config.fix_applicability)),
|
||||
is_fixable: diag.has_applicable_fix(config),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,11 +77,9 @@ impl<'a> ConciseRenderer<'a> {
|
||||
)?;
|
||||
}
|
||||
if self.config.show_fix_status {
|
||||
if let Some(fix) = diag.fix() {
|
||||
// Do not display an indicator for inapplicable fixes
|
||||
if fix.applies(self.config.fix_applicability) {
|
||||
write!(f, "[{fix}] ", fix = fmt_styled("*", stylesheet.separator))?;
|
||||
}
|
||||
// Do not display an indicator for inapplicable fixes
|
||||
if diag.has_applicable_fix(self.config) {
|
||||
write!(f, "[{fix}] ", fix = fmt_styled("*", stylesheet.separator))?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -58,7 +58,7 @@ impl<'a> FullRenderer<'a> {
|
||||
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
|
||||
}
|
||||
|
||||
if self.config.show_fix_diff {
|
||||
if self.config.show_fix_diff && diag.has_applicable_fix(self.config) {
|
||||
if let Some(diff) = Diff::from_diagnostic(diag, &stylesheet, self.resolver) {
|
||||
write!(f, "{diff}")?;
|
||||
}
|
||||
@@ -697,6 +697,8 @@ print()
|
||||
fn notebook_output_with_diff() {
|
||||
let (mut env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
|
||||
env.show_fix_diff(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
@@ -726,7 +728,7 @@ print()
|
||||
2 |
|
||||
3 | print('hello world')
|
||||
|
||||
error[unused-variable]: Local variable `x` is assigned to but never used
|
||||
error[unused-variable][*]: Local variable `x` is assigned to but never used
|
||||
--> notebook.ipynb:cell 3:4:5
|
||||
|
|
||||
2 | def foo():
|
||||
@@ -749,6 +751,7 @@ print()
|
||||
fn notebook_output_with_diff_spanning_cells() {
|
||||
let (mut env, mut diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
|
||||
env.show_fix_diff(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
|
||||
// Move all of the edits from the later diagnostics to the first diagnostic to simulate a
|
||||
// single diagnostic with edits in different cells.
|
||||
@@ -761,7 +764,7 @@ print()
|
||||
*fix = Fix::unsafe_edits(edits.remove(0), edits);
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[unused-import]: `os` imported but unused
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
@@ -924,6 +927,7 @@ line 10
|
||||
env.add("example.py", contents);
|
||||
env.format(DiagnosticFormat::Full);
|
||||
env.show_fix_diff(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
|
||||
let mut diagnostic = env.err().primary("example.py", "3", "3", "label").build();
|
||||
diagnostic.help("Start of diff:");
|
||||
@@ -936,7 +940,7 @@ line 10
|
||||
)));
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
error[test-diagnostic][*]: main diagnostic message
|
||||
--> example.py:3:1
|
||||
|
|
||||
1 | line 1
|
||||
|
||||
@@ -23,3 +23,11 @@ def test_error():
|
||||
assert list([])
|
||||
assert set(set())
|
||||
assert tuple("")
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19935
|
||||
def test_all_ok():
|
||||
assert f"{b""}"
|
||||
assert f"{""=}"
|
||||
assert f"{""!a}"
|
||||
assert f"{""!r}"
|
||||
assert f"{"":1}"
|
||||
|
||||
@@ -110,3 +110,9 @@ deque(t"{""}") # OK
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20050
|
||||
deque(f"{""}") # RUF037
|
||||
|
||||
deque(f"{b""}")
|
||||
deque(f"{""=}")
|
||||
deque(f"{""!a}")
|
||||
deque(f"{""!r}")
|
||||
deque(f"{"":1}")
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::io::Write;
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
|
||||
};
|
||||
use ruff_diagnostics::Applicability;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
pub struct TextEmitter {
|
||||
config: DisplayDiagnosticConfig,
|
||||
@@ -46,10 +46,8 @@ impl TextEmitter {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
|
||||
self.config = self
|
||||
.config
|
||||
.fix_applicability(unsafe_fixes.required_applicability());
|
||||
pub fn with_fix_applicability(mut self, applicability: Applicability) -> Self {
|
||||
self.config = self.config.fix_applicability(applicability);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -86,13 +84,13 @@ impl Emitter for TextEmitter {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
use ruff_diagnostics::Applicability;
|
||||
|
||||
use crate::message::TextEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_diagnostics,
|
||||
create_notebook_diagnostics, create_syntax_error_diagnostics,
|
||||
};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
@@ -117,7 +115,7 @@ mod tests {
|
||||
let mut emitter = TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled);
|
||||
.with_fix_applicability(Applicability::Unsafe);
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
@@ -128,7 +126,7 @@ mod tests {
|
||||
let mut emitter = TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled);
|
||||
.with_fix_applicability(Applicability::Unsafe);
|
||||
let (messages, notebook_indexes) = create_notebook_diagnostics();
|
||||
let content = capture_emitter_notebook_output(&mut emitter, &messages, ¬ebook_indexes);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/eradicate/mod.rs
|
||||
---
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:1:1
|
||||
|
|
||||
1 | #import os
|
||||
@@ -16,7 +16,7 @@ help: Remove commented-out code
|
||||
3 | a = 4
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:2:1
|
||||
|
|
||||
1 | #import os
|
||||
@@ -33,7 +33,7 @@ help: Remove commented-out code
|
||||
4 | #foo(1, 2, 3)
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:3:1
|
||||
|
|
||||
1 | #import os
|
||||
@@ -52,7 +52,7 @@ help: Remove commented-out code
|
||||
5 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:5:1
|
||||
|
|
||||
3 | #a = 3
|
||||
@@ -72,7 +72,7 @@ help: Remove commented-out code
|
||||
7 | content = 1 # print('hello')
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:13:5
|
||||
|
|
||||
11 | # This is a real comment.
|
||||
@@ -91,7 +91,7 @@ help: Remove commented-out code
|
||||
15 | #import os # noqa: ERA001
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:21:5
|
||||
|
|
||||
19 | class A():
|
||||
@@ -109,7 +109,7 @@ help: Remove commented-out code
|
||||
23 | dictionary = {
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:26:5
|
||||
|
|
||||
24 | dictionary = {
|
||||
@@ -129,7 +129,7 @@ help: Remove commented-out code
|
||||
28 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:27:5
|
||||
|
|
||||
25 | # "key1": 123, # noqa: ERA001
|
||||
@@ -148,7 +148,7 @@ help: Remove commented-out code
|
||||
29 | #import os # noqa
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:32:1
|
||||
|
|
||||
30 | #import os # noqa
|
||||
@@ -168,7 +168,7 @@ help: Remove commented-out code
|
||||
34 | # try: print()
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:33:1
|
||||
|
|
||||
32 | # case 1:
|
||||
@@ -187,7 +187,7 @@ help: Remove commented-out code
|
||||
35 | # except:
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:34:1
|
||||
|
|
||||
32 | # case 1:
|
||||
@@ -207,7 +207,7 @@ help: Remove commented-out code
|
||||
36 | # except Foo:
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:35:1
|
||||
|
|
||||
33 | # try:
|
||||
@@ -227,7 +227,7 @@ help: Remove commented-out code
|
||||
37 | # except Exception as e: print(e)
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:36:1
|
||||
|
|
||||
34 | # try: # with comment
|
||||
@@ -247,7 +247,7 @@ help: Remove commented-out code
|
||||
38 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:37:1
|
||||
|
|
||||
35 | # try: print()
|
||||
@@ -266,7 +266,7 @@ help: Remove commented-out code
|
||||
39 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:38:1
|
||||
|
|
||||
36 | # except:
|
||||
@@ -284,7 +284,7 @@ help: Remove commented-out code
|
||||
40 | # Script tag without an opening tag (Error)
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:44:1
|
||||
|
|
||||
43 | # requires-python = ">=3.11"
|
||||
@@ -303,7 +303,7 @@ help: Remove commented-out code
|
||||
46 | # ]
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:47:1
|
||||
|
|
||||
45 | # "requests<3",
|
||||
@@ -322,7 +322,7 @@ help: Remove commented-out code
|
||||
49 | # Script tag (OK)
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:75:1
|
||||
|
|
||||
73 | # /// script
|
||||
@@ -342,7 +342,7 @@ help: Remove commented-out code
|
||||
77 | # ]
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> ERA001.py:78:1
|
||||
|
|
||||
76 | # "requests<3",
|
||||
|
||||
@@ -277,7 +277,7 @@ help: Use `Self` as return type
|
||||
334 | def __new__(cls: type[Generic1]) -> Generic1: ...
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
--> PYI034.py:334:9
|
||||
|
|
||||
333 | class Generic1[T](list):
|
||||
@@ -296,7 +296,7 @@ help: Use `Self` as return type
|
||||
337 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
--> PYI034.py:335:9
|
||||
|
|
||||
333 | class Generic1[T](list):
|
||||
@@ -315,7 +315,7 @@ help: Use `Self` as return type
|
||||
338 | ### Correctness of typevar-likes are not verified.
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
--> PYI034.py:345:9
|
||||
|
|
||||
344 | class Generic2(Generic[T]):
|
||||
@@ -334,7 +334,7 @@ help: Use `Self` as return type
|
||||
348 | class Generic3(tuple[*Ts]):
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
--> PYI034.py:346:9
|
||||
|
|
||||
344 | class Generic2(Generic[T]):
|
||||
@@ -355,7 +355,7 @@ help: Use `Self` as return type
|
||||
349 | def __new__(cls: type[Generic3]) -> Generic3: ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
--> PYI034.py:349:9
|
||||
|
|
||||
348 | class Generic3(tuple[*Ts]):
|
||||
@@ -374,7 +374,7 @@ help: Use `Self` as return type
|
||||
352 | class Generic4(collections.abc.Callable[P, ...]):
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
--> PYI034.py:350:9
|
||||
|
|
||||
348 | class Generic3(tuple[*Ts]):
|
||||
@@ -395,7 +395,7 @@ help: Use `Self` as return type
|
||||
353 | def __new__(cls: type[Generic4]) -> Generic4: ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
--> PYI034.py:353:9
|
||||
|
|
||||
352 | class Generic4(collections.abc.Callable[P, ...]):
|
||||
@@ -414,7 +414,7 @@ help: Use `Self` as return type
|
||||
356 | from some_module import PotentialTypeVar
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
--> PYI034.py:354:9
|
||||
|
|
||||
352 | class Generic4(collections.abc.Callable[P, ...]):
|
||||
|
||||
@@ -258,7 +258,7 @@ help: Use `Self` as return type
|
||||
228 | def __new__(cls: type[Generic1]) -> Generic1: ...
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
--> PYI034.pyi:228:9
|
||||
|
|
||||
227 | class Generic1[T](list):
|
||||
@@ -277,7 +277,7 @@ help: Use `Self` as return type
|
||||
231 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic1` usually return `self` at runtime
|
||||
--> PYI034.pyi:229:9
|
||||
|
|
||||
227 | class Generic1[T](list):
|
||||
@@ -296,7 +296,7 @@ help: Use `Self` as return type
|
||||
232 | ### Correctness of typevar-likes are not verified.
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
--> PYI034.pyi:239:9
|
||||
|
|
||||
238 | class Generic2(Generic[T]):
|
||||
@@ -315,7 +315,7 @@ help: Use `Self` as return type
|
||||
242 | class Generic3(tuple[*Ts]):
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic2` usually return `self` at runtime
|
||||
--> PYI034.pyi:240:9
|
||||
|
|
||||
238 | class Generic2(Generic[T]):
|
||||
@@ -336,7 +336,7 @@ help: Use `Self` as return type
|
||||
243 | def __new__(cls: type[Generic3]) -> Generic3: ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
--> PYI034.pyi:243:9
|
||||
|
|
||||
242 | class Generic3(tuple[*Ts]):
|
||||
@@ -355,7 +355,7 @@ help: Use `Self` as return type
|
||||
246 | class Generic4(collections.abc.Callable[P, ...]):
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic3` usually return `self` at runtime
|
||||
--> PYI034.pyi:244:9
|
||||
|
|
||||
242 | class Generic3(tuple[*Ts]):
|
||||
@@ -376,7 +376,7 @@ help: Use `Self` as return type
|
||||
247 | def __new__(cls: type[Generic4]) -> Generic4: ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
--> PYI034.pyi:247:9
|
||||
|
|
||||
246 | class Generic4(collections.abc.Callable[P, ...]):
|
||||
@@ -395,7 +395,7 @@ help: Use `Self` as return type
|
||||
250 | from some_module import PotentialTypeVar
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic4` usually return `self` at runtime
|
||||
--> PYI034.pyi:248:9
|
||||
|
|
||||
246 | class Generic4(collections.abc.Callable[P, ...]):
|
||||
@@ -416,7 +416,7 @@ help: Use `Self` as return type
|
||||
251 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__new__` methods in classes like `Generic5` usually return `self` at runtime
|
||||
PYI034 [*] `__new__` methods in classes like `Generic5` usually return `self` at runtime
|
||||
--> PYI034.pyi:253:9
|
||||
|
|
||||
252 | class Generic5(list[PotentialTypeVar]):
|
||||
@@ -433,7 +433,7 @@ help: Use `Self` as return type
|
||||
254 | def __enter__(self: Generic5) -> Generic5: ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PYI034 `__enter__` methods in classes like `Generic5` usually return `self` at runtime
|
||||
PYI034 [*] `__enter__` methods in classes like `Generic5` usually return `self` at runtime
|
||||
--> PYI034.pyi:254:9
|
||||
|
|
||||
252 | class Generic5(list[PotentialTypeVar]):
|
||||
|
||||
@@ -182,4 +182,6 @@ PT015 Assertion always fails, replace with `pytest.fail()`
|
||||
24 | assert set(set())
|
||||
25 | assert tuple("")
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
26 |
|
||||
27 | # https://github.com/astral-sh/ruff/issues/19935
|
||||
|
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
|
||||
---
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:3:16
|
||||
|
|
||||
1 | # Errors
|
||||
@@ -21,7 +21,7 @@ help: Remove default argument
|
||||
6 | def test_foo(a: int=1): ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:4:18
|
||||
|
|
||||
3 | def test_foo(a=1): ...
|
||||
@@ -41,7 +41,7 @@ help: Remove default argument
|
||||
7 | def test_foo(a: int = 1): ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:5:19
|
||||
|
|
||||
3 | def test_foo(a=1): ...
|
||||
@@ -62,7 +62,7 @@ help: Remove default argument
|
||||
8 | def test_foo(a: (int) = 1): ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:6:21
|
||||
|
|
||||
4 | def test_foo(a = 1): ...
|
||||
@@ -83,7 +83,7 @@ help: Remove default argument
|
||||
9 | def test_foo(a: int = (1)): ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:7:23
|
||||
|
|
||||
5 | def test_foo(a = (1)): ...
|
||||
@@ -104,7 +104,7 @@ help: Remove default argument
|
||||
10 | def test_foo(a: (int) = (1)): ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:8:25
|
||||
|
|
||||
6 | def test_foo(a: int=1): ...
|
||||
@@ -125,7 +125,7 @@ help: Remove default argument
|
||||
11 | def test_foo(a=1, /, b=2, *, c=3): ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:9:24
|
||||
|
|
||||
7 | def test_foo(a: int = 1): ...
|
||||
@@ -146,7 +146,7 @@ help: Remove default argument
|
||||
12 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:10:26
|
||||
|
|
||||
8 | def test_foo(a: (int) = 1): ...
|
||||
@@ -166,7 +166,7 @@ help: Remove default argument
|
||||
13 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> PT028.py:11:16
|
||||
|
|
||||
9 | def test_foo(a: int = (1)): ...
|
||||
@@ -185,7 +185,7 @@ help: Remove default argument
|
||||
14 | # No errors
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `b` has default argument
|
||||
PT028 [*] Test function parameter `b` has default argument
|
||||
--> PT028.py:11:24
|
||||
|
|
||||
9 | def test_foo(a: int = (1)): ...
|
||||
@@ -204,7 +204,7 @@ help: Remove default argument
|
||||
14 | # No errors
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `c` has default argument
|
||||
PT028 [*] Test function parameter `c` has default argument
|
||||
--> PT028.py:11:32
|
||||
|
|
||||
9 | def test_foo(a: int = (1)): ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
|
||||
---
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> is_pytest_test.py:3:27
|
||||
|
|
||||
1 | # Errors
|
||||
@@ -20,7 +20,7 @@ help: Remove default argument
|
||||
6 | class TestClass:
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> is_pytest_test.py:4:27
|
||||
|
|
||||
3 | def test_this_is_a_test(a=1): ...
|
||||
@@ -40,7 +40,7 @@ help: Remove default argument
|
||||
7 | def test_this_too_is_a_test(self, a=1): ...
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> is_pytest_test.py:7:41
|
||||
|
|
||||
6 | class TestClass:
|
||||
@@ -59,7 +59,7 @@ help: Remove default argument
|
||||
10 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
PT028 Test function parameter `a` has default argument
|
||||
PT028 [*] Test function parameter `a` has default argument
|
||||
--> is_pytest_test.py:8:37
|
||||
|
|
||||
6 | class TestClass:
|
||||
|
||||
@@ -120,7 +120,7 @@ help: Rewrite `f` as a `def`
|
||||
61 | class Scope:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
E731 Do not assign a `lambda` expression, use a `def`
|
||||
E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
--> E731.py:73:9
|
||||
|
|
||||
71 | x: Callable[[int], int]
|
||||
@@ -142,7 +142,7 @@ help: Rewrite `x` as a `def`
|
||||
77 | return x
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
E731 Do not assign a `lambda` expression, use a `def`
|
||||
E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
--> E731.py:75:9
|
||||
|
|
||||
73 | x = lambda: 1
|
||||
|
||||
@@ -27,6 +27,21 @@ use crate::{AlwaysFixableViolation, Fix};
|
||||
/// str(1)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This fix is marked as unsafe because removing the import
|
||||
/// may change program behavior. For example, in the following
|
||||
/// situation:
|
||||
///
|
||||
/// ```python
|
||||
/// def str(x):
|
||||
/// return x
|
||||
///
|
||||
///
|
||||
/// from builtins import str
|
||||
///
|
||||
/// str(1) # `"1"` with the import, `1` without
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The Python Standard Library](https://docs.python.org/3/library/index.html)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -262,7 +262,7 @@ UP049 Generic class uses private type parameters
|
||||
|
|
||||
help: Rename type parameter to remove leading underscores
|
||||
|
||||
UP049 Generic class uses private type parameters
|
||||
UP049 [*] Generic class uses private type parameters
|
||||
--> UP049_1.py:71:9
|
||||
|
|
||||
71 | class C[_T]:
|
||||
@@ -295,7 +295,7 @@ help: Rename type parameter to remove leading underscores
|
||||
82 | class C[_T]:
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
UP049 Generic class uses private type parameters
|
||||
UP049 [*] Generic class uses private type parameters
|
||||
--> UP049_1.py:82:9
|
||||
|
|
||||
82 | class C[_T]:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||
---
|
||||
FURB116 Replace `oct` call with `f"{num:o}"`
|
||||
FURB116 [*] Replace `oct` call with `f"{num:o}"`
|
||||
--> FURB116.py:9:7
|
||||
|
|
||||
7 | return num
|
||||
@@ -22,7 +22,7 @@ help: Replace with `f"{num:o}"`
|
||||
12 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `hex` call with `f"{num:x}"`
|
||||
FURB116 [*] Replace `hex` call with `f"{num:x}"`
|
||||
--> FURB116.py:10:7
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
@@ -41,7 +41,7 @@ help: Replace with `f"{num:x}"`
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `bin` call with `f"{num:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{num:b}"`
|
||||
--> FURB116.py:11:7
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
@@ -162,7 +162,7 @@ FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
help: Replace with f-string
|
||||
|
||||
FURB116 Replace `bin` call with `f"{d:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{d:b}"`
|
||||
--> FURB116.py:32:7
|
||||
|
|
||||
30 | d = datetime.datetime.now(tz=datetime.UTC)
|
||||
@@ -183,7 +183,7 @@ help: Replace with `f"{d:b}"`
|
||||
35 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `bin` call with `f"{len("xyz").numerator:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{len("xyz").numerator:b}"`
|
||||
--> FURB116.py:34:7
|
||||
|
|
||||
32 | print(bin(d)[2:])
|
||||
@@ -204,7 +204,7 @@ help: Replace with `f"{len("xyz").numerator:b}"`
|
||||
37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"`
|
||||
--> FURB116.py:37:7
|
||||
|
|
||||
36 | # autofix is display-only
|
||||
@@ -224,7 +224,7 @@ help: Replace with `f"{ {0: 1}[0].numerator:b}"`
|
||||
40 | print(hex(sys
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `bin` call with `f"{ord("\\").numerator:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{ord("\\").numerator:b}"`
|
||||
--> FURB116.py:39:7
|
||||
|
|
||||
37 | print(bin({0: 1}[0].numerator)[2:])
|
||||
@@ -245,7 +245,7 @@ help: Replace with `f"{ord("\\").numerator:b}"`
|
||||
42 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `hex` call with f-string
|
||||
FURB116 [*] Replace `hex` call with f-string
|
||||
--> FURB116.py:40:7
|
||||
|
|
||||
38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error
|
||||
@@ -270,7 +270,7 @@ help: Replace with f-string
|
||||
44 | print(bin(-1)[2:])
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `bin` call with `f"{-1:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{-1:b}"`
|
||||
--> FURB116.py:44:7
|
||||
|
|
||||
43 | # for negatives numbers autofix is display-only
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||
---
|
||||
FURB116 Replace `oct` call with `f"{num:o}"`
|
||||
FURB116 [*] Replace `oct` call with `f"{num:o}"`
|
||||
--> FURB116.py:9:7
|
||||
|
|
||||
7 | return num
|
||||
@@ -22,7 +22,7 @@ help: Replace with `f"{num:o}"`
|
||||
12 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `hex` call with `f"{num:x}"`
|
||||
FURB116 [*] Replace `hex` call with `f"{num:x}"`
|
||||
--> FURB116.py:10:7
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
@@ -41,7 +41,7 @@ help: Replace with `f"{num:x}"`
|
||||
13 | print(oct(1337)[2:]) # FURB116
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
FURB116 Replace `bin` call with `f"{num:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{num:b}"`
|
||||
--> FURB116.py:11:7
|
||||
|
|
||||
9 | print(oct(num)[2:]) # FURB116
|
||||
@@ -162,7 +162,7 @@ FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
help: Replace with f-string
|
||||
|
||||
FURB116 Replace `bin` call with `f"{d:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{d:b}"`
|
||||
--> FURB116.py:32:7
|
||||
|
|
||||
30 | d = datetime.datetime.now(tz=datetime.UTC)
|
||||
@@ -195,7 +195,7 @@ FURB116 Replace `bin` call with f-string
|
||||
|
|
||||
help: Replace with f-string
|
||||
|
||||
FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"`
|
||||
--> FURB116.py:37:7
|
||||
|
|
||||
36 | # autofix is display-only
|
||||
@@ -241,7 +241,7 @@ FURB116 Replace `hex` call with f-string
|
||||
|
|
||||
help: Replace with f-string
|
||||
|
||||
FURB116 Replace `bin` call with `f"{-1:b}"`
|
||||
FURB116 [*] Replace `bin` call with `f"{-1:b}"`
|
||||
--> FURB116.py:44:7
|
||||
|
|
||||
43 | # for negatives numbers autofix is display-only
|
||||
|
||||
@@ -396,6 +396,8 @@ RUF037 [*] Unnecessary empty iterable within a deque call
|
||||
111 | # https://github.com/astral-sh/ruff/issues/20050
|
||||
112 | deque(f"{""}") # RUF037
|
||||
| ^^^^^^^^^^^^^^
|
||||
113 |
|
||||
114 | deque(f"{b""}")
|
||||
|
|
||||
help: Replace with `deque()`
|
||||
109 | deque(t"{""}") # OK
|
||||
@@ -403,3 +405,6 @@ help: Replace with `deque()`
|
||||
111 | # https://github.com/astral-sh/ruff/issues/20050
|
||||
- deque(f"{""}") # RUF037
|
||||
112 + deque() # RUF037
|
||||
113 |
|
||||
114 | deque(f"{b""}")
|
||||
115 | deque(f"{""=}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> RUF100_5.py:7:5
|
||||
|
|
||||
5 | # "key1": 123, # noqa: ERA001
|
||||
@@ -20,7 +20,7 @@ help: Remove commented-out code
|
||||
9 |
|
||||
note: This is a display-only fix and is likely to be incorrect
|
||||
|
||||
ERA001 Found commented-out code
|
||||
ERA001 [*] Found commented-out code
|
||||
--> RUF100_5.py:11:1
|
||||
|
|
||||
11 | #import os # noqa: E501
|
||||
|
||||
@@ -447,7 +447,7 @@ pub(crate) fn print_jupyter_messages(
|
||||
.with_show_fix_status(true)
|
||||
.with_show_fix_diff(true)
|
||||
.with_show_source(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled)
|
||||
.with_fix_applicability(Applicability::DisplayOnly)
|
||||
.emit(
|
||||
&mut output,
|
||||
diagnostics,
|
||||
@@ -468,7 +468,7 @@ pub(crate) fn print_messages(diagnostics: &[Diagnostic]) -> String {
|
||||
.with_show_fix_status(true)
|
||||
.with_show_fix_diff(true)
|
||||
.with_show_source(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled)
|
||||
.with_fix_applicability(Applicability::DisplayOnly)
|
||||
.emit(
|
||||
&mut output,
|
||||
diagnostics,
|
||||
|
||||
@@ -1410,31 +1410,38 @@ pub fn is_empty_f_string(expr: &ast::ExprFString) -> bool {
|
||||
fn inner(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => value.is_empty(),
|
||||
// Confusingly, `bool(f"{b""}") == True` even though
|
||||
// `bool(b"") == False`. This is because `f"{b""}"`
|
||||
// evaluates as the string `'b""'` of length 3.
|
||||
Expr::BytesLiteral(_) => false,
|
||||
Expr::FString(ast::ExprFString { value, .. }) => {
|
||||
value
|
||||
.elements()
|
||||
.all(|f_string_element| match f_string_element {
|
||||
InterpolatedStringElement::Literal(
|
||||
ast::InterpolatedStringLiteralElement { value, .. },
|
||||
) => value.is_empty(),
|
||||
InterpolatedStringElement::Interpolation(ast::InterpolatedElement {
|
||||
expression,
|
||||
..
|
||||
}) => inner(expression),
|
||||
})
|
||||
is_empty_interpolated_elements(value.elements())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty_interpolated_elements<'a>(
|
||||
mut elements: impl Iterator<Item = &'a InterpolatedStringElement>,
|
||||
) -> bool {
|
||||
elements.all(|element| match element {
|
||||
InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
|
||||
value,
|
||||
..
|
||||
}) => value.is_empty(),
|
||||
InterpolatedStringElement::Interpolation(f_string) => {
|
||||
f_string.debug_text.is_none()
|
||||
&& f_string.conversion.is_none()
|
||||
&& f_string.format_spec.is_none()
|
||||
&& inner(&f_string.expression)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
expr.value.iter().all(|part| match part {
|
||||
ast::FStringPart::Literal(string_literal) => string_literal.is_empty(),
|
||||
ast::FStringPart::FString(f_string) => {
|
||||
f_string.elements.iter().all(|element| match element {
|
||||
InterpolatedStringElement::Literal(string_literal) => string_literal.is_empty(),
|
||||
InterpolatedStringElement::Interpolation(f_string) => inner(&f_string.expression),
|
||||
})
|
||||
is_empty_interpolated_elements(f_string.elements.iter())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1411,7 +1411,7 @@ quux.<CURSOR>
|
||||
__getstate__ :: bound method Quux.__getstate__() -> object
|
||||
__hash__ :: bound method Quux.__hash__() -> int
|
||||
__init__ :: bound method Quux.__init__() -> Unknown
|
||||
__init_subclass__ :: bound method Quux.__init_subclass__() -> None
|
||||
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
|
||||
__module__ :: str
|
||||
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
||||
__new__ :: bound method Quux.__new__() -> Quux
|
||||
@@ -1456,7 +1456,7 @@ quux.b<CURSOR>
|
||||
__getstate__ :: bound method Quux.__getstate__() -> object
|
||||
__hash__ :: bound method Quux.__hash__() -> int
|
||||
__init__ :: bound method Quux.__init__() -> Unknown
|
||||
__init_subclass__ :: bound method Quux.__init_subclass__() -> None
|
||||
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
|
||||
__module__ :: str
|
||||
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
||||
__new__ :: bound method Quux.__new__() -> Quux
|
||||
@@ -1506,7 +1506,7 @@ C.<CURSOR>
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> None
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__init_subclass__ :: bound method <class 'C'>.__init_subclass__() -> None
|
||||
__instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
@@ -1575,7 +1575,7 @@ Meta.<CURSOR>
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: Overload[(self, o: object, /) -> None, (self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None]
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__init_subclass__ :: bound method <class 'Meta'>.__init_subclass__() -> None
|
||||
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
@@ -1682,7 +1682,7 @@ Quux.<CURSOR>
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> Unknown
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__init_subclass__ :: bound method <class 'Quux'>.__init_subclass__() -> None
|
||||
__instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
@@ -1756,7 +1756,7 @@ Answer.<CURSOR>
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> None
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__init_subclass__ :: bound method <class 'Answer'>.__init_subclass__() -> None
|
||||
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
|
||||
|
||||
@@ -462,6 +462,22 @@ reveal_type(C.f2(1)) # revealed: str
|
||||
reveal_type(C().f2(1)) # revealed: str
|
||||
```
|
||||
|
||||
### `__init_subclass__`
|
||||
|
||||
The [`__init_subclass__`] method is implicitly a classmethod:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
cls.custom_attribute: int = 0
|
||||
|
||||
class Derived(Base):
|
||||
pass
|
||||
|
||||
reveal_type(Derived.custom_attribute) # revealed: int
|
||||
```
|
||||
|
||||
## `@staticmethod`
|
||||
|
||||
### Basic
|
||||
@@ -571,3 +587,4 @@ reveal_type(C().f2(1)) # revealed: str
|
||||
```
|
||||
|
||||
[functions and methods]: https://docs.python.org/3/howto/descriptor.html#functions-and-methods
|
||||
[`__init_subclass__`]: https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__
|
||||
|
||||
25
crates/ty_python_semantic/resources/mdtest/classes.md
Normal file
25
crates/ty_python_semantic/resources/mdtest/classes.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Class definitions
|
||||
|
||||
## Deferred resolution of bases
|
||||
|
||||
### Only the stringified name is deferred
|
||||
|
||||
If a class base contains a stringified name, only that name is deferred. Other names are resolved
|
||||
normally.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
A = int
|
||||
|
||||
class G[T]: ...
|
||||
class C(A, G["B"]): ...
|
||||
|
||||
A = str
|
||||
B = bytes
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>]
|
||||
```
|
||||
@@ -120,6 +120,23 @@ def f(x: IntOrStr, y: str | bytes):
|
||||
reveal_type(z) # revealed: (int & ~AlwaysFalsy) | str | bytes
|
||||
```
|
||||
|
||||
## Multiple layers of union aliases
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
class D: ...
|
||||
|
||||
type W = A | B
|
||||
type X = C | D
|
||||
type Y = W | X
|
||||
|
||||
from ty_extensions import is_equivalent_to, static_assert
|
||||
|
||||
static_assert(is_equivalent_to(Y, A | B | C | D))
|
||||
```
|
||||
|
||||
## `TypeAliasType` properties
|
||||
|
||||
Two `TypeAliasType`s are distinct and disjoint, even if they refer to the same type
|
||||
@@ -259,6 +276,17 @@ def h(x: Intersection[A, B]):
|
||||
reveal_type(x) # revealed: tuple[B] | None
|
||||
```
|
||||
|
||||
### Self-recursive callable type
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
type C = Callable[[], C | None]
|
||||
|
||||
def _(x: C):
|
||||
reveal_type(x) # revealed: () -> C | None
|
||||
```
|
||||
|
||||
### Union inside generic
|
||||
|
||||
#### With old-style union
|
||||
|
||||
@@ -19,6 +19,12 @@ class Identity:
|
||||
reveal_type(Identity[0]) # revealed: str
|
||||
```
|
||||
|
||||
`__class_getitem__` is implicitly a classmethod, so it can be called like this:
|
||||
|
||||
```py
|
||||
reveal_type(Identity.__class_getitem__(0)) # revealed: str
|
||||
```
|
||||
|
||||
## Class getitem union
|
||||
|
||||
```py
|
||||
|
||||
@@ -450,19 +450,51 @@ class Person(TypedDict, total=False):
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
age: int | None
|
||||
extra: NotRequired[str]
|
||||
|
||||
def _(p: Person) -> None:
|
||||
reveal_type(p.keys()) # revealed: dict_keys[str, object]
|
||||
reveal_type(p.values()) # revealed: dict_values[str, object]
|
||||
|
||||
reveal_type(p.setdefault("name", "Alice")) # revealed: @Todo(Support for `TypedDict`)
|
||||
# `get()` returns the field type for required keys (no None union)
|
||||
reveal_type(p.get("name")) # revealed: str
|
||||
reveal_type(p.get("age")) # revealed: int | None
|
||||
|
||||
reveal_type(p.get("name")) # revealed: @Todo(Support for `TypedDict`)
|
||||
reveal_type(p.get("name", "Unknown")) # revealed: @Todo(Support for `TypedDict`)
|
||||
# It doesn't matter if a default is specified:
|
||||
reveal_type(p.get("name", "default")) # revealed: str
|
||||
reveal_type(p.get("age", 999)) # revealed: int | None
|
||||
|
||||
# `get()` can return `None` for non-required keys
|
||||
reveal_type(p.get("extra")) # revealed: str | None
|
||||
reveal_type(p.get("extra", "default")) # revealed: str
|
||||
|
||||
# The type of the default parameter can be anything:
|
||||
reveal_type(p.get("extra", 0)) # revealed: str | Literal[0]
|
||||
|
||||
# We allow access to unknown keys (they could be set for a subtype of Person)
|
||||
reveal_type(p.get("unknown")) # revealed: Unknown | None
|
||||
reveal_type(p.get("unknown", "default")) # revealed: Unknown | Literal["default"]
|
||||
|
||||
# `pop()` only works on non-required fields
|
||||
reveal_type(p.pop("extra")) # revealed: str
|
||||
reveal_type(p.pop("extra", "fallback")) # revealed: str
|
||||
# error: [invalid-argument-type] "Cannot pop required field 'name' from TypedDict `Person`"
|
||||
reveal_type(p.pop("name")) # revealed: Unknown
|
||||
|
||||
# Similar to above, the default parameter can be of any type:
|
||||
reveal_type(p.pop("extra", 0)) # revealed: str | Literal[0]
|
||||
|
||||
# `setdefault()` always returns the field type
|
||||
reveal_type(p.setdefault("name", "Alice")) # revealed: str
|
||||
reveal_type(p.setdefault("extra", "default")) # revealed: str
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extraz" - did you mean "extra"?"
|
||||
reveal_type(p.setdefault("extraz", "value")) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Unlike normal classes
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
Tanjun # too many iterations
|
||||
altair # too many iterations (uses packaging)
|
||||
antidote # hangs / slow (single threaded)
|
||||
artigraph # cycle panics (value_type_)
|
||||
arviz # too many iterations on versions of arviz newer than https://github.com/arviz-devs/arviz/commit/3205b82bb4d6097c31f7334d7ac51a6de37002d0
|
||||
core # cycle panics (value_type_)
|
||||
cpython # too many cycle iterations
|
||||
graphql-core # stack overflow
|
||||
hydpy # too many iterations
|
||||
@@ -12,9 +9,6 @@ jax # too many iterations
|
||||
mypy # too many iterations (self-recursive type alias)
|
||||
nox # too many iterations (uses packaging)
|
||||
packaging # too many iterations
|
||||
pandas # slow (9s)
|
||||
pandas-stubs # panics on versions of pandas-stubs newer than https://github.com/pandas-dev/pandas-stubs/commit/bf1221eb7ea0e582c30fe233d1f4f5713fce376b
|
||||
# Panicked at crates/ty_python_semantic/src/types/type_ordering.rs:207:13 when checking `/tmp/mypy_primer/projects/pandas-stubs/tests/test_indexes.py`: `internal error: entered unreachable code: our type representation does not permit nested unions`
|
||||
pandera # too many iterations
|
||||
pip # vendors packaging, see above
|
||||
pylint # cycle panics (self-recursive type alias)
|
||||
@@ -25,4 +19,3 @@ spack # slow, success, but mypy-primer hangs processing the output
|
||||
spark # too many iterations
|
||||
steam.py # hangs (single threaded)
|
||||
streamlit # too many iterations (uses packaging)
|
||||
xarray # too many iterations
|
||||
|
||||
@@ -10,8 +10,10 @@ aioredis
|
||||
aiortc
|
||||
alectryon
|
||||
alerta
|
||||
antidote
|
||||
anyio
|
||||
apprise
|
||||
artigraph
|
||||
async-utils
|
||||
asynq
|
||||
attrs
|
||||
@@ -27,6 +29,7 @@ cloud-init
|
||||
colour
|
||||
com2ann
|
||||
comtypes
|
||||
core
|
||||
cwltool
|
||||
dacite
|
||||
dd-trace-py
|
||||
@@ -66,6 +69,8 @@ openlibrary
|
||||
operator
|
||||
optuna
|
||||
paasta
|
||||
pandas
|
||||
pandas-stubs
|
||||
paroxython
|
||||
parso
|
||||
pegen
|
||||
@@ -114,6 +119,7 @@ urllib3
|
||||
vision
|
||||
websockets
|
||||
werkzeug
|
||||
xarray
|
||||
xarray-dataclasses
|
||||
yarl
|
||||
zipp
|
||||
|
||||
@@ -182,7 +182,7 @@ impl<'db> DunderAllNamesCollector<'db> {
|
||||
///
|
||||
/// This function panics if `expr` was not marked as a standalone expression during semantic indexing.
|
||||
fn standalone_expression_type(&self, expr: &ast::Expr) -> Type<'db> {
|
||||
infer_expression_types(self.db, self.index.expression(expr)).expression_type(expr)
|
||||
infer_expression_types(self.db, self.index.expression(expr), false).expression_type(expr)
|
||||
}
|
||||
|
||||
/// Evaluate the given expression and return its truthiness.
|
||||
|
||||
@@ -328,10 +328,10 @@ fn singleton_to_type(db: &dyn Db, singleton: ruff_python_ast::Singleton) -> Type
|
||||
fn pattern_kind_to_type<'db>(db: &'db dyn Db, kind: &PatternPredicateKind<'db>) -> Type<'db> {
|
||||
match kind {
|
||||
PatternPredicateKind::Singleton(singleton) => singleton_to_type(db, *singleton),
|
||||
PatternPredicateKind::Value(value) => infer_expression_type(db, *value),
|
||||
PatternPredicateKind::Value(value) => infer_expression_type(db, *value, false),
|
||||
PatternPredicateKind::Class(class_expr, kind) => {
|
||||
if kind.is_irrefutable() {
|
||||
infer_expression_type(db, *class_expr)
|
||||
infer_expression_type(db, *class_expr, false)
|
||||
.to_instance(db)
|
||||
.unwrap_or(Type::Never)
|
||||
} else {
|
||||
@@ -718,7 +718,7 @@ impl ReachabilityConstraints {
|
||||
) -> Truthiness {
|
||||
match predicate_kind {
|
||||
PatternPredicateKind::Value(value) => {
|
||||
let value_ty = infer_expression_type(db, *value);
|
||||
let value_ty = infer_expression_type(db, *value, false);
|
||||
|
||||
if subject_ty.is_single_valued(db) {
|
||||
Truthiness::from(subject_ty.is_equivalent_to(db, value_ty))
|
||||
@@ -769,7 +769,7 @@ impl ReachabilityConstraints {
|
||||
truthiness
|
||||
}
|
||||
PatternPredicateKind::Class(class_expr, kind) => {
|
||||
let class_ty = infer_expression_type(db, *class_expr).to_instance(db);
|
||||
let class_ty = infer_expression_type(db, *class_expr, false).to_instance(db);
|
||||
|
||||
class_ty.map_or(Truthiness::Ambiguous, |class_ty| {
|
||||
if subject_ty.is_subtype_of(db, class_ty) {
|
||||
@@ -797,7 +797,7 @@ impl ReachabilityConstraints {
|
||||
}
|
||||
|
||||
fn analyze_single_pattern_predicate(db: &dyn Db, predicate: PatternPredicate) -> Truthiness {
|
||||
let subject_ty = infer_expression_type(db, predicate.subject(db));
|
||||
let subject_ty = infer_expression_type(db, predicate.subject(db), false);
|
||||
|
||||
let narrowed_subject_ty = IntersectionBuilder::new(db)
|
||||
.add_positive(subject_ty)
|
||||
@@ -837,7 +837,7 @@ impl ReachabilityConstraints {
|
||||
// selection algorithm).
|
||||
// Avoiding this on the happy-path is important because these constraints can be
|
||||
// very large in number, since we add them on all statement level function calls.
|
||||
let ty = infer_expression_type(db, callable);
|
||||
let ty = infer_expression_type(db, callable, false);
|
||||
|
||||
// Short-circuit for well known types that are known not to return `Never` when called.
|
||||
// Without the short-circuit, we've seen that threads keep blocking each other
|
||||
@@ -875,7 +875,7 @@ impl ReachabilityConstraints {
|
||||
} else if all_overloads_return_never {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
let call_expr_ty = infer_expression_type(db, call_expr);
|
||||
let call_expr_ty = infer_expression_type(db, call_expr, false);
|
||||
if call_expr_ty.is_equivalent_to(db, Type::Never) {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
|
||||
@@ -46,7 +46,7 @@ use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOO
|
||||
pub use crate::types::display::DisplaySettings;
|
||||
use crate::types::enums::{enum_metadata, is_single_member_enum};
|
||||
use crate::types::function::{
|
||||
DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction,
|
||||
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
|
||||
};
|
||||
use crate::types::generics::{
|
||||
GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context,
|
||||
@@ -191,7 +191,7 @@ pub(crate) struct IsDisjoint;
|
||||
pub(crate) type IsEquivalentVisitor<'db, C> = PairVisitor<'db, IsEquivalent, C>;
|
||||
pub(crate) struct IsEquivalent;
|
||||
|
||||
/// A [`CycleDetector`] for `find_legacy_typevars` methods.
|
||||
/// A [`CycleDetector`] that is used in `find_legacy_typevars` methods.
|
||||
pub(crate) type FindLegacyTypeVarsVisitor<'db> = CycleDetector<FindLegacyTypeVars, Type<'db>, ()>;
|
||||
pub(crate) struct FindLegacyTypeVars;
|
||||
|
||||
@@ -489,13 +489,18 @@ fn walk_property_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
impl get_size2::GetSize for PropertyInstanceType<'_> {}
|
||||
|
||||
impl<'db> PropertyInstanceType<'db> {
|
||||
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
let getter = self
|
||||
.getter(db)
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping));
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor));
|
||||
let setter = self
|
||||
.setter(db)
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping));
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor));
|
||||
Self::new(db, getter, setter)
|
||||
}
|
||||
|
||||
@@ -1118,9 +1123,7 @@ impl<'db> Type<'db> {
|
||||
#[must_use]
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
match self {
|
||||
Type::Union(union) => {
|
||||
visitor.visit(self, || Type::Union(union.normalized_impl(db, visitor)))
|
||||
}
|
||||
Type::Union(union) => visitor.visit(self, || union.normalized_impl(db, visitor)),
|
||||
Type::Intersection(intersection) => visitor.visit(self, || {
|
||||
Type::Intersection(intersection.normalized_impl(db, visitor))
|
||||
}),
|
||||
@@ -1887,14 +1890,14 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Type::TypeAlias(self_alias), _) => {
|
||||
let self_alias_ty = self_alias.value_type(db);
|
||||
let self_alias_ty = self_alias.value_type(db).normalized(db);
|
||||
visitor.visit((self_alias_ty, other), || {
|
||||
self_alias_ty.is_equivalent_to_impl(db, other, visitor)
|
||||
})
|
||||
}
|
||||
|
||||
(_, Type::TypeAlias(other_alias)) => {
|
||||
let other_alias_ty = other_alias.value_type(db);
|
||||
let other_alias_ty = other_alias.value_type(db).normalized(db);
|
||||
visitor.visit((self, other_alias_ty), || {
|
||||
self.is_equivalent_to_impl(db, other_alias_ty, visitor)
|
||||
})
|
||||
@@ -1988,11 +1991,6 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => C::unsatisfiable(db),
|
||||
|
||||
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
|
||||
// TODO: Implement disjointness for TypedDict
|
||||
C::unsatisfiable(db)
|
||||
}
|
||||
|
||||
(Type::TypeAlias(alias), _) => {
|
||||
let self_alias_ty = alias.value_type(db);
|
||||
visitor.visit((self_alias_ty, other), || {
|
||||
@@ -2007,6 +2005,11 @@ impl<'db> Type<'db> {
|
||||
})
|
||||
}
|
||||
|
||||
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
|
||||
// TODO: Implement disjointness for TypedDict
|
||||
C::unsatisfiable(db)
|
||||
}
|
||||
|
||||
// A typevar is never disjoint from itself, since all occurrences of the typevar must
|
||||
// be specialized to the same type. (This is an important difference between typevars
|
||||
// and `Any`!) Different typevars might be disjoint, depending on their bounds and
|
||||
@@ -3334,7 +3337,12 @@ impl<'db> Type<'db> {
|
||||
name: Name,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
tracing::trace!("member_lookup_with_policy: {}.{}", self.display(db), name);
|
||||
let _span = tracing::trace_span!(
|
||||
"member_lookup_with_policy",
|
||||
ty = self.display(db).to_string(),
|
||||
?name
|
||||
)
|
||||
.entered();
|
||||
if name == "__class__" {
|
||||
return Place::bound(self.dunder_class(db)).into();
|
||||
}
|
||||
@@ -5926,6 +5934,8 @@ impl<'db> Type<'db> {
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
||||
Type::BoundSuper(_) => KnownClass::Super.to_class_literal(db),
|
||||
Type::ProtocolInstance(protocol) => protocol.to_meta_type(db),
|
||||
// `TypedDict` instances are instances of `dict` at runtime, but its important that we
|
||||
// understand a more specific meta type in order to correctly handle `__getitem__`.
|
||||
Type::TypedDict(typed_dict) => SubclassOfType::from(db, typed_dict.defining_class()),
|
||||
Type::TypeAlias(alias) => alias.value_type(db).to_meta_type(db),
|
||||
}
|
||||
@@ -6077,18 +6087,18 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)) => {
|
||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(
|
||||
property.apply_type_mapping(db, type_mapping),
|
||||
property.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
))
|
||||
}
|
||||
|
||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(property)) => {
|
||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(
|
||||
property.apply_type_mapping(db, type_mapping),
|
||||
property.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
))
|
||||
}
|
||||
|
||||
Type::Callable(callable) => {
|
||||
Type::Callable(callable.apply_type_mapping(db, type_mapping))
|
||||
Type::Callable(callable.apply_type_mapping_impl(db, type_mapping, visitor))
|
||||
}
|
||||
|
||||
Type::GenericAlias(generic) => {
|
||||
@@ -6104,7 +6114,7 @@ impl<'db> Type<'db> {
|
||||
),
|
||||
|
||||
Type::PropertyInstance(property) => {
|
||||
Type::PropertyInstance(property.apply_type_mapping(db, type_mapping))
|
||||
Type::PropertyInstance(property.apply_type_mapping_impl(db, type_mapping, visitor))
|
||||
}
|
||||
|
||||
Type::Union(union) => union.map(db, |element| {
|
||||
@@ -7528,6 +7538,28 @@ pub struct BoundTypeVarInstance<'db> {
|
||||
impl get_size2::GetSize for BoundTypeVarInstance<'_> {}
|
||||
|
||||
impl<'db> BoundTypeVarInstance<'db> {
|
||||
/// Create a new PEP 695 type variable that can be used in signatures
|
||||
/// of synthetic generic functions.
|
||||
pub(crate) fn synthetic(
|
||||
db: &'db dyn Db,
|
||||
name: &'static str,
|
||||
variance: TypeVarVariance,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
TypeVarInstance::new(
|
||||
db,
|
||||
Name::new_static(name),
|
||||
None, // definition
|
||||
None, // _bound_or_constraints
|
||||
Some(variance),
|
||||
None, // _default
|
||||
TypeVarKind::Pep695,
|
||||
),
|
||||
BindingContext::Synthetic,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn variance_with_polarity(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
@@ -7675,7 +7707,17 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound.normalized_impl(db, visitor))
|
||||
}
|
||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||
TypeVarBoundOrConstraints::Constraints(constraints.normalized_impl(db, visitor))
|
||||
// Constraints are a non-normalized union by design (it's not really a union at
|
||||
// all, we are just using a union to store the types). Normalize the types but not
|
||||
// the containing union.
|
||||
TypeVarBoundOrConstraints::Constraints(UnionType::new(
|
||||
db,
|
||||
constraints
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| ty.normalized_impl(db, visitor))
|
||||
.collect::<Box<_>>(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8782,10 +8824,7 @@ impl<'db> BoundMethodType<'db> {
|
||||
/// a `@classmethod`, then it should be an instance of that bound-instance type.
|
||||
pub(crate) fn typing_self_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let mut self_instance = self.self_instance(db);
|
||||
if self
|
||||
.function(db)
|
||||
.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|
||||
{
|
||||
if self.function(db).is_classmethod(db) {
|
||||
self_instance = self_instance.to_instance(db).unwrap_or_else(Type::unknown);
|
||||
}
|
||||
self_instance
|
||||
@@ -8953,10 +8992,16 @@ impl<'db> CallableType<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
CallableType::new(
|
||||
db,
|
||||
self.signatures(db).apply_type_mapping(db, type_mapping),
|
||||
self.signatures(db)
|
||||
.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
self.is_function_like(db),
|
||||
)
|
||||
}
|
||||
@@ -9632,18 +9677,25 @@ impl<'db> UnionType<'db> {
|
||||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
#[must_use]
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.normalized_impl(db, &NormalizedVisitor::default())
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
let mut new_elements: Vec<Type<'db>> = self
|
||||
.elements(db)
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &NormalizedVisitor<'db>,
|
||||
) -> Type<'db> {
|
||||
self.elements(db)
|
||||
.iter()
|
||||
.map(|element| element.normalized_impl(db, visitor))
|
||||
.collect();
|
||||
new_elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
|
||||
UnionType::new(db, new_elements.into_boxed_slice())
|
||||
.map(|ty| ty.normalized_impl(db, visitor))
|
||||
.fold(
|
||||
UnionBuilder::new(db)
|
||||
.order_elements(true)
|
||||
.unpack_aliases(true),
|
||||
UnionBuilder::add,
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||
@@ -9665,7 +9717,7 @@ impl<'db> UnionType<'db> {
|
||||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == other {
|
||||
if sorted_self == Type::Union(other) {
|
||||
return C::always_satisfiable(db);
|
||||
}
|
||||
|
||||
@@ -10376,11 +10428,7 @@ static_assertions::assert_eq_size!(Type, [u8; 16]);
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::db::tests::{TestDbBuilder, setup_db};
|
||||
use crate::place::{global_symbol, typing_extensions_symbol, typing_symbol};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::DbWithWritableSystem as _;
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use crate::place::{typing_extensions_symbol, typing_symbol};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use test_case::test_case;
|
||||
|
||||
@@ -10421,62 +10469,62 @@ pub(crate) mod tests {
|
||||
|
||||
/// Inferring the result of a call-expression shouldn't need to re-run after
|
||||
/// a trivial change to the function's file (e.g. by adding a docstring to the function).
|
||||
#[test]
|
||||
fn call_type_doesnt_rerun_when_only_callee_changed() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
// #[test]
|
||||
// fn call_type_doesnt_rerun_when_only_callee_changed() -> anyhow::Result<()> {
|
||||
// let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/foo.py",
|
||||
r#"
|
||||
def foo() -> int:
|
||||
return 5
|
||||
"#,
|
||||
)?;
|
||||
db.write_dedented(
|
||||
"src/bar.py",
|
||||
r#"
|
||||
from foo import foo
|
||||
// db.write_dedented(
|
||||
// "src/foo.py",
|
||||
// r#"
|
||||
// def foo() -> int:
|
||||
// return 5
|
||||
// "#,
|
||||
// )?;
|
||||
// db.write_dedented(
|
||||
// "src/bar.py",
|
||||
// r#"
|
||||
// from foo import foo
|
||||
|
||||
a = foo()
|
||||
"#,
|
||||
)?;
|
||||
// a = foo()
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let bar = system_path_to_file(&db, "src/bar.py")?;
|
||||
let a = global_symbol(&db, bar, "a").place;
|
||||
// let bar = system_path_to_file(&db, "src/bar.py")?;
|
||||
// let a = global_symbol(&db, bar, "a").place;
|
||||
|
||||
assert_eq!(
|
||||
a.expect_type(),
|
||||
UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
|
||||
);
|
||||
// assert_eq!(
|
||||
// a.expect_type(),
|
||||
// UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
|
||||
// );
|
||||
|
||||
// Add a docstring to foo to trigger a re-run.
|
||||
// The bar-call site of foo should not be re-run because of that
|
||||
db.write_dedented(
|
||||
"src/foo.py",
|
||||
r#"
|
||||
def foo() -> int:
|
||||
"Computes a value"
|
||||
return 5
|
||||
"#,
|
||||
)?;
|
||||
db.clear_salsa_events();
|
||||
// // Add a docstring to foo to trigger a re-run.
|
||||
// // The bar-call site of foo should not be re-run because of that
|
||||
// db.write_dedented(
|
||||
// "src/foo.py",
|
||||
// r#"
|
||||
// def foo() -> int:
|
||||
// "Computes a value"
|
||||
// return 5
|
||||
// "#,
|
||||
// )?;
|
||||
// db.clear_salsa_events();
|
||||
|
||||
let a = global_symbol(&db, bar, "a").place;
|
||||
// let a = global_symbol(&db, bar, "a").place;
|
||||
|
||||
assert_eq!(
|
||||
a.expect_type(),
|
||||
UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
|
||||
);
|
||||
let events = db.take_salsa_events();
|
||||
// assert_eq!(
|
||||
// a.expect_type(),
|
||||
// UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
|
||||
// );
|
||||
// let events = db.take_salsa_events();
|
||||
|
||||
let module = parsed_module(&db, bar).load(&db);
|
||||
let call = &*module.syntax().body[1].as_assign_stmt().unwrap().value;
|
||||
let foo_call = semantic_index(&db, bar).expression(call);
|
||||
// let module = parsed_module(&db, bar).load(&db);
|
||||
// let call = &*module.syntax().body[1].as_assign_stmt().unwrap().value;
|
||||
// let foo_call = semantic_index(&db, bar).expression(call);
|
||||
|
||||
assert_function_query_was_not_run(&db, infer_expression_types, foo_call, &events);
|
||||
// assert_function_query_was_not_run(&db, infer_expression_types, foo_call, &events, false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
/// All other tests also make sure that `Type::Todo` works as expected. This particular
|
||||
/// test makes sure that we handle `Todo` types correctly, even if they originate from
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
//! unnecessary `is_subtype_of` checks.
|
||||
|
||||
use crate::types::enums::{enum_member_literals, enum_metadata};
|
||||
use crate::types::type_ordering::union_or_intersection_elements_ordering;
|
||||
use crate::types::{
|
||||
BytesLiteralType, IntersectionType, KnownClass, StringLiteralType, Type,
|
||||
TypeVarBoundOrConstraints, UnionType,
|
||||
@@ -211,6 +212,7 @@ pub(crate) struct UnionBuilder<'db> {
|
||||
elements: Vec<UnionElement<'db>>,
|
||||
db: &'db dyn Db,
|
||||
unpack_aliases: bool,
|
||||
order_elements: bool,
|
||||
}
|
||||
|
||||
impl<'db> UnionBuilder<'db> {
|
||||
@@ -219,6 +221,7 @@ impl<'db> UnionBuilder<'db> {
|
||||
db,
|
||||
elements: vec![],
|
||||
unpack_aliases: true,
|
||||
order_elements: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +230,11 @@ impl<'db> UnionBuilder<'db> {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn order_elements(mut self, val: bool) -> Self {
|
||||
self.order_elements = val;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.elements.is_empty()
|
||||
}
|
||||
@@ -545,6 +553,9 @@ impl<'db> UnionBuilder<'db> {
|
||||
UnionElement::Type(ty) => types.push(ty),
|
||||
}
|
||||
}
|
||||
if self.order_elements {
|
||||
types.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(self.db, l, r));
|
||||
}
|
||||
match types.len() {
|
||||
0 => None,
|
||||
1 => Some(types[0]),
|
||||
|
||||
@@ -272,7 +272,7 @@ impl<'db> Bindings<'db> {
|
||||
for (overload_index, overload) in binding.matching_overloads_mut() {
|
||||
match binding_type {
|
||||
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
|
||||
if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) {
|
||||
if function.is_classmethod(db) {
|
||||
match overload.parameter_types() {
|
||||
[_, Some(owner)] => {
|
||||
overload.set_return_type(Type::BoundMethod(
|
||||
@@ -308,7 +308,7 @@ impl<'db> Bindings<'db> {
|
||||
if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) {
|
||||
if function.is_classmethod(db) {
|
||||
match overload.parameter_types() {
|
||||
[_, _, Some(owner)] => {
|
||||
overload.set_return_type(Type::BoundMethod(
|
||||
|
||||
@@ -34,7 +34,7 @@ use crate::types::{
|
||||
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
|
||||
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping,
|
||||
TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams,
|
||||
VarianceInferable, declaration_type, infer_definition_types, todo_type,
|
||||
UnionBuilder, VarianceInferable, declaration_type, infer_definition_types,
|
||||
};
|
||||
use crate::{
|
||||
Db, FxIndexMap, FxOrderSet, Program,
|
||||
@@ -51,7 +51,7 @@ use crate::{
|
||||
semantic_index, use_def_map,
|
||||
},
|
||||
types::{
|
||||
CallArguments, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType,
|
||||
CallArguments, CallError, CallErrorKind, MetaclassCandidate, UnionType,
|
||||
definition_expression_type,
|
||||
},
|
||||
};
|
||||
@@ -1258,7 +1258,7 @@ pub(super) enum MethodDecorator {
|
||||
impl MethodDecorator {
|
||||
fn try_from_fn_type(db: &dyn Db, fn_type: FunctionType) -> Result<Self, ()> {
|
||||
match (
|
||||
fn_type.has_known_decorator(db, FunctionDecorators::CLASSMETHOD),
|
||||
fn_type.is_classmethod(db),
|
||||
fn_type.has_known_decorator(db, FunctionDecorators::STATICMETHOD),
|
||||
) {
|
||||
(true, true) => Err(()), // A method can't be static and class method at the same time.
|
||||
@@ -2331,49 +2331,179 @@ impl<'db> ClassLiteral<'db> {
|
||||
)))
|
||||
}
|
||||
(CodeGeneratorKind::TypedDict, "get") => {
|
||||
// TODO: synthesize a set of overloads with precise types
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key"))),
|
||||
Parameter::positional_only(Some(Name::new_static("default")))
|
||||
.with_default_type(Type::unknown()),
|
||||
]),
|
||||
Some(todo_type!("Support for `TypedDict`")),
|
||||
);
|
||||
let overloads = self
|
||||
.fields(db, specialization, field_policy)
|
||||
.into_iter()
|
||||
.flat_map(|(name, field)| {
|
||||
let key_type =
|
||||
Type::StringLiteral(StringLiteralType::new(db, name.as_str()));
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
// For a required key, `.get()` always returns the value type. For a non-required key,
|
||||
// `.get()` returns the union of the value type and the type of the default argument
|
||||
// (which defaults to `None`).
|
||||
|
||||
// TODO: For now, we use two overloads here. They can be merged into a single function
|
||||
// once the generics solver takes default arguments into account.
|
||||
|
||||
let get_sig = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key")))
|
||||
.with_annotated_type(key_type),
|
||||
]),
|
||||
Some(if field.is_required() {
|
||||
field.declared_ty
|
||||
} else {
|
||||
UnionType::from_elements(db, [field.declared_ty, Type::none(db)])
|
||||
}),
|
||||
);
|
||||
|
||||
let t_default =
|
||||
BoundTypeVarInstance::synthetic(db, "T", TypeVarVariance::Covariant);
|
||||
|
||||
let get_with_default_sig = Signature::new_generic(
|
||||
Some(GenericContext::from_typevar_instances(db, [t_default])),
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key")))
|
||||
.with_annotated_type(key_type),
|
||||
Parameter::positional_only(Some(Name::new_static("default")))
|
||||
.with_annotated_type(Type::TypeVar(t_default)),
|
||||
]),
|
||||
Some(if field.is_required() {
|
||||
field.declared_ty
|
||||
} else {
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
[field.declared_ty, Type::TypeVar(t_default)],
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
[get_sig, get_with_default_sig]
|
||||
})
|
||||
// Fallback overloads for unknown keys
|
||||
.chain(std::iter::once({
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key")))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
]),
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[Type::unknown(), Type::none(db)],
|
||||
)),
|
||||
)
|
||||
}))
|
||||
.chain(std::iter::once({
|
||||
let t_default =
|
||||
BoundTypeVarInstance::synthetic(db, "T", TypeVarVariance::Covariant);
|
||||
|
||||
Signature::new_generic(
|
||||
Some(GenericContext::from_typevar_instances(db, [t_default])),
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key")))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
Parameter::positional_only(Some(Name::new_static("default")))
|
||||
.with_annotated_type(Type::TypeVar(t_default)),
|
||||
]),
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[Type::unknown(), Type::TypeVar(t_default)],
|
||||
)),
|
||||
)
|
||||
}));
|
||||
|
||||
Some(Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(overloads),
|
||||
true,
|
||||
)))
|
||||
}
|
||||
(CodeGeneratorKind::TypedDict, "pop") => {
|
||||
// TODO: synthesize a set of overloads with precise types.
|
||||
// Required keys should be forbidden to be popped.
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key"))),
|
||||
Parameter::positional_only(Some(Name::new_static("default")))
|
||||
.with_default_type(Type::unknown()),
|
||||
]),
|
||||
Some(todo_type!("Support for `TypedDict`")),
|
||||
);
|
||||
let fields = self.fields(db, specialization, field_policy);
|
||||
let overloads = fields
|
||||
.iter()
|
||||
.filter(|(_, field)| {
|
||||
// Only synthesize `pop` for fields that are not required.
|
||||
!field.is_required()
|
||||
})
|
||||
.flat_map(|(name, field)| {
|
||||
let key_type =
|
||||
Type::StringLiteral(StringLiteralType::new(db, name.as_str()));
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
// TODO: Similar to above: consider merging these two overloads into one
|
||||
|
||||
// `.pop()` without default
|
||||
let pop_sig = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key")))
|
||||
.with_annotated_type(key_type),
|
||||
]),
|
||||
Some(field.declared_ty),
|
||||
);
|
||||
|
||||
// `.pop()` with a default value
|
||||
let t_default =
|
||||
BoundTypeVarInstance::synthetic(db, "T", TypeVarVariance::Covariant);
|
||||
|
||||
let pop_with_default_sig = Signature::new_generic(
|
||||
Some(GenericContext::from_typevar_instances(db, [t_default])),
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key")))
|
||||
.with_annotated_type(key_type),
|
||||
Parameter::positional_only(Some(Name::new_static("default")))
|
||||
.with_annotated_type(Type::TypeVar(t_default)),
|
||||
]),
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[field.declared_ty, Type::TypeVar(t_default)],
|
||||
)),
|
||||
);
|
||||
|
||||
[pop_sig, pop_with_default_sig]
|
||||
});
|
||||
|
||||
Some(Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(overloads),
|
||||
true,
|
||||
)))
|
||||
}
|
||||
(CodeGeneratorKind::TypedDict, "setdefault") => {
|
||||
// TODO: synthesize a set of overloads with precise types
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key"))),
|
||||
Parameter::positional_only(Some(Name::new_static("default"))),
|
||||
]),
|
||||
Some(todo_type!("Support for `TypedDict`")),
|
||||
);
|
||||
let fields = self.fields(db, specialization, field_policy);
|
||||
let overloads = fields.iter().map(|(name, field)| {
|
||||
let key_type = Type::StringLiteral(StringLiteralType::new(db, name.as_str()));
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
// `setdefault` always returns the field type
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_only(Some(Name::new_static("key")))
|
||||
.with_annotated_type(key_type),
|
||||
Parameter::positional_only(Some(Name::new_static("default")))
|
||||
.with_annotated_type(field.declared_ty),
|
||||
]),
|
||||
Some(field.declared_ty),
|
||||
)
|
||||
});
|
||||
|
||||
Some(Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(overloads),
|
||||
true,
|
||||
)))
|
||||
}
|
||||
(CodeGeneratorKind::TypedDict, "update") => {
|
||||
// TODO: synthesize a set of overloads with precise types
|
||||
@@ -2752,7 +2882,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
// `self.SOME_CONSTANT: Final = 1`, infer the type from the value
|
||||
// on the right-hand side.
|
||||
|
||||
let inferred_ty = infer_expression_type(db, index.expression(value));
|
||||
let inferred_ty = infer_expression_type(db, index.expression(value), true);
|
||||
return Place::bound(inferred_ty).with_qualifiers(all_qualifiers);
|
||||
}
|
||||
|
||||
@@ -2838,6 +2968,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
let inferred_ty = infer_expression_type(
|
||||
db,
|
||||
index.expression(assign.value(&module)),
|
||||
true,
|
||||
);
|
||||
|
||||
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
|
||||
@@ -2865,6 +2996,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
let iterable_ty = infer_expression_type(
|
||||
db,
|
||||
index.expression(for_stmt.iterable(&module)),
|
||||
true,
|
||||
);
|
||||
// TODO: Potential diagnostics resulting from the iterable are currently not reported.
|
||||
let inferred_ty =
|
||||
@@ -2895,6 +3027,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
let context_ty = infer_expression_type(
|
||||
db,
|
||||
index.expression(with_item.context_expr(&module)),
|
||||
true,
|
||||
);
|
||||
let inferred_ty = if with_item.is_async() {
|
||||
context_ty.aenter(db)
|
||||
@@ -2928,6 +3061,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
let iterable_ty = infer_expression_type(
|
||||
db,
|
||||
index.expression(comprehension.iterable(&module)),
|
||||
true,
|
||||
);
|
||||
// TODO: Potential diagnostics resulting from the iterable are currently not reported.
|
||||
let inferred_ty =
|
||||
|
||||
@@ -2952,6 +2952,21 @@ pub(crate) fn report_missing_typed_dict_key<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report_cannot_pop_required_field_on_typed_dict<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
key_node: AnyNodeRef,
|
||||
typed_dict_ty: Type<'db>,
|
||||
field_name: &str,
|
||||
) {
|
||||
let db = context.db();
|
||||
if let Some(builder) = context.report_lint(&INVALID_ARGUMENT_TYPE, key_node) {
|
||||
let typed_dict_name = typed_dict_ty.display(db);
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot pop required field '{field_name}' from TypedDict `{typed_dict_name}`",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// This function receives an unresolved `from foo import bar` import,
|
||||
/// where `foo` can be resolved to a module but that module does not
|
||||
/// have a `bar` member or submodule.
|
||||
|
||||
@@ -569,7 +569,7 @@ impl<'db> FunctionLiteral<'db> {
|
||||
if overloads.is_empty() {
|
||||
return CallableSignature::single(type_mappings.iter().fold(
|
||||
implementation.signature(db, inherited_generic_context),
|
||||
|ty, mapping| ty.apply_type_mapping(db, mapping),
|
||||
|sig, mapping| sig.apply_type_mapping(db, mapping),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -577,7 +577,7 @@ impl<'db> FunctionLiteral<'db> {
|
||||
CallableSignature::from_overloads(overloads.iter().map(|overload| {
|
||||
type_mappings.iter().fold(
|
||||
overload.signature(db, inherited_generic_context),
|
||||
|ty, mapping| ty.apply_type_mapping(db, mapping),
|
||||
|sig, mapping| sig.apply_type_mapping(db, mapping),
|
||||
)
|
||||
}))
|
||||
}
|
||||
@@ -602,7 +602,7 @@ impl<'db> FunctionLiteral<'db> {
|
||||
type_mappings.iter().fold(
|
||||
self.last_definition(db)
|
||||
.signature(db, inherited_generic_context),
|
||||
|ty, mapping| ty.apply_type_mapping(db, mapping),
|
||||
|sig, mapping| sig.apply_type_mapping(db, mapping),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -721,6 +721,16 @@ impl<'db> FunctionType<'db> {
|
||||
self.literal(db).has_known_decorator(db, decorator)
|
||||
}
|
||||
|
||||
/// Returns true if this method is decorated with `@classmethod`, or if it is implicitly a
|
||||
/// classmethod.
|
||||
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
|
||||
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|
||||
|| matches!(
|
||||
self.name(db).as_str(),
|
||||
"__init_subclass__" | "__class_getitem__"
|
||||
)
|
||||
}
|
||||
|
||||
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
|
||||
///
|
||||
/// Checking if an overload is deprecated requires deeper call analysis.
|
||||
|
||||
@@ -119,13 +119,19 @@ impl<'db> GenericContext<'db> {
|
||||
binding_context: Definition<'db>,
|
||||
type_params_node: &ast::TypeParams,
|
||||
) -> Self {
|
||||
let variables: FxOrderSet<_> = type_params_node
|
||||
.iter()
|
||||
.filter_map(|type_param| {
|
||||
Self::variable_from_type_param(db, index, binding_context, type_param)
|
||||
})
|
||||
.collect();
|
||||
Self::new(db, variables)
|
||||
let variables = type_params_node.iter().filter_map(|type_param| {
|
||||
Self::variable_from_type_param(db, index, binding_context, type_param)
|
||||
});
|
||||
|
||||
Self::from_typevar_instances(db, variables)
|
||||
}
|
||||
|
||||
/// Creates a generic context from a list of `BoundTypeVarInstance`s.
|
||||
pub(crate) fn from_typevar_instances(
|
||||
db: &'db dyn Db,
|
||||
type_params: impl IntoIterator<Item = BoundTypeVarInstance<'db>>,
|
||||
) -> Self {
|
||||
Self::new(db, type_params.into_iter().collect::<FxOrderSet<_>>())
|
||||
}
|
||||
|
||||
fn variable_from_type_param(
|
||||
@@ -365,12 +371,12 @@ impl<'db> GenericContext<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
let variables: FxOrderSet<_> = self
|
||||
let variables = self
|
||||
.variables(db)
|
||||
.iter()
|
||||
.map(|bound_typevar| bound_typevar.normalized_impl(db, visitor))
|
||||
.collect();
|
||||
Self::new(db, variables)
|
||||
.map(|bound_typevar| bound_typevar.normalized_impl(db, visitor));
|
||||
|
||||
Self::from_typevar_instances(db, variables)
|
||||
}
|
||||
|
||||
fn heap_size((variables,): &(FxOrderSet<BoundTypeVarInstance<'db>>,)) -> usize {
|
||||
|
||||
@@ -102,7 +102,8 @@ use crate::types::diagnostic::{
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, POSSIBLY_UNBOUND_IMPLICIT_CALL,
|
||||
POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE,
|
||||
UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR,
|
||||
report_bad_dunder_set_call, report_implicit_return_type, report_instance_layout_conflict,
|
||||
report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict,
|
||||
report_implicit_return_type, report_instance_layout_conflict,
|
||||
report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated,
|
||||
report_invalid_arguments_to_callable, report_invalid_assignment,
|
||||
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
||||
@@ -255,6 +256,7 @@ fn deferred_cycle_initial<'db>(
|
||||
pub(crate) fn infer_expression_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
_break_cycle: bool,
|
||||
) -> ExpressionInference<'db> {
|
||||
let file = expression.file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
@@ -277,6 +279,7 @@ fn expression_cycle_recover<'db>(
|
||||
_value: &ExpressionInference<'db>,
|
||||
_count: u32,
|
||||
_expression: Expression<'db>,
|
||||
_break_cycle: bool,
|
||||
) -> salsa::CycleRecoveryAction<ExpressionInference<'db>> {
|
||||
salsa::CycleRecoveryAction::Iterate
|
||||
}
|
||||
@@ -284,6 +287,7 @@ fn expression_cycle_recover<'db>(
|
||||
fn expression_cycle_initial<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
_break_cycle: bool,
|
||||
) -> ExpressionInference<'db> {
|
||||
ExpressionInference::cycle_fallback(expression.scope(db))
|
||||
}
|
||||
@@ -297,8 +301,9 @@ pub(super) fn infer_same_file_expression_type<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
parsed: &ParsedModuleRef,
|
||||
break_cycle: bool,
|
||||
) -> Type<'db> {
|
||||
let inference = infer_expression_types(db, expression);
|
||||
let inference = infer_expression_types(db, expression, break_cycle);
|
||||
inference.expression_type(expression.node_ref(db, parsed))
|
||||
}
|
||||
|
||||
@@ -313,19 +318,33 @@ pub(super) fn infer_same_file_expression_type<'db>(
|
||||
pub(crate) fn infer_expression_type<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
break_cycle: bool,
|
||||
) -> Type<'db> {
|
||||
let file = expression.file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
|
||||
// It's okay to call the "same file" version here because we're inside a salsa query.
|
||||
infer_same_file_expression_type(db, expression, &module)
|
||||
infer_same_file_expression_type(db, expression, &module, break_cycle)
|
||||
}
|
||||
|
||||
// #[salsa::tracked(cycle_fn=single_expression_cycle_recover, cycle_initial=single_expression_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
// pub(crate) fn infer_expression_type_query<'db>(
|
||||
// db: &'db dyn Db,
|
||||
// expression: Expression<'db>,
|
||||
// ) -> Type<'db> {
|
||||
// let file = expression.file(db);
|
||||
// let module = parsed_module(db, file).load(db);
|
||||
|
||||
// // It's okay to call the "same file" version here because we're inside a salsa query.
|
||||
// infer_same_file_expression_type(db, expression, &module)
|
||||
// }
|
||||
|
||||
fn single_expression_cycle_recover<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_value: &Type<'db>,
|
||||
_count: u32,
|
||||
_expression: Expression<'db>,
|
||||
_break_cycle: bool,
|
||||
) -> salsa::CycleRecoveryAction<Type<'db>> {
|
||||
salsa::CycleRecoveryAction::Iterate
|
||||
}
|
||||
@@ -333,6 +352,7 @@ fn single_expression_cycle_recover<'db>(
|
||||
fn single_expression_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_expression: Expression<'db>,
|
||||
_break_cycle: bool,
|
||||
) -> Type<'db> {
|
||||
Type::Never
|
||||
}
|
||||
@@ -346,7 +366,7 @@ pub(crate) fn static_expression_truthiness<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
) -> Truthiness {
|
||||
let inference = infer_expression_types(db, expression);
|
||||
let inference = infer_expression_types(db, expression, false);
|
||||
|
||||
if !inference.all_places_definitely_bound() {
|
||||
return Truthiness::Ambiguous;
|
||||
@@ -998,9 +1018,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.index.has_future_annotations() || self.in_stub()
|
||||
}
|
||||
|
||||
/// Are we currently inferring deferred types?
|
||||
/// Are we currently in a context where name resolution should be deferred
|
||||
/// (`__future__.annotations`, stub file, or stringified annotation)?
|
||||
fn is_deferred(&self) -> bool {
|
||||
matches!(self.region, InferenceRegion::Deferred(_)) || self.deferred_state.is_deferred()
|
||||
self.deferred_state.is_deferred()
|
||||
}
|
||||
|
||||
/// Return the node key of the given AST node, or the key of the outermost enclosing string
|
||||
@@ -2674,7 +2695,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
fn infer_definition(&mut self, node: impl Into<DefinitionNodeKey> + std::fmt::Debug + Copy) {
|
||||
let definition = self.index.expect_single_definition(node);
|
||||
let result = infer_definition_types(self.db(), definition);
|
||||
self.extend_definition(result);
|
||||
self.extend_definition(&result);
|
||||
}
|
||||
|
||||
fn infer_function_definition_statement(&mut self, function: &ast::StmtFunctionDef) {
|
||||
@@ -3172,10 +3193,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.infer_expression(&keyword.value);
|
||||
}
|
||||
|
||||
// Inference of bases deferred in stubs
|
||||
// TODO: Only defer the references that are actually string literals, instead of
|
||||
// deferring the entire class definition if a string literal occurs anywhere in the
|
||||
// base class list.
|
||||
// Inference of bases deferred in stubs, or if any are string literals.
|
||||
if self.in_stub() || class_node.bases().iter().any(contains_string_literal) {
|
||||
self.deferred.insert(definition);
|
||||
} else {
|
||||
@@ -3206,7 +3224,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
fn infer_class_deferred(&mut self, definition: Definition<'db>, class: &ast::StmtClassDef) {
|
||||
let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);
|
||||
for base in class.bases() {
|
||||
self.infer_expression(base);
|
||||
if self.in_stub() {
|
||||
self.infer_expression_with_state(base, DeferredExpressionState::Deferred);
|
||||
} else {
|
||||
self.infer_expression(base);
|
||||
}
|
||||
}
|
||||
self.typevar_binding_context = previous_typevar_binding_context;
|
||||
}
|
||||
@@ -3560,6 +3582,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
bound,
|
||||
default,
|
||||
} = node;
|
||||
let previous_deferred_state =
|
||||
std::mem::replace(&mut self.deferred_state, DeferredExpressionState::Deferred);
|
||||
match bound.as_deref() {
|
||||
Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => {
|
||||
// We don't use UnionType::from_elements or UnionBuilder here, because we don't
|
||||
@@ -3581,6 +3605,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
None => {}
|
||||
}
|
||||
self.infer_optional_type_expression(default.as_deref());
|
||||
self.deferred_state = previous_deferred_state;
|
||||
}
|
||||
|
||||
fn infer_paramspec_definition(
|
||||
@@ -5136,7 +5161,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.check_deprecated(alias, ty.inner);
|
||||
}
|
||||
}
|
||||
self.extend_definition(inferred);
|
||||
self.extend_definition(&inferred);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5599,6 +5624,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.infer_expression_impl(expression)
|
||||
}
|
||||
|
||||
fn infer_expression_with_state(
|
||||
&mut self,
|
||||
expression: &ast::Expr,
|
||||
state: DeferredExpressionState,
|
||||
) -> Type<'db> {
|
||||
let previous_deferred_state = std::mem::replace(&mut self.deferred_state, state);
|
||||
let ty = self.infer_expression(expression);
|
||||
self.deferred_state = previous_deferred_state;
|
||||
ty
|
||||
}
|
||||
|
||||
fn infer_maybe_standalone_expression(&mut self, expression: &ast::Expr) -> Type<'db> {
|
||||
if let Some(standalone_expression) = self.index.try_expression(expression) {
|
||||
self.infer_standalone_expression_impl(expression, standalone_expression)
|
||||
@@ -5618,7 +5654,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
expression: &ast::Expr,
|
||||
standalone_expression: Expression<'db>,
|
||||
) -> Type<'db> {
|
||||
let types = infer_expression_types(self.db(), standalone_expression);
|
||||
let types = infer_expression_types(self.db(), standalone_expression, false);
|
||||
self.extend_expression(types);
|
||||
|
||||
// Instead of calling `self.expression_type(expr)` after extending here, we get
|
||||
@@ -6052,6 +6088,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
builder.db(),
|
||||
builder.index.expression(iter_expr),
|
||||
builder.module(),
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
builder.infer_standalone_expression(iter_expr)
|
||||
@@ -6074,7 +6111,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
let mut infer_iterable_type = || {
|
||||
let expression = self.index.expression(iterable);
|
||||
let result = infer_expression_types(self.db(), expression);
|
||||
let result = infer_expression_types(self.db(), expression, false);
|
||||
|
||||
// Two things are different if it's the first comprehension:
|
||||
// (1) We must lookup the `ScopedExpressionId` of the iterable expression in the outer scope,
|
||||
@@ -6124,7 +6161,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if named.target.is_name_expr() {
|
||||
let definition = self.index.expect_single_definition(named);
|
||||
let result = infer_definition_types(self.db(), definition);
|
||||
self.extend_definition(result);
|
||||
self.extend_definition(&result);
|
||||
result.binding_type(definition)
|
||||
} else {
|
||||
// For syntactically invalid targets, we still need to run type inference:
|
||||
@@ -6270,6 +6307,58 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
let callable_type = self.infer_maybe_standalone_expression(func);
|
||||
|
||||
// Special handling for `TypedDict` method calls
|
||||
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
|
||||
let value_type = self.expression_type(value);
|
||||
if let Type::TypedDict(typed_dict_ty) = value_type {
|
||||
if matches!(attr.id.as_str(), "pop" | "setdefault") && !arguments.args.is_empty() {
|
||||
// Validate the key argument for `TypedDict` methods
|
||||
if let Some(first_arg) = arguments.args.first() {
|
||||
if let ast::Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
value: key_literal,
|
||||
..
|
||||
}) = first_arg
|
||||
{
|
||||
let key = key_literal.to_str();
|
||||
let items = typed_dict_ty.items(self.db());
|
||||
|
||||
// Check if key exists
|
||||
if let Some((_, field)) = items
|
||||
.iter()
|
||||
.find(|(field_name, _)| field_name.as_str() == key)
|
||||
{
|
||||
// Key exists - check if it's a `pop()` on a required field
|
||||
if attr.id.as_str() == "pop" && field.is_required() {
|
||||
report_cannot_pop_required_field_on_typed_dict(
|
||||
&self.context,
|
||||
first_arg.into(),
|
||||
Type::TypedDict(typed_dict_ty),
|
||||
key,
|
||||
);
|
||||
return Type::unknown();
|
||||
}
|
||||
} else {
|
||||
// Key not found, report error with suggestion and return early
|
||||
let key_ty = Type::StringLiteral(
|
||||
crate::types::StringLiteralType::new(self.db(), key),
|
||||
);
|
||||
report_invalid_key_on_typed_dict(
|
||||
&self.context,
|
||||
first_arg.into(),
|
||||
first_arg.into(),
|
||||
Type::TypedDict(typed_dict_ty),
|
||||
key_ty,
|
||||
&items,
|
||||
);
|
||||
// Return `Unknown` to prevent the overload system from generating its own error
|
||||
return Type::unknown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Type::FunctionLiteral(function) = callable_type {
|
||||
// Make sure that the `function.definition` is only called when the function is defined
|
||||
// in the same file as the one we're currently inferring the types for. This is because
|
||||
@@ -7170,13 +7259,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
/// Infer the type of a [`ast::ExprAttribute`] expression, assuming a load context.
|
||||
fn infer_attribute_load(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
|
||||
let ast::ExprAttribute {
|
||||
value,
|
||||
attr,
|
||||
range: _,
|
||||
node_index: _,
|
||||
ctx: _,
|
||||
} = attribute;
|
||||
let ast::ExprAttribute { value, attr, .. } = attribute;
|
||||
|
||||
let value_type = self.infer_maybe_standalone_expression(value);
|
||||
let db = self.db();
|
||||
@@ -9140,7 +9223,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
match ty.try_call(db, &CallArguments::positional([value_ty, slice_ty])) {
|
||||
match ty.try_call(db, &CallArguments::positional([slice_ty])) {
|
||||
Ok(bindings) => return bindings.return_type(db),
|
||||
Err(CallError(_, bindings)) => {
|
||||
if let Some(builder) =
|
||||
@@ -11604,7 +11687,7 @@ mod tests {
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{File, system_path_to_file};
|
||||
use ruff_db::system::DbWithWritableSystem as _;
|
||||
use ruff_db::testing::{assert_function_query_was_not_run, assert_function_query_was_run};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -11986,267 +12069,267 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_implicit_instance_attribute() -> anyhow::Result<()> {
|
||||
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
|
||||
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
|
||||
let ast = parsed_module(db, file_main).load(db);
|
||||
// Get the second statement in `main.py` (x = …) and extract the expression
|
||||
// node on the right-hand side:
|
||||
let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
|
||||
// #[test]
|
||||
// fn dependency_implicit_instance_attribute() -> anyhow::Result<()> {
|
||||
// fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
|
||||
// let file_main = system_path_to_file(db, "/src/main.py").unwrap();
|
||||
// let ast = parsed_module(db, file_main).load(db);
|
||||
// // Get the second statement in `main.py` (x = …) and extract the expression
|
||||
// // node on the right-hand side:
|
||||
// let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
|
||||
|
||||
let index = semantic_index(db, file_main);
|
||||
index.expression(x_rhs_node.as_ref())
|
||||
}
|
||||
// let index = semantic_index(db, file_main);
|
||||
// index.expression(x_rhs_node.as_ref())
|
||||
// }
|
||||
|
||||
let mut db = setup_db();
|
||||
// let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
def f(self):
|
||||
self.attr: int | None = None
|
||||
"#,
|
||||
)?;
|
||||
db.write_dedented(
|
||||
"/src/main.py",
|
||||
r#"
|
||||
from mod import C
|
||||
x = C().attr
|
||||
"#,
|
||||
)?;
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// def f(self):
|
||||
// self.attr: int | None = None
|
||||
// "#,
|
||||
// )?;
|
||||
// db.write_dedented(
|
||||
// "/src/main.py",
|
||||
// r#"
|
||||
// from mod import C
|
||||
// x = C().attr
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
|
||||
// let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
|
||||
|
||||
// Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
def f(self):
|
||||
self.attr: str | None = None
|
||||
"#,
|
||||
)?;
|
||||
// // Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// def f(self):
|
||||
// self.attr: str | None = None
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
|
||||
// let events = {
|
||||
// db.clear_salsa_events();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
// db.take_salsa_events()
|
||||
// };
|
||||
// assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
|
||||
|
||||
// Add a comment; this should not trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
def f(self):
|
||||
# a comment!
|
||||
self.attr: str | None = None
|
||||
"#,
|
||||
)?;
|
||||
// // Add a comment; this should not trigger the type of `x` to be re-inferred
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// def f(self):
|
||||
// # a comment!
|
||||
// self.attr: str | None = None
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
// let events = {
|
||||
// db.clear_salsa_events();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
// db.take_salsa_events()
|
||||
// };
|
||||
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
infer_expression_types,
|
||||
x_rhs_expression(&db),
|
||||
&events,
|
||||
);
|
||||
// assert_function_query_was_not_run(
|
||||
// &db,
|
||||
// infer_expression_types,
|
||||
// x_rhs_expression(&db),
|
||||
// &events,
|
||||
// );
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
/// This test verifies that changing a class's declaration in a non-meaningful way (e.g. by adding a comment)
|
||||
/// doesn't trigger type inference for expressions that depend on the class's members.
|
||||
#[test]
|
||||
fn dependency_own_instance_member() -> anyhow::Result<()> {
|
||||
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
|
||||
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
|
||||
let ast = parsed_module(db, file_main).load(db);
|
||||
// Get the second statement in `main.py` (x = …) and extract the expression
|
||||
// node on the right-hand side:
|
||||
let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
|
||||
// /// This test verifies that changing a class's declaration in a non-meaningful way (e.g. by adding a comment)
|
||||
// /// doesn't trigger type inference for expressions that depend on the class's members.
|
||||
// #[test]
|
||||
// fn dependency_own_instance_member() -> anyhow::Result<()> {
|
||||
// fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
|
||||
// let file_main = system_path_to_file(db, "/src/main.py").unwrap();
|
||||
// let ast = parsed_module(db, file_main).load(db);
|
||||
// // Get the second statement in `main.py` (x = …) and extract the expression
|
||||
// // node on the right-hand side:
|
||||
// let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
|
||||
|
||||
let index = semantic_index(db, file_main);
|
||||
index.expression(x_rhs_node.as_ref())
|
||||
}
|
||||
// let index = semantic_index(db, file_main);
|
||||
// index.expression(x_rhs_node.as_ref())
|
||||
// }
|
||||
|
||||
let mut db = setup_db();
|
||||
// let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
if random.choice([True, False]):
|
||||
attr: int = 42
|
||||
else:
|
||||
attr: None = None
|
||||
"#,
|
||||
)?;
|
||||
db.write_dedented(
|
||||
"/src/main.py",
|
||||
r#"
|
||||
from mod import C
|
||||
x = C().attr
|
||||
"#,
|
||||
)?;
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// if random.choice([True, False]):
|
||||
// attr: int = 42
|
||||
// else:
|
||||
// attr: None = None
|
||||
// "#,
|
||||
// )?;
|
||||
// db.write_dedented(
|
||||
// "/src/main.py",
|
||||
// r#"
|
||||
// from mod import C
|
||||
// x = C().attr
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
|
||||
// let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
|
||||
|
||||
// Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
if random.choice([True, False]):
|
||||
attr: str = "42"
|
||||
else:
|
||||
attr: None = None
|
||||
"#,
|
||||
)?;
|
||||
// // Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// if random.choice([True, False]):
|
||||
// attr: str = "42"
|
||||
// else:
|
||||
// attr: None = None
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
|
||||
// let events = {
|
||||
// db.clear_salsa_events();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
// db.take_salsa_events()
|
||||
// };
|
||||
// assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
|
||||
|
||||
// Add a comment; this should not trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
# comment
|
||||
if random.choice([True, False]):
|
||||
attr: str = "42"
|
||||
else:
|
||||
attr: None = None
|
||||
"#,
|
||||
)?;
|
||||
// // Add a comment; this should not trigger the type of `x` to be re-inferred
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// # comment
|
||||
// if random.choice([True, False]):
|
||||
// attr: str = "42"
|
||||
// else:
|
||||
// attr: None = None
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
// let events = {
|
||||
// db.clear_salsa_events();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
|
||||
// db.take_salsa_events()
|
||||
// };
|
||||
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
infer_expression_types,
|
||||
x_rhs_expression(&db),
|
||||
&events,
|
||||
);
|
||||
// assert_function_query_was_not_run(
|
||||
// &db,
|
||||
// infer_expression_types,
|
||||
// x_rhs_expression(&db),
|
||||
// &events,
|
||||
// );
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn dependency_implicit_class_member() -> anyhow::Result<()> {
|
||||
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
|
||||
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
|
||||
let ast = parsed_module(db, file_main).load(db);
|
||||
// Get the third statement in `main.py` (x = …) and extract the expression
|
||||
// node on the right-hand side:
|
||||
let x_rhs_node = &ast.syntax().body[2].as_assign_stmt().unwrap().value;
|
||||
// #[test]
|
||||
// fn dependency_implicit_class_member() -> anyhow::Result<()> {
|
||||
// fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
|
||||
// let file_main = system_path_to_file(db, "/src/main.py").unwrap();
|
||||
// let ast = parsed_module(db, file_main).load(db);
|
||||
// // Get the third statement in `main.py` (x = …) and extract the expression
|
||||
// // node on the right-hand side:
|
||||
// let x_rhs_node = &ast.syntax().body[2].as_assign_stmt().unwrap().value;
|
||||
|
||||
let index = semantic_index(db, file_main);
|
||||
index.expression(x_rhs_node.as_ref())
|
||||
}
|
||||
// let index = semantic_index(db, file_main);
|
||||
// index.expression(x_rhs_node.as_ref())
|
||||
// }
|
||||
|
||||
let mut db = setup_db();
|
||||
// let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.instance_attr: str = "24"
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// def __init__(self):
|
||||
// self.instance_attr: str = "24"
|
||||
|
||||
@classmethod
|
||||
def method(cls):
|
||||
cls.class_attr: int = 42
|
||||
"#,
|
||||
)?;
|
||||
db.write_dedented(
|
||||
"/src/main.py",
|
||||
r#"
|
||||
from mod import C
|
||||
C.method()
|
||||
x = C().class_attr
|
||||
"#,
|
||||
)?;
|
||||
// @classmethod
|
||||
// def method(cls):
|
||||
// cls.class_attr: int = 42
|
||||
// "#,
|
||||
// )?;
|
||||
// db.write_dedented(
|
||||
// "/src/main.py",
|
||||
// r#"
|
||||
// from mod import C
|
||||
// C.method()
|
||||
// x = C().class_attr
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int");
|
||||
// let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int");
|
||||
|
||||
// Change the type of `class_attr` to `str`; this should trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.instance_attr: str = "24"
|
||||
// // Change the type of `class_attr` to `str`; this should trigger the type of `x` to be re-inferred
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// def __init__(self):
|
||||
// self.instance_attr: str = "24"
|
||||
|
||||
@classmethod
|
||||
def method(cls):
|
||||
cls.class_attr: str = "42"
|
||||
"#,
|
||||
)?;
|
||||
// @classmethod
|
||||
// def method(cls):
|
||||
// cls.class_attr: str = "42"
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
|
||||
// let events = {
|
||||
// db.clear_salsa_events();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
|
||||
// db.take_salsa_events()
|
||||
// };
|
||||
// assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
|
||||
|
||||
// Add a comment; this should not trigger the type of `x` to be re-inferred
|
||||
db.write_dedented(
|
||||
"/src/mod.py",
|
||||
r#"
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.instance_attr: str = "24"
|
||||
// // Add a comment; this should not trigger the type of `x` to be re-inferred
|
||||
// db.write_dedented(
|
||||
// "/src/mod.py",
|
||||
// r#"
|
||||
// class C:
|
||||
// def __init__(self):
|
||||
// self.instance_attr: str = "24"
|
||||
|
||||
@classmethod
|
||||
def method(cls):
|
||||
# comment
|
||||
cls.class_attr: str = "42"
|
||||
"#,
|
||||
)?;
|
||||
// @classmethod
|
||||
// def method(cls):
|
||||
// # comment
|
||||
// cls.class_attr: str = "42"
|
||||
// "#,
|
||||
// )?;
|
||||
|
||||
let events = {
|
||||
db.clear_salsa_events();
|
||||
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
|
||||
db.take_salsa_events()
|
||||
};
|
||||
// let events = {
|
||||
// db.clear_salsa_events();
|
||||
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
|
||||
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
|
||||
// db.take_salsa_events()
|
||||
// };
|
||||
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
infer_expression_types,
|
||||
x_rhs_expression(&db),
|
||||
&events,
|
||||
);
|
||||
// assert_function_query_was_not_run(
|
||||
// &db,
|
||||
// infer_expression_types,
|
||||
// x_rhs_expression(&db),
|
||||
// &events,
|
||||
// );
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inference = infer_expression_types(self.db, expression);
|
||||
let inference = infer_expression_types(self.db, expression, false);
|
||||
|
||||
let comparator_tuples = std::iter::once(&**left)
|
||||
.chain(comparators)
|
||||
@@ -799,7 +799,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
expression: Expression<'db>,
|
||||
is_positive: bool,
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
let inference = infer_expression_types(self.db, expression);
|
||||
let inference = infer_expression_types(self.db, expression, false);
|
||||
|
||||
let callable_ty = inference.expression_type(&*expr_call.func);
|
||||
|
||||
@@ -921,7 +921,8 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
let subject = place_expr(subject.node_ref(self.db, self.module))?;
|
||||
let place = self.expect_place(&subject);
|
||||
|
||||
let ty = infer_same_file_expression_type(self.db, cls, self.module).to_instance(self.db)?;
|
||||
let ty = infer_same_file_expression_type(self.db, cls, self.module, false)
|
||||
.to_instance(self.db)?;
|
||||
|
||||
Some(NarrowingConstraints::from_iter([(place, ty)]))
|
||||
}
|
||||
@@ -934,7 +935,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
let subject = place_expr(subject.node_ref(self.db, self.module))?;
|
||||
let place = self.expect_place(&subject);
|
||||
|
||||
let ty = infer_same_file_expression_type(self.db, value, self.module);
|
||||
let ty = infer_same_file_expression_type(self.db, value, self.module, false);
|
||||
Some(NarrowingConstraints::from_iter([(place, ty)]))
|
||||
}
|
||||
|
||||
@@ -963,7 +964,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
expression: Expression<'db>,
|
||||
is_positive: bool,
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
let inference = infer_expression_types(self.db, expression);
|
||||
let inference = infer_expression_types(self.db, expression, false);
|
||||
let mut sub_constraints = expr_bool_op
|
||||
.values
|
||||
.iter()
|
||||
|
||||
@@ -17,10 +17,10 @@ use crate::{
|
||||
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
|
||||
semantic_index::{definition::Definition, use_def_map},
|
||||
types::{
|
||||
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsDisjointVisitor, KnownFunction, MaterializationKind,
|
||||
NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers,
|
||||
TypeRelation, VarianceInferable,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownFunction,
|
||||
MaterializationKind, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping,
|
||||
TypeQualifiers, TypeRelation, VarianceInferable,
|
||||
constraints::{Constraints, IteratorConstraintsExtension},
|
||||
signatures::{Parameter, Parameters},
|
||||
},
|
||||
@@ -282,7 +282,12 @@ impl<'db> ProtocolInterface<'db> {
|
||||
.map(|(name, data)| {
|
||||
(
|
||||
name.clone(),
|
||||
data.apply_type_mapping(db, type_mapping).normalized(db),
|
||||
data.apply_type_mapping_impl(
|
||||
db,
|
||||
type_mapping,
|
||||
&ApplyTypeMappingVisitor::default(),
|
||||
)
|
||||
.normalized(db),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
@@ -354,9 +359,14 @@ impl<'db> ProtocolMemberData<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
Self {
|
||||
kind: self.kind.apply_type_mapping(db, type_mapping),
|
||||
kind: self.kind.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
qualifiers: self.qualifiers,
|
||||
}
|
||||
}
|
||||
@@ -444,16 +454,21 @@ impl<'db> ProtocolMemberKind<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
ProtocolMemberKind::Method(callable) => {
|
||||
ProtocolMemberKind::Method(callable.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
ProtocolMemberKind::Property(property) => {
|
||||
ProtocolMemberKind::Property(property.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
ProtocolMemberKind::Method(callable) => ProtocolMemberKind::Method(
|
||||
callable.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
),
|
||||
ProtocolMemberKind::Property(property) => ProtocolMemberKind::Property(
|
||||
property.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
),
|
||||
ProtocolMemberKind::Other(ty) => {
|
||||
ProtocolMemberKind::Other(ty.apply_type_mapping(db, type_mapping))
|
||||
ProtocolMemberKind::Other(ty.apply_type_mapping_impl(db, type_mapping, visitor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ use crate::semantic_index::definition::Definition;
|
||||
use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension};
|
||||
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||
use crate::types::{
|
||||
BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||
IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor, TypeMapping,
|
||||
TypeRelation, VarianceInferable, todo_type,
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor,
|
||||
TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
@@ -82,15 +82,16 @@ impl<'db> CallableSignature<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_type_mapping<'a>(
|
||||
pub(crate) fn apply_type_mapping_impl<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
Self::from_overloads(
|
||||
self.overloads
|
||||
.iter()
|
||||
.map(|signature| signature.apply_type_mapping(db, type_mapping)),
|
||||
.map(|signature| signature.apply_type_mapping_impl(db, type_mapping, visitor)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -458,15 +459,26 @@ impl<'db> Signature<'db> {
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
) -> Self {
|
||||
self.apply_type_mapping_impl(db, type_mapping, &ApplyTypeMappingVisitor::default())
|
||||
}
|
||||
|
||||
pub(crate) fn apply_type_mapping_impl<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
inherited_generic_context: self.inherited_generic_context,
|
||||
definition: self.definition,
|
||||
parameters: self.parameters.apply_type_mapping(db, type_mapping),
|
||||
parameters: self
|
||||
.parameters
|
||||
.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
return_ty: self
|
||||
.return_ty
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +516,11 @@ impl<'db> Signature<'db> {
|
||||
let mut parameters = Parameters::new(self.parameters().iter().skip(1).cloned());
|
||||
let mut return_ty = self.return_ty;
|
||||
if let Some(self_type) = self_type {
|
||||
parameters = parameters.apply_type_mapping(db, &TypeMapping::BindSelf(self_type));
|
||||
parameters = parameters.apply_type_mapping_impl(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
&ApplyTypeMappingVisitor::default(),
|
||||
);
|
||||
return_ty =
|
||||
return_ty.map(|ty| ty.apply_type_mapping(db, &TypeMapping::BindSelf(self_type)));
|
||||
}
|
||||
@@ -1232,12 +1248,17 @@ impl<'db> Parameters<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
Self {
|
||||
value: self
|
||||
.value
|
||||
.iter()
|
||||
.map(|param| param.apply_type_mapping(db, type_mapping))
|
||||
.map(|param| param.apply_type_mapping_impl(db, type_mapping, visitor))
|
||||
.collect(),
|
||||
is_gradual: self.is_gradual,
|
||||
}
|
||||
@@ -1416,12 +1437,17 @@ impl<'db> Parameter<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
Self {
|
||||
annotated_type: self
|
||||
.annotated_type
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||
kind: self.kind.apply_type_mapping(db, type_mapping),
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
|
||||
kind: self.kind.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
form: self.form,
|
||||
}
|
||||
}
|
||||
@@ -1625,24 +1651,29 @@ pub(crate) enum ParameterKind<'db> {
|
||||
}
|
||||
|
||||
impl<'db> ParameterKind<'db> {
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
Self::PositionalOnly { default_type, name } => Self::PositionalOnly {
|
||||
default_type: default_type
|
||||
.as_ref()
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
|
||||
name: name.clone(),
|
||||
},
|
||||
Self::PositionalOrKeyword { default_type, name } => Self::PositionalOrKeyword {
|
||||
default_type: default_type
|
||||
.as_ref()
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
|
||||
name: name.clone(),
|
||||
},
|
||||
Self::KeywordOnly { default_type, name } => Self::KeywordOnly {
|
||||
default_type: default_type
|
||||
.as_ref()
|
||||
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
|
||||
name: name.clone(),
|
||||
},
|
||||
Self::Variadic { .. } | Self::KeywordVariadic { .. } => self.clone(),
|
||||
|
||||
@@ -212,6 +212,12 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
(Type::TypeAlias(_), _) => Ordering::Less,
|
||||
(_, Type::TypeAlias(_)) => Ordering::Greater,
|
||||
|
||||
(Type::TypedDict(left), Type::TypedDict(right)) => {
|
||||
left.defining_class().cmp(&right.defining_class())
|
||||
}
|
||||
(Type::TypedDict(_), _) => Ordering::Less,
|
||||
(_, Type::TypedDict(_)) => Ordering::Greater,
|
||||
|
||||
(Type::Union(_), _) | (_, Type::Union(_)) => {
|
||||
unreachable!("our type representation does not permit nested unions");
|
||||
}
|
||||
@@ -243,12 +249,6 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
|
||||
unreachable!("Two equal, normalized intersections should share the same Salsa ID")
|
||||
}
|
||||
|
||||
(Type::TypedDict(left), Type::TypedDict(right)) => {
|
||||
left.defining_class().cmp(&right.defining_class())
|
||||
}
|
||||
(Type::TypedDict(_), _) => Ordering::Less,
|
||||
(_, Type::TypedDict(_)) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
||||
"Unpacking target must be a list or tuple expression"
|
||||
);
|
||||
|
||||
let value_type = infer_expression_types(self.db(), value.expression())
|
||||
let value_type = infer_expression_types(self.db(), value.expression(), false)
|
||||
.expression_type(value.expression().node_ref(self.db(), self.module()));
|
||||
|
||||
let value_type = match value.kind() {
|
||||
|
||||
@@ -1 +1 @@
|
||||
91e2ed0953592795fd8c29e3005a1315bf652ffc
|
||||
2480d7e7c74493a024eaf254c5d2c6f452c80ee2
|
||||
|
||||
@@ -579,7 +579,7 @@ class Distribution(_distribution_parent):
|
||||
"""Return the 'Name' metadata for the distribution package."""
|
||||
if sys.version_info >= (3, 13):
|
||||
@property
|
||||
def origin(self) -> types.SimpleNamespace: ...
|
||||
def origin(self) -> types.SimpleNamespace | None: ...
|
||||
|
||||
class DistributionFinder(MetaPathFinder):
|
||||
"""
|
||||
|
||||
@@ -257,7 +257,7 @@ class _BaseNetwork(_IPAddressBase, Generic[_A]):
|
||||
|
||||
"""
|
||||
|
||||
def hosts(self) -> Iterator[_A]:
|
||||
def hosts(self) -> Iterator[_A] | list[_A]:
|
||||
"""Generate Iterator over usable hosts in a network.
|
||||
|
||||
This is like __iter__ except it doesn't return the network
|
||||
|
||||
@@ -13,12 +13,13 @@ of the widgets appearance lies at Themes.
|
||||
"""
|
||||
|
||||
import _tkinter
|
||||
import sys
|
||||
import tkinter
|
||||
from _typeshed import Incomplete, MaybeNone
|
||||
from collections.abc import Callable
|
||||
from _typeshed import MaybeNone
|
||||
from collections.abc import Callable, Iterable
|
||||
from tkinter.font import _FontDescription
|
||||
from typing import Any, Literal, TypedDict, overload, type_check_only
|
||||
from typing_extensions import TypeAlias
|
||||
from typing_extensions import Never, TypeAlias, Unpack
|
||||
|
||||
__all__ = [
|
||||
"Button",
|
||||
@@ -53,7 +54,7 @@ def tclobjs_to_py(adict: dict[Any, Any]) -> dict[Any, Any]:
|
||||
objects.
|
||||
"""
|
||||
|
||||
def setup_master(master=None):
|
||||
def setup_master(master: tkinter.Misc | None = None):
|
||||
"""If master is not None, itself is returned. If master is None,
|
||||
the default master is returned if there is one, otherwise a new
|
||||
master is created and returned.
|
||||
@@ -73,13 +74,80 @@ _Padding: TypeAlias = (
|
||||
# from ttk_widget (aka ttk::widget) manual page, differs from tkinter._Compound
|
||||
_TtkCompound: TypeAlias = Literal["", "text", "image", tkinter._Compound]
|
||||
|
||||
# Last item (option value to apply) varies between different options so use Any.
|
||||
# It could also be any iterable with items matching the tuple, but that case
|
||||
# hasn't been added here for consistency with _Padding above.
|
||||
_Statespec: TypeAlias = tuple[Unpack[tuple[str, ...]], Any]
|
||||
_ImageStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], tkinter._ImageSpec]
|
||||
_VsapiStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], int]
|
||||
|
||||
class _Layout(TypedDict, total=False):
|
||||
side: Literal["left", "right", "top", "bottom"]
|
||||
sticky: str # consists of letters 'n', 's', 'w', 'e', may contain repeats, may be empty
|
||||
unit: Literal[0, 1] | bool
|
||||
children: _LayoutSpec
|
||||
# Note: there seem to be some other undocumented keys sometimes
|
||||
|
||||
# This could be any sequence when passed as a parameter but will always be a list when returned.
|
||||
_LayoutSpec: TypeAlias = list[tuple[str, _Layout | None]]
|
||||
|
||||
# Keep these in sync with the appropriate methods in Style
|
||||
class _ElementCreateImageKwargs(TypedDict, total=False):
|
||||
border: _Padding
|
||||
height: tkinter._ScreenUnits
|
||||
padding: _Padding
|
||||
sticky: str
|
||||
width: tkinter._ScreenUnits
|
||||
|
||||
_ElementCreateArgsCrossPlatform: TypeAlias = (
|
||||
# Could be any sequence here but types are not homogenous so just type it as tuple
|
||||
tuple[Literal["image"], tkinter._ImageSpec, Unpack[tuple[_ImageStatespec, ...]], _ElementCreateImageKwargs]
|
||||
| tuple[Literal["from"], str, str]
|
||||
| tuple[Literal["from"], str] # (fromelement is optional)
|
||||
)
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 13):
|
||||
class _ElementCreateVsapiKwargsPadding(TypedDict, total=False):
|
||||
padding: _Padding
|
||||
|
||||
class _ElementCreateVsapiKwargsMargin(TypedDict, total=False):
|
||||
padding: _Padding
|
||||
|
||||
class _ElementCreateVsapiKwargsSize(TypedDict):
|
||||
width: tkinter._ScreenUnits
|
||||
height: tkinter._ScreenUnits
|
||||
|
||||
_ElementCreateVsapiKwargsDict: TypeAlias = (
|
||||
_ElementCreateVsapiKwargsPadding | _ElementCreateVsapiKwargsMargin | _ElementCreateVsapiKwargsSize
|
||||
)
|
||||
_ElementCreateArgs: TypeAlias = ( # noqa: Y047 # It doesn't recognise the usage below for whatever reason
|
||||
_ElementCreateArgsCrossPlatform
|
||||
| tuple[Literal["vsapi"], str, int, _ElementCreateVsapiKwargsDict]
|
||||
| tuple[Literal["vsapi"], str, int, _VsapiStatespec, _ElementCreateVsapiKwargsDict]
|
||||
)
|
||||
else:
|
||||
_ElementCreateArgs: TypeAlias = _ElementCreateArgsCrossPlatform
|
||||
_ThemeSettingsValue = TypedDict(
|
||||
"_ThemeSettingsValue",
|
||||
{
|
||||
"configure": dict[str, Any],
|
||||
"map": dict[str, Iterable[_Statespec]],
|
||||
"layout": _LayoutSpec,
|
||||
"element create": _ElementCreateArgs,
|
||||
},
|
||||
total=False,
|
||||
)
|
||||
_ThemeSettings: TypeAlias = dict[str, _ThemeSettingsValue]
|
||||
|
||||
class Style:
|
||||
"""Manipulate style database."""
|
||||
|
||||
master: Incomplete
|
||||
master: tkinter.Misc
|
||||
tk: _tkinter.TkappType
|
||||
def __init__(self, master: tkinter.Misc | None = None) -> None: ...
|
||||
def configure(self, style, query_opt=None, **kw):
|
||||
# For these methods, values given vary between options. Returned values
|
||||
# seem to be str, but this might not always be the case.
|
||||
@overload
|
||||
def configure(self, style: str) -> dict[str, Any] | None: # Returns None if no configuration.
|
||||
"""Query or sets the default value of the specified option(s) in
|
||||
style.
|
||||
|
||||
@@ -87,7 +155,12 @@ class Style:
|
||||
a sequence identifying the value for that option.
|
||||
"""
|
||||
|
||||
def map(self, style, query_opt=None, **kw):
|
||||
@overload
|
||||
def configure(self, style: str, query_opt: str, **kw: Any) -> Any: ...
|
||||
@overload
|
||||
def configure(self, style: str, query_opt: None = None, **kw: Any) -> None: ...
|
||||
@overload
|
||||
def map(self, style: str, query_opt: str) -> _Statespec:
|
||||
"""Query or sets dynamic values of the specified option(s) in
|
||||
style.
|
||||
|
||||
@@ -97,7 +170,9 @@ class Style:
|
||||
one or more states and then a value.
|
||||
"""
|
||||
|
||||
def lookup(self, style, option, state=None, default=None):
|
||||
@overload
|
||||
def map(self, style: str, query_opt: None = None, **kw: Iterable[_Statespec]) -> dict[str, _Statespec]: ...
|
||||
def lookup(self, style: str, option: str, state: Iterable[str] | None = None, default: Any | None = None) -> Any:
|
||||
"""Returns the value specified for option in style.
|
||||
|
||||
If state is specified it is expected to be a sequence of one
|
||||
@@ -105,7 +180,8 @@ class Style:
|
||||
a fallback value in case no specification for option is found.
|
||||
"""
|
||||
|
||||
def layout(self, style, layoutspec=None):
|
||||
@overload
|
||||
def layout(self, style: str, layoutspec: _LayoutSpec) -> list[Never]: # Always seems to return an empty list
|
||||
"""Define the widget layout for given style. If layoutspec is
|
||||
omitted, return the layout specification for given style.
|
||||
|
||||
@@ -139,16 +215,77 @@ class Style:
|
||||
is a LAYOUT.
|
||||
"""
|
||||
|
||||
def element_create(self, elementname, etype, *args, **kw) -> None:
|
||||
@overload
|
||||
def layout(self, style: str, layoutspec: None = None) -> _LayoutSpec: ...
|
||||
@overload
|
||||
def element_create(
|
||||
self,
|
||||
elementname: str,
|
||||
etype: Literal["image"],
|
||||
default_image: tkinter._ImageSpec,
|
||||
/,
|
||||
*imagespec: _ImageStatespec,
|
||||
border: _Padding = ...,
|
||||
height: tkinter._ScreenUnits = ...,
|
||||
padding: _Padding = ...,
|
||||
sticky: str = ...,
|
||||
width: tkinter._ScreenUnits = ...,
|
||||
) -> None:
|
||||
"""Create a new element in the current theme of given etype."""
|
||||
|
||||
def element_names(self):
|
||||
@overload
|
||||
def element_create(self, elementname: str, etype: Literal["from"], themename: str, fromelement: str = ..., /) -> None: ...
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 13): # and tk version >= 8.6
|
||||
# margin, padding, and (width + height) are mutually exclusive. width
|
||||
# and height must either both be present or not present at all. Note:
|
||||
# There are other undocumented options if you look at ttk's source code.
|
||||
@overload
|
||||
def element_create(
|
||||
self,
|
||||
elementname: str,
|
||||
etype: Literal["vsapi"],
|
||||
class_: str,
|
||||
part: int,
|
||||
vs_statespec: _VsapiStatespec = ...,
|
||||
/,
|
||||
*,
|
||||
padding: _Padding = ...,
|
||||
) -> None:
|
||||
"""Create a new element in the current theme of given etype."""
|
||||
|
||||
@overload
|
||||
def element_create(
|
||||
self,
|
||||
elementname: str,
|
||||
etype: Literal["vsapi"],
|
||||
class_: str,
|
||||
part: int,
|
||||
vs_statespec: _VsapiStatespec = ...,
|
||||
/,
|
||||
*,
|
||||
margin: _Padding = ...,
|
||||
) -> None: ...
|
||||
@overload
|
||||
def element_create(
|
||||
self,
|
||||
elementname: str,
|
||||
etype: Literal["vsapi"],
|
||||
class_: str,
|
||||
part: int,
|
||||
vs_statespec: _VsapiStatespec = ...,
|
||||
/,
|
||||
*,
|
||||
width: tkinter._ScreenUnits,
|
||||
height: tkinter._ScreenUnits,
|
||||
) -> None: ...
|
||||
|
||||
def element_names(self) -> tuple[str, ...]:
|
||||
"""Returns the list of elements defined in the current theme."""
|
||||
|
||||
def element_options(self, elementname):
|
||||
def element_options(self, elementname: str) -> tuple[str, ...]:
|
||||
"""Return the list of elementname's options."""
|
||||
|
||||
def theme_create(self, themename, parent=None, settings=None) -> None:
|
||||
def theme_create(self, themename: str, parent: str | None = None, settings: _ThemeSettings | None = None) -> None:
|
||||
"""Creates a new theme.
|
||||
|
||||
It is an error if themename already exists. If parent is
|
||||
@@ -157,7 +294,7 @@ class Style:
|
||||
they are expected to have the same syntax used for theme_settings.
|
||||
"""
|
||||
|
||||
def theme_settings(self, themename, settings) -> None:
|
||||
def theme_settings(self, themename: str, settings: _ThemeSettings) -> None:
|
||||
"""Temporarily sets the current theme to themename, apply specified
|
||||
settings and then restore the previous theme.
|
||||
|
||||
@@ -1148,7 +1285,7 @@ class Panedwindow(Widget, tkinter.PanedWindow):
|
||||
|
||||
@overload
|
||||
def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ...
|
||||
forget: Incomplete
|
||||
forget = tkinter.PanedWindow.forget
|
||||
def insert(self, pos, child, **kw) -> None:
|
||||
"""Inserts a pane at the specified positions.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PyYAML==6.0.2
|
||||
ruff==0.12.10
|
||||
ruff==0.12.11
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff
|
||||
mkdocs-redirects==1.2.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PyYAML==6.0.2
|
||||
ruff==0.12.10
|
||||
ruff==0.12.11
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material==9.5.38
|
||||
mkdocs-redirects==1.2.2
|
||||
|
||||
@@ -152,13 +152,16 @@ class FuzzResult:
|
||||
|
||||
def fuzz_code(seed: Seed, args: ResolvedCliArgs) -> FuzzResult:
|
||||
"""Return a `FuzzResult` instance describing the fuzzing result from this seed."""
|
||||
# TODO(carljm) debug slowness of this seed
|
||||
skip_check = seed in {208}
|
||||
|
||||
code = generate_random_code(seed)
|
||||
bug_found = False
|
||||
minimizer_callback: Callable[[str], bool] | None = None
|
||||
|
||||
if args.baseline_executable_path is None:
|
||||
only_new_bugs = False
|
||||
if contains_bug(
|
||||
if not skip_check and contains_bug(
|
||||
code, executable=args.executable, executable_path=args.test_executable_path
|
||||
):
|
||||
bug_found = True
|
||||
@@ -169,7 +172,7 @@ def fuzz_code(seed: Seed, args: ResolvedCliArgs) -> FuzzResult:
|
||||
)
|
||||
else:
|
||||
only_new_bugs = True
|
||||
if contains_new_bug(
|
||||
if not skip_check and contains_new_bug(
|
||||
code,
|
||||
executable=args.executable,
|
||||
test_executable_path=args.test_executable_path,
|
||||
|
||||
Reference in New Issue
Block a user