Compare commits
13 Commits
0.14.7
...
david/gene
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96e7ebb588 | ||
|
|
54c88b599d | ||
|
|
8ed96b04e4 | ||
|
|
0a2536736b | ||
|
|
6aaa9d784a | ||
|
|
d85469e94c | ||
|
|
f184132d69 | ||
|
|
96c491099f | ||
|
|
c1e6ecccc0 | ||
|
|
343c6b6287 | ||
|
|
f40ab81093 | ||
|
|
eee6f25f2e | ||
|
|
013d43a2dd |
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,40 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.7
|
||||
|
||||
Released on 2025-11-28.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bandit`\] Handle string literal bindings in suspicious-url-open-usage (`S310`) ([#21469](https://github.com/astral-sh/ruff/pull/21469))
|
||||
- \[`pylint`\] Fix `PLR1708` false positives on nested functions ([#21177](https://github.com/astral-sh/ruff/pull/21177))
|
||||
- \[`pylint`\] Fix suppression for empty dict without tuple key annotation (`PLE1141`) ([#21290](https://github.com/astral-sh/ruff/pull/21290))
|
||||
- \[`ruff`\] Add rule `RUF066` to detect unnecessary class properties ([#21535](https://github.com/astral-sh/ruff/pull/21535))
|
||||
- \[`ruff`\] Catch more dummy variable uses (`RUF052`) ([#19799](https://github.com/astral-sh/ruff/pull/19799))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [server] Set severity for non-rule diagnostics ([#21559](https://github.com/astral-sh/ruff/pull/21559))
|
||||
- \[`flake8-implicit-str-concat`\] Avoid invalid fix in (`ISC003`) ([#21517](https://github.com/astral-sh/ruff/pull/21517))
|
||||
- \[`parser`\] Fix panic when parsing IPython escape command expressions ([#21480](https://github.com/astral-sh/ruff/pull/21480))
|
||||
|
||||
### CLI
|
||||
|
||||
- Show partial fixability indicator in statistics output ([#21513](https://github.com/astral-sh/ruff/pull/21513))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@mikeleppane](https://github.com/mikeleppane)
|
||||
- [@senekor](https://github.com/senekor)
|
||||
- [@ShaharNaveh](https://github.com/ShaharNaveh)
|
||||
- [@JumboBear](https://github.com/JumboBear)
|
||||
- [@prakhar1144](https://github.com/prakhar1144)
|
||||
- [@tsvikas](https://github.com/tsvikas)
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@chirizxc](https://github.com/chirizxc)
|
||||
- [@AlexWaygood](https://github.com/AlexWaygood)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
|
||||
## 0.14.6
|
||||
|
||||
Released on 2025-11-21.
|
||||
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -2859,7 +2859,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.7"
|
||||
version = "0.14.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3117,7 +3117,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.7"
|
||||
version = "0.14.6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3472,7 +3472,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.7"
|
||||
version = "0.14.6"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -4474,7 +4474,6 @@ dependencies = [
|
||||
"quickcheck_macros",
|
||||
"ruff_annotate_snippets",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_index",
|
||||
"ruff_macros",
|
||||
"ruff_memory_usage",
|
||||
@@ -4520,7 +4519,6 @@ dependencies = [
|
||||
"lsp-types",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_macros",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
@@ -4561,7 +4559,6 @@ dependencies = [
|
||||
"path-slash",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_index",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
@@ -4603,7 +4600,6 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_notebook",
|
||||
"ruff_python_formatter",
|
||||
"ruff_source_file",
|
||||
|
||||
@@ -147,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.14.7/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.7/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.6/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.6/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -181,7 +181,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.7
|
||||
rev: v0.14.6
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.7"
|
||||
version = "0.14.6"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -34,21 +34,9 @@ struct ExpandedStatistics<'a> {
|
||||
code: Option<&'a SecondaryCode>,
|
||||
name: &'static str,
|
||||
count: usize,
|
||||
#[serde(rename = "fixable")]
|
||||
all_fixable: bool,
|
||||
fixable_count: usize,
|
||||
fixable: bool,
|
||||
}
|
||||
|
||||
impl ExpandedStatistics<'_> {
|
||||
fn any_fixable(&self) -> bool {
|
||||
self.fixable_count > 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Accumulator type for grouping diagnostics by code.
|
||||
/// Format: (`code`, `representative_diagnostic`, `total_count`, `fixable_count`)
|
||||
type DiagnosticGroup<'a> = (Option<&'a SecondaryCode>, &'a Diagnostic, usize, usize);
|
||||
|
||||
pub(crate) struct Printer {
|
||||
format: OutputFormat,
|
||||
log_level: LogLevel,
|
||||
@@ -145,7 +133,7 @@ impl Printer {
|
||||
if fixables.applicable > 0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"{fix_prefix} {} fixable with the `--fix` option.",
|
||||
"{fix_prefix} {} fixable with the --fix option.",
|
||||
fixables.applicable
|
||||
)?;
|
||||
}
|
||||
@@ -268,41 +256,35 @@ impl Printer {
|
||||
diagnostics: &Diagnostics,
|
||||
writer: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
let required_applicability = self.unsafe_fixes.required_applicability();
|
||||
let statistics: Vec<ExpandedStatistics> = diagnostics
|
||||
.inner
|
||||
.iter()
|
||||
.sorted_by_key(|diagnostic| diagnostic.secondary_code())
|
||||
.fold(vec![], |mut acc: Vec<DiagnosticGroup>, diagnostic| {
|
||||
let is_fixable = diagnostic
|
||||
.fix()
|
||||
.is_some_and(|fix| fix.applies(required_applicability));
|
||||
let code = diagnostic.secondary_code();
|
||||
|
||||
if let Some((prev_code, _prev_message, count, fixable_count)) = acc.last_mut() {
|
||||
if *prev_code == code {
|
||||
*count += 1;
|
||||
if is_fixable {
|
||||
*fixable_count += 1;
|
||||
.map(|message| (message.secondary_code(), message))
|
||||
.sorted_by_key(|(code, message)| (*code, message.fixable()))
|
||||
.fold(
|
||||
vec![],
|
||||
|mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| {
|
||||
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
|
||||
if *prev_code == code {
|
||||
*count += 1;
|
||||
return acc;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
acc.push((code, diagnostic, 1, usize::from(is_fixable)));
|
||||
acc
|
||||
})
|
||||
.iter()
|
||||
.map(
|
||||
|&(code, message, count, fixable_count)| ExpandedStatistics {
|
||||
code,
|
||||
name: message.name(),
|
||||
count,
|
||||
// Backward compatibility: `fixable` is true only when all violations are fixable.
|
||||
// See: https://github.com/astral-sh/ruff/pull/21513
|
||||
all_fixable: fixable_count == count,
|
||||
fixable_count,
|
||||
acc.push(((code, message), 1));
|
||||
acc
|
||||
},
|
||||
)
|
||||
.iter()
|
||||
.map(|&((code, message), count)| ExpandedStatistics {
|
||||
code,
|
||||
name: message.name(),
|
||||
count,
|
||||
fixable: if let Some(fix) = message.fix() {
|
||||
fix.applies(self.unsafe_fixes.required_applicability())
|
||||
} else {
|
||||
false
|
||||
},
|
||||
})
|
||||
.sorted_by_key(|statistic| Reverse(statistic.count))
|
||||
.collect();
|
||||
|
||||
@@ -326,14 +308,13 @@ impl Printer {
|
||||
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
|
||||
.max()
|
||||
.unwrap();
|
||||
let any_fixable = statistics.iter().any(ExpandedStatistics::any_fixable);
|
||||
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
|
||||
|
||||
let all_fixable = format!("[{}] ", "*".cyan());
|
||||
let partially_fixable = format!("[{}] ", "-".cyan());
|
||||
let fixable = format!("[{}] ", "*".cyan());
|
||||
let unfixable = "[ ] ";
|
||||
|
||||
// By default, we mimic Flake8's `--statistics` format.
|
||||
for statistic in &statistics {
|
||||
for statistic in statistics {
|
||||
writeln!(
|
||||
writer,
|
||||
"{:>count_width$}\t{:<code_width$}\t{}{}",
|
||||
@@ -345,10 +326,8 @@ impl Printer {
|
||||
.red()
|
||||
.bold(),
|
||||
if any_fixable {
|
||||
if statistic.all_fixable {
|
||||
&all_fixable
|
||||
} else if statistic.any_fixable() {
|
||||
&partially_fixable
|
||||
if statistic.fixable {
|
||||
&fixable
|
||||
} else {
|
||||
unfixable
|
||||
}
|
||||
|
||||
@@ -1043,7 +1043,7 @@ def mvce(keys, values):
|
||||
----- stdout -----
|
||||
1 C416 [*] unnecessary-comprehension
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
[*] 1 fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
@@ -1073,8 +1073,7 @@ def mvce(keys, values):
|
||||
"code": "C416",
|
||||
"name": "unnecessary-comprehension",
|
||||
"count": 1,
|
||||
"fixable": false,
|
||||
"fixable_count": 0
|
||||
"fixable": false
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1107,8 +1106,7 @@ def mvce(keys, values):
|
||||
"code": "C416",
|
||||
"name": "unnecessary-comprehension",
|
||||
"count": 1,
|
||||
"fixable": true,
|
||||
"fixable_count": 1
|
||||
"fixable": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1116,54 +1114,6 @@ def mvce(keys, values):
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_statistics_json_partial_fix() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args([
|
||||
"--select",
|
||||
"UP035",
|
||||
"--statistics",
|
||||
"--output-format",
|
||||
"json",
|
||||
])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd
|
||||
.pass_stdin("from typing import List, AsyncGenerator"), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
[
|
||||
{
|
||||
"code": "UP035",
|
||||
"name": "deprecated-import",
|
||||
"count": 2,
|
||||
"fixable": false,
|
||||
"fixable_count": 1
|
||||
}
|
||||
]
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_statistics_partial_fix() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--select", "UP035", "--statistics"])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd
|
||||
.pass_stdin("from typing import List, AsyncGenerator"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
2 UP035 [-] deprecated-import
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_statistics_syntax_errors() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
@@ -1860,7 +1810,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
|
||||
--> -:1:1
|
||||
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
[*] 1 fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
@@ -1903,7 +1853,7 @@ fn check_shows_unsafe_fixes_with_opt_in() {
|
||||
--> -:1:1
|
||||
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
[*] 2 fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
@@ -120,7 +120,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY310,
|
||||
},
|
||||
1070,
|
||||
600,
|
||||
);
|
||||
|
||||
static FREQTRADE: Benchmark = Benchmark::new(
|
||||
@@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
950,
|
||||
900,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.7"
|
||||
version = "0.14.6"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -45,22 +45,3 @@ urllib.request.urlopen(urllib.request.Request(url))
|
||||
# https://github.com/astral-sh/ruff/issues/15522
|
||||
map(urllib.request.urlopen, [])
|
||||
foo = urllib.request.urlopen
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/21462
|
||||
path = "https://example.com/data.csv"
|
||||
urllib.request.urlretrieve(path, "data.csv")
|
||||
url = "https://example.com/api"
|
||||
urllib.request.Request(url)
|
||||
|
||||
# Test resolved f-strings and concatenated string literals
|
||||
fstring_url = f"https://example.com/data.csv"
|
||||
urllib.request.urlopen(fstring_url)
|
||||
urllib.request.Request(fstring_url)
|
||||
|
||||
concatenated_url = "https://" + "example.com/data.csv"
|
||||
urllib.request.urlopen(concatenated_url)
|
||||
urllib.request.Request(concatenated_url)
|
||||
|
||||
nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||
urllib.request.urlopen(nested_concatenated)
|
||||
urllib.request.Request(nested_concatenated)
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import abc
|
||||
import typing
|
||||
|
||||
|
||||
class User: # Test normal class properties
|
||||
@property
|
||||
def name(self): # ERROR: No return
|
||||
f"{self.first_name} {self.last_name}"
|
||||
|
||||
@property
|
||||
def age(self): # OK: Returning something
|
||||
return 100
|
||||
|
||||
def method(self): # OK: Not a property
|
||||
x = 1
|
||||
|
||||
@property
|
||||
def nested(self): # ERROR: Property itself doesn't return
|
||||
def inner():
|
||||
return 0
|
||||
|
||||
@property
|
||||
def stub(self): ... # OK: A stub; doesn't return anything
|
||||
|
||||
|
||||
class UserMeta(metaclass=abc.ABCMeta): # Test properies inside of an ABC class
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def abstr_prop1(self): ... # OK: Abstract methods doesn't need to return anything
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def abstr_prop2(self): # OK: Abstract methods doesn't need to return anything
|
||||
"""
|
||||
A cool docstring
|
||||
"""
|
||||
|
||||
@property
|
||||
def prop1(self): # OK: Returning a value
|
||||
return 1
|
||||
|
||||
@property
|
||||
def prop2(self): # ERROR: Not returning something (even when we are inside an ABC)
|
||||
50
|
||||
|
||||
def method(self): # OK: Not a property
|
||||
x = 1
|
||||
|
||||
|
||||
def func(): # OK: Not a property
|
||||
x = 1
|
||||
|
||||
|
||||
class Proto(typing.Protocol): # Tests for a Protocol class
|
||||
@property
|
||||
def prop1(self) -> int: ... # OK: A stub property
|
||||
|
||||
|
||||
class File: # Extra tests for things like yield/yield from/raise
|
||||
@property
|
||||
def stream1(self): # OK: Yields something
|
||||
yield
|
||||
|
||||
@property
|
||||
def stream2(self): # OK: Yields from something
|
||||
yield from self.stream1
|
||||
|
||||
@property
|
||||
def children(self): # OK: Raises
|
||||
raise ValueError("File does not have children")
|
||||
@@ -17,7 +17,7 @@ crates/ruff_linter/resources/test/project/examples/docs/docs/file.py:8:5: F841 [
|
||||
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
|
||||
crates/ruff_linter/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
Found 7 errors.
|
||||
[*] 7 potentially fixable with the `--fix` option.
|
||||
[*] 7 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from the project directory itself should exhibit the same behavior:
|
||||
@@ -32,7 +32,7 @@ examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but n
|
||||
project/file.py:1:8: F401 [*] `os` imported but unused
|
||||
project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
Found 7 errors.
|
||||
[*] 7 potentially fixable with the `--fix` option.
|
||||
[*] 7 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
|
||||
@@ -43,7 +43,7 @@ files:
|
||||
docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
Found 2 errors.
|
||||
[*] 2 potentially fixable with the `--fix` option.
|
||||
[*] 2 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
|
||||
@@ -61,7 +61,7 @@ crates/ruff_linter/resources/test/project/examples/docs/docs/file.py:4:27: F401
|
||||
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
|
||||
crates/ruff_linter/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
|
||||
Found 9 errors.
|
||||
[*] 9 potentially fixable with the `--fix` option.
|
||||
[*] 9 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
|
||||
@@ -74,7 +74,7 @@ docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
Found 4 errors.
|
||||
[*] 4 potentially fixable with the `--fix` option.
|
||||
[*] 4 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Passing an excluded directory directly should report errors in the contained files:
|
||||
@@ -83,7 +83,7 @@ Passing an excluded directory directly should report errors in the contained fil
|
||||
∴ cargo run -p ruff -- check crates/ruff_linter/resources/test/project/examples/excluded/
|
||||
crates/ruff_linter/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 potentially fixable with the `--fix` option.
|
||||
[*] 1 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Unless we `--force-exclude`:
|
||||
|
||||
@@ -347,9 +347,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.is_rule_enabled(Rule::InvalidArgumentName) {
|
||||
pep8_naming::rules::invalid_argument_name_function(checker, function_def);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PropertyWithoutReturn) {
|
||||
ruff::rules::property_without_return(checker, function_def);
|
||||
}
|
||||
}
|
||||
Stmt::Return(_) => {
|
||||
if checker.is_rule_enabled(Rule::ReturnInInit) {
|
||||
|
||||
@@ -1058,7 +1058,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "063") => rules::ruff::rules::AccessAnnotationsFromClassDict,
|
||||
(Ruff, "064") => rules::ruff::rules::NonOctalPermissions,
|
||||
(Ruff, "065") => rules::ruff::rules::LoggingEagerConversion,
|
||||
(Ruff, "066") => rules::ruff::rules::PropertyWithoutReturn,
|
||||
|
||||
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
|
||||
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
|
||||
|
||||
@@ -279,10 +279,3 @@ pub(crate) const fn is_extended_snmp_api_path_detection_enabled(settings: &Linte
|
||||
pub(crate) const fn is_enumerate_for_loop_int_index_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/21469
|
||||
pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::assert_diagnostics;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, assert_diagnostics_diff};
|
||||
|
||||
#[test_case(Rule::Assert, Path::new("S101.py"))]
|
||||
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"))]
|
||||
@@ -112,19 +112,14 @@ mod tests {
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
|
||||
assert_diagnostics_diff!(
|
||||
snapshot,
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_bandit").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Disabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
}
|
||||
);
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,11 @@
|
||||
use itertools::Either;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operator};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::typing::find_binding_value;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::{
|
||||
is_s310_resolve_string_literal_bindings_enabled, is_suspicious_function_reference_enabled,
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::preview::is_suspicious_function_reference_enabled;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for calls to `pickle` functions or modules that wrap them.
|
||||
@@ -1021,25 +1016,6 @@ fn suspicious_function(
|
||||
|| has_prefix(chars.skip_while(|c| c.is_whitespace()), "https://")
|
||||
}
|
||||
|
||||
/// Resolves `expr` to its binding and checks if the resolved expression starts with an HTTP or HTTPS prefix.
|
||||
fn expression_starts_with_http_prefix(
|
||||
expr: &Expr,
|
||||
semantic: &SemanticModel,
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
let resolved_expression = if is_s310_resolve_string_literal_bindings_enabled(settings)
|
||||
&& let Some(name_expr) = expr.as_name_expr()
|
||||
&& let Some(binding_id) = semantic.only_binding(name_expr)
|
||||
&& let Some(value) = find_binding_value(semantic.binding(binding_id), semantic)
|
||||
{
|
||||
value
|
||||
} else {
|
||||
expr
|
||||
};
|
||||
|
||||
leading_chars(resolved_expression).is_some_and(has_http_prefix)
|
||||
}
|
||||
|
||||
/// Return the leading characters for an expression, if it's a string literal, f-string, or
|
||||
/// string concatenation.
|
||||
fn leading_chars(expr: &Expr) -> Option<impl Iterator<Item = char> + Clone + '_> {
|
||||
@@ -1163,19 +1139,17 @@ fn suspicious_function(
|
||||
// URLOpen (`Request`)
|
||||
["urllib", "request", "Request"] | ["six", "moves", "urllib", "request", "Request"] => {
|
||||
if let Some(arguments) = arguments {
|
||||
// If the `url` argument is a string literal (including resolved bindings), allow `http` and `https` schemes.
|
||||
// If the `url` argument is a string literal or an f-string, allow `http` and `https` schemes.
|
||||
if arguments.args.iter().all(|arg| !arg.is_starred_expr())
|
||||
&& arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.all(|keyword| keyword.arg.is_some())
|
||||
{
|
||||
if let Some(url_expr) = arguments.find_argument_value("url", 0)
|
||||
&& expression_starts_with_http_prefix(
|
||||
url_expr,
|
||||
checker.semantic(),
|
||||
checker.settings(),
|
||||
)
|
||||
if arguments
|
||||
.find_argument_value("url", 0)
|
||||
.and_then(leading_chars)
|
||||
.is_some_and(has_http_prefix)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1212,25 +1186,19 @@ fn suspicious_function(
|
||||
name.segments() == ["urllib", "request", "Request"]
|
||||
})
|
||||
{
|
||||
if let Some(url_expr) = arguments.find_argument_value("url", 0)
|
||||
&& expression_starts_with_http_prefix(
|
||||
url_expr,
|
||||
checker.semantic(),
|
||||
checker.settings(),
|
||||
)
|
||||
if arguments
|
||||
.find_argument_value("url", 0)
|
||||
.and_then(leading_chars)
|
||||
.is_some_and(has_http_prefix)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the `url` argument is a string literal (including resolved bindings), allow `http` and `https` schemes.
|
||||
// If the `url` argument is a string literal, allow `http` and `https` schemes.
|
||||
Some(expr) => {
|
||||
if expression_starts_with_http_prefix(
|
||||
expr,
|
||||
checker.semantic(),
|
||||
checker.settings(),
|
||||
) {
|
||||
if leading_chars(expr).is_some_and(has_http_prefix) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,84 +254,3 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
|
||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:51:1
|
||||
|
|
||||
49 | # https://github.com/astral-sh/ruff/issues/21462
|
||||
50 | path = "https://example.com/data.csv"
|
||||
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 | url = "https://example.com/api"
|
||||
53 | urllib.request.Request(url)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:53:1
|
||||
|
|
||||
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||
52 | url = "https://example.com/api"
|
||||
53 | urllib.request.Request(url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
54 |
|
||||
55 | # Test resolved f-strings and concatenated string literals
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:57:1
|
||||
|
|
||||
55 | # Test resolved f-strings and concatenated string literals
|
||||
56 | fstring_url = f"https://example.com/data.csv"
|
||||
57 | urllib.request.urlopen(fstring_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 | urllib.request.Request(fstring_url)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:58:1
|
||||
|
|
||||
56 | fstring_url = f"https://example.com/data.csv"
|
||||
57 | urllib.request.urlopen(fstring_url)
|
||||
58 | urllib.request.Request(fstring_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
59 |
|
||||
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:61:1
|
||||
|
|
||||
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||
61 | urllib.request.urlopen(concatenated_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
62 | urllib.request.Request(concatenated_url)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:62:1
|
||||
|
|
||||
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||
61 | urllib.request.urlopen(concatenated_url)
|
||||
62 | urllib.request.Request(concatenated_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
63 |
|
||||
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:65:1
|
||||
|
|
||||
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||
65 | urllib.request.urlopen(nested_concatenated)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
66 | urllib.request.Request(nested_concatenated)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:66:1
|
||||
|
|
||||
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||
65 | urllib.request.urlopen(nested_concatenated)
|
||||
66 | urllib.request.Request(nested_concatenated)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
||||
--> S301.py:3:1
|
||||
|
|
||||
1 | import pickle
|
||||
2 |
|
||||
3 | pickle.loads()
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 2
|
||||
|
||||
--- Added ---
|
||||
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
||||
--> S301.py:7:5
|
||||
|
|
||||
@@ -19,7 +19,6 @@ S301 `pickle` and modules that wrap it can be unsafe when used to deserialize un
|
||||
8 | foo = pickle.load
|
||||
|
|
||||
|
||||
|
||||
S301 `pickle` and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue
|
||||
--> S301.py:8:7
|
||||
|
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||
--> S307.py:3:7
|
||||
|
|
||||
1 | import os
|
||||
2 |
|
||||
3 | print(eval("1+1")) # S307
|
||||
| ^^^^^^^^^^^
|
||||
4 | print(eval("os.getcwd()")) # S307
|
||||
|
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 2
|
||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||
--> S307.py:4:7
|
||||
|
|
||||
3 | print(eval("1+1")) # S307
|
||||
4 | print(eval("os.getcwd()")) # S307
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
--- Added ---
|
||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||
--> S307.py:16:5
|
||||
|
|
||||
@@ -19,7 +28,6 @@ S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||
17 | foo = eval
|
||||
|
|
||||
|
||||
|
||||
S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||
--> S307.py:17:7
|
||||
|
|
||||
|
||||
@@ -1,37 +1,60 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 2
|
||||
Added: 4
|
||||
|
||||
--- Removed ---
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:16:1
|
||||
|
|
||||
16 | @mark_safe
|
||||
| ^^^^^^^^^^
|
||||
17 | def some_func():
|
||||
18 | return '<script>alert("evil!")</script>'
|
||||
|
|
||||
|
||||
--> S308.py:6:5
|
||||
|
|
||||
4 | def bad_func():
|
||||
5 | inject = "harmful_input"
|
||||
6 | mark_safe(inject)
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
7 | mark_safe("I will add" + inject + "to my string")
|
||||
8 | mark_safe("I will add %s to my string" % inject)
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:36:1
|
||||
--> S308.py:7:5
|
||||
|
|
||||
5 | inject = "harmful_input"
|
||||
6 | mark_safe(inject)
|
||||
7 | mark_safe("I will add" + inject + "to my string")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | mark_safe("I will add %s to my string" % inject)
|
||||
9 | mark_safe("I will add {} to my string".format(inject))
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:8:5
|
||||
|
|
||||
36 | @mark_safe
|
||||
| ^^^^^^^^^^
|
||||
37 | def some_func():
|
||||
38 | return '<script>alert("evil!")</script>'
|
||||
6 | mark_safe(inject)
|
||||
7 | mark_safe("I will add" + inject + "to my string")
|
||||
8 | mark_safe("I will add %s to my string" % inject)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
9 | mark_safe("I will add {} to my string".format(inject))
|
||||
10 | mark_safe(f"I will add {inject} to my string")
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:9:5
|
||||
|
|
||||
7 | mark_safe("I will add" + inject + "to my string")
|
||||
8 | mark_safe("I will add %s to my string" % inject)
|
||||
9 | mark_safe("I will add {} to my string".format(inject))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
10 | mark_safe(f"I will add {inject} to my string")
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:10:5
|
||||
|
|
||||
8 | mark_safe("I will add %s to my string" % inject)
|
||||
9 | mark_safe("I will add {} to my string".format(inject))
|
||||
10 | mark_safe(f"I will add {inject} to my string")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
11 |
|
||||
12 | def good_func():
|
||||
|
|
||||
|
||||
--- Added ---
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:16:2
|
||||
|
|
||||
@@ -41,6 +64,59 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
18 | return '<script>alert("evil!")</script>'
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:26:5
|
||||
|
|
||||
24 | def bad_func():
|
||||
25 | inject = "harmful_input"
|
||||
26 | mark_safe(inject)
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
27 | mark_safe("I will add" + inject + "to my string")
|
||||
28 | mark_safe("I will add %s to my string" % inject)
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:27:5
|
||||
|
|
||||
25 | inject = "harmful_input"
|
||||
26 | mark_safe(inject)
|
||||
27 | mark_safe("I will add" + inject + "to my string")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
28 | mark_safe("I will add %s to my string" % inject)
|
||||
29 | mark_safe("I will add {} to my string".format(inject))
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:28:5
|
||||
|
|
||||
26 | mark_safe(inject)
|
||||
27 | mark_safe("I will add" + inject + "to my string")
|
||||
28 | mark_safe("I will add %s to my string" % inject)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 | mark_safe("I will add {} to my string".format(inject))
|
||||
30 | mark_safe(f"I will add {inject} to my string")
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:29:5
|
||||
|
|
||||
27 | mark_safe("I will add" + inject + "to my string")
|
||||
28 | mark_safe("I will add %s to my string" % inject)
|
||||
29 | mark_safe("I will add {} to my string".format(inject))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
30 | mark_safe(f"I will add {inject} to my string")
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:30:5
|
||||
|
|
||||
28 | mark_safe("I will add %s to my string" % inject)
|
||||
29 | mark_safe("I will add {} to my string".format(inject))
|
||||
30 | mark_safe(f"I will add {inject} to my string")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
31 |
|
||||
32 | def good_func():
|
||||
|
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:36:2
|
||||
@@ -51,7 +127,6 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
38 | return '<script>alert("evil!")</script>'
|
||||
|
|
||||
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:42:5
|
||||
|
|
||||
@@ -61,7 +136,6 @@ S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
43 | foo = mark_safe
|
||||
|
|
||||
|
||||
|
||||
S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
|
||||
--> S308.py:43:7
|
||||
|
|
||||
|
||||
@@ -1,106 +1,260 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 8
|
||||
Added: 2
|
||||
|
||||
--- Removed ---
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:51:1
|
||||
|
|
||||
49 | # https://github.com/astral-sh/ruff/issues/21462
|
||||
50 | path = "https://example.com/data.csv"
|
||||
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 | url = "https://example.com/api"
|
||||
53 | urllib.request.Request(url)
|
||||
|
|
||||
|
||||
--> S310.py:6:1
|
||||
|
|
||||
4 | urllib.request.urlopen(url=f'http://www.google.com')
|
||||
5 | urllib.request.urlopen(url='http://' + 'www' + '.google.com')
|
||||
6 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | urllib.request.urlopen(url=f'http://www.google.com', **kwargs)
|
||||
8 | urllib.request.urlopen('http://www.google.com')
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:53:1
|
||||
--> S310.py:7:1
|
||||
|
|
||||
5 | urllib.request.urlopen(url='http://' + 'www' + '.google.com')
|
||||
6 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
|
||||
7 | urllib.request.urlopen(url=f'http://www.google.com', **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | urllib.request.urlopen('http://www.google.com')
|
||||
9 | urllib.request.urlopen(f'http://www.google.com')
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:10:1
|
||||
|
|
||||
51 | urllib.request.urlretrieve(path, "data.csv")
|
||||
52 | url = "https://example.com/api"
|
||||
53 | urllib.request.Request(url)
|
||||
8 | urllib.request.urlopen('http://www.google.com')
|
||||
9 | urllib.request.urlopen(f'http://www.google.com')
|
||||
10 | urllib.request.urlopen('file:///foo/bar/baz')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
11 | urllib.request.urlopen(url)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:11:1
|
||||
|
|
||||
9 | urllib.request.urlopen(f'http://www.google.com')
|
||||
10 | urllib.request.urlopen('file:///foo/bar/baz')
|
||||
11 | urllib.request.urlopen(url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
54 |
|
||||
55 | # Test resolved f-strings and concatenated string literals
|
||||
12 |
|
||||
13 | urllib.request.Request(url='http://www.google.com')
|
||||
|
|
||||
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:57:1
|
||||
--> S310.py:16:1
|
||||
|
|
||||
55 | # Test resolved f-strings and concatenated string literals
|
||||
56 | fstring_url = f"https://example.com/data.csv"
|
||||
57 | urllib.request.urlopen(fstring_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 | urllib.request.Request(fstring_url)
|
||||
14 | urllib.request.Request(url=f'http://www.google.com')
|
||||
15 | urllib.request.Request(url='http://' + 'www' + '.google.com')
|
||||
16 | urllib.request.Request(url='http://www.google.com', **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
17 | urllib.request.Request(url=f'http://www.google.com', **kwargs)
|
||||
18 | urllib.request.Request('http://www.google.com')
|
||||
|
|
||||
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:58:1
|
||||
--> S310.py:17:1
|
||||
|
|
||||
56 | fstring_url = f"https://example.com/data.csv"
|
||||
57 | urllib.request.urlopen(fstring_url)
|
||||
58 | urllib.request.Request(fstring_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
59 |
|
||||
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||
15 | urllib.request.Request(url='http://' + 'www' + '.google.com')
|
||||
16 | urllib.request.Request(url='http://www.google.com', **kwargs)
|
||||
17 | urllib.request.Request(url=f'http://www.google.com', **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
18 | urllib.request.Request('http://www.google.com')
|
||||
19 | urllib.request.Request(f'http://www.google.com')
|
||||
|
|
||||
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:61:1
|
||||
--> S310.py:20:1
|
||||
|
|
||||
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||
61 | urllib.request.urlopen(concatenated_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
62 | urllib.request.Request(concatenated_url)
|
||||
18 | urllib.request.Request('http://www.google.com')
|
||||
19 | urllib.request.Request(f'http://www.google.com')
|
||||
20 | urllib.request.Request('file:///foo/bar/baz')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
21 | urllib.request.Request(url)
|
||||
|
|
||||
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:62:1
|
||||
--> S310.py:21:1
|
||||
|
|
||||
60 | concatenated_url = "https://" + "example.com/data.csv"
|
||||
61 | urllib.request.urlopen(concatenated_url)
|
||||
62 | urllib.request.Request(concatenated_url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
63 |
|
||||
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||
19 | urllib.request.Request(f'http://www.google.com')
|
||||
20 | urllib.request.Request('file:///foo/bar/baz')
|
||||
21 | urllib.request.Request(url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
22 |
|
||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
||||
|
|
||||
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:65:1
|
||||
--> S310.py:23:1
|
||||
|
|
||||
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||
65 | urllib.request.urlopen(nested_concatenated)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
66 | urllib.request.Request(nested_concatenated)
|
||||
21 | urllib.request.Request(url)
|
||||
22 |
|
||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
||||
|
|
||||
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:66:1
|
||||
--> S310.py:24:1
|
||||
|
|
||||
64 | nested_concatenated = "http://" + "example.com" + "/data.csv"
|
||||
65 | urllib.request.urlopen(nested_concatenated)
|
||||
66 | urllib.request.Request(nested_concatenated)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:25:1
|
||||
|
|
||||
23 | urllib.request.URLopener().open(fullurl='http://www.google.com')
|
||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:26:1
|
||||
|
|
||||
24 | urllib.request.URLopener().open(fullurl=f'http://www.google.com')
|
||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:27:1
|
||||
|
|
||||
25 | urllib.request.URLopener().open(fullurl='http://' + 'www' + '.google.com')
|
||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:28:1
|
||||
|
|
||||
26 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:29:1
|
||||
|
|
||||
27 | urllib.request.URLopener().open(fullurl=f'http://www.google.com', **kwargs)
|
||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:30:1
|
||||
|
|
||||
28 | urllib.request.URLopener().open('http://www.google.com')
|
||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
||||
32 | urllib.request.URLopener().open(url)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:31:1
|
||||
|
|
||||
29 | urllib.request.URLopener().open(f'http://www.google.com')
|
||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
32 | urllib.request.URLopener().open(url)
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:32:1
|
||||
|
|
||||
30 | urllib.request.URLopener().open('http://' + 'www' + '.google.com')
|
||||
31 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
||||
32 | urllib.request.URLopener().open(url)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
33 |
|
||||
34 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'))
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:37:1
|
||||
|
|
||||
35 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'))
|
||||
36 | urllib.request.urlopen(url=urllib.request.Request('http://' + 'www' + '.google.com'))
|
||||
37 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
38 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'), **kwargs)
|
||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:38:1
|
||||
|
|
||||
36 | urllib.request.urlopen(url=urllib.request.Request('http://' + 'www' + '.google.com'))
|
||||
37 | urllib.request.urlopen(url=urllib.request.Request('http://www.google.com'), **kwargs)
|
||||
38 | urllib.request.urlopen(url=urllib.request.Request(f'http://www.google.com'), **kwargs)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:41:1
|
||||
|
|
||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:41:24
|
||||
|
|
||||
39 | urllib.request.urlopen(urllib.request.Request('http://www.google.com'))
|
||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:42:1
|
||||
|
|
||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:42:24
|
||||
|
|
||||
40 | urllib.request.urlopen(urllib.request.Request(f'http://www.google.com'))
|
||||
41 | urllib.request.urlopen(urllib.request.Request('file:///foo/bar/baz'))
|
||||
42 | urllib.request.urlopen(urllib.request.Request(url))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
--- Added ---
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:46:5
|
||||
|
|
||||
@@ -110,7 +264,6 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
|
||||
47 | foo = urllib.request.urlopen
|
||||
|
|
||||
|
||||
|
||||
S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
||||
--> S310.py:47:7
|
||||
|
|
||||
@@ -118,6 +271,4 @@ S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom sch
|
||||
46 | map(urllib.request.urlopen, [])
|
||||
47 | foo = urllib.request.urlopen
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
48 |
|
||||
49 | # https://github.com/astral-sh/ruff/issues/21462
|
||||
|
|
||||
|
||||
@@ -1,15 +1,103 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:10:1
|
||||
|
|
||||
9 | # Errors
|
||||
10 | random.Random()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
11 | random.random()
|
||||
12 | random.randrange()
|
||||
|
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 2
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:11:1
|
||||
|
|
||||
9 | # Errors
|
||||
10 | random.Random()
|
||||
11 | random.random()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
12 | random.randrange()
|
||||
13 | random.randint()
|
||||
|
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:12:1
|
||||
|
|
||||
10 | random.Random()
|
||||
11 | random.random()
|
||||
12 | random.randrange()
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
13 | random.randint()
|
||||
14 | random.choice()
|
||||
|
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:13:1
|
||||
|
|
||||
11 | random.random()
|
||||
12 | random.randrange()
|
||||
13 | random.randint()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
14 | random.choice()
|
||||
15 | random.choices()
|
||||
|
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:14:1
|
||||
|
|
||||
12 | random.randrange()
|
||||
13 | random.randint()
|
||||
14 | random.choice()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
15 | random.choices()
|
||||
16 | random.uniform()
|
||||
|
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:15:1
|
||||
|
|
||||
13 | random.randint()
|
||||
14 | random.choice()
|
||||
15 | random.choices()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
16 | random.uniform()
|
||||
17 | random.triangular()
|
||||
|
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:16:1
|
||||
|
|
||||
14 | random.choice()
|
||||
15 | random.choices()
|
||||
16 | random.uniform()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
17 | random.triangular()
|
||||
18 | random.randbytes()
|
||||
|
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:17:1
|
||||
|
|
||||
15 | random.choices()
|
||||
16 | random.uniform()
|
||||
17 | random.triangular()
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
18 | random.randbytes()
|
||||
|
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:18:1
|
||||
|
|
||||
16 | random.uniform()
|
||||
17 | random.triangular()
|
||||
18 | random.randbytes()
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
19 |
|
||||
20 | # Unrelated
|
||||
|
|
||||
|
||||
--- Added ---
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:26:5
|
||||
|
|
||||
@@ -19,7 +107,6 @@ S311 Standard pseudo-random generators are not suitable for cryptographic purpos
|
||||
27 | foo = random.randrange
|
||||
|
|
||||
|
||||
|
||||
S311 Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
--> S311.py:27:7
|
||||
|
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
--> S312.py:3:1
|
||||
|
|
||||
1 | from telnetlib import Telnet
|
||||
2 |
|
||||
3 | Telnet("localhost", 23)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 3
|
||||
|
||||
--- Added ---
|
||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
--> S312.py:7:5
|
||||
|
|
||||
@@ -19,7 +19,6 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
8 | foo = Telnet
|
||||
|
|
||||
|
||||
|
||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
--> S312.py:8:7
|
||||
|
|
||||
@@ -31,7 +30,6 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
10 | import telnetlib
|
||||
|
|
||||
|
||||
|
||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
--> S312.py:11:5
|
||||
|
|
||||
@@ -41,3 +39,13 @@ S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
12 |
|
||||
13 | from typing import Annotated
|
||||
|
|
||||
|
||||
S312 Telnet is considered insecure. Use SSH or some other encrypted protocol.
|
||||
--> S312.py:14:24
|
||||
|
|
||||
13 | from typing import Annotated
|
||||
14 | foo: Annotated[Telnet, telnetlib.Telnet()]
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
15 |
|
||||
16 | def _() -> Telnet: ...
|
||||
|
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:3:25
|
||||
|
|
||||
1 | from pysnmp.hlapi import CommunityData
|
||||
2 |
|
||||
3 | CommunityData("public", mpModel=0) # S508
|
||||
| ^^^^^^^^^
|
||||
4 | CommunityData("public", mpModel=1) # S508
|
||||
|
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 8
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:4:25
|
||||
|
|
||||
3 | CommunityData("public", mpModel=0) # S508
|
||||
4 | CommunityData("public", mpModel=1) # S508
|
||||
| ^^^^^^^^^
|
||||
5 |
|
||||
6 | CommunityData("public", mpModel=2) # OK
|
||||
|
|
||||
|
||||
--- Added ---
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:18:46
|
||||
|
|
||||
@@ -21,7 +32,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508
|
||||
|
|
||||
|
||||
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:19:58
|
||||
|
|
||||
@@ -32,7 +42,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508
|
||||
|
|
||||
|
||||
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:20:53
|
||||
|
|
||||
@@ -44,7 +53,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508
|
||||
|
|
||||
|
||||
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:21:45
|
||||
|
|
||||
@@ -56,7 +64,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508
|
||||
|
|
||||
|
||||
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:22:58
|
||||
|
|
||||
@@ -68,7 +75,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508
|
||||
|
|
||||
|
||||
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:23:53
|
||||
|
|
||||
@@ -80,7 +86,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
||||
|
|
||||
|
||||
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:24:45
|
||||
|
|
||||
@@ -91,7 +96,6 @@ S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508
|
||||
|
|
||||
|
||||
|
||||
S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
--> S508.py:25:43
|
||||
|
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||
--> S509.py:4:12
|
||||
|
|
||||
4 | insecure = UsmUserData("securityName") # S509
|
||||
| ^^^^^^^^^^^
|
||||
5 | auth_no_priv = UsmUserData("securityName", "authName") # S509
|
||||
|
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 4
|
||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||
--> S509.py:5:16
|
||||
|
|
||||
4 | insecure = UsmUserData("securityName") # S509
|
||||
5 | auth_no_priv = UsmUserData("securityName", "authName") # S509
|
||||
| ^^^^^^^^^^^
|
||||
6 |
|
||||
7 | less_insecure = UsmUserData("securityName", "authName", "privName") # OK
|
||||
|
|
||||
|
||||
--- Added ---
|
||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||
--> S509.py:15:1
|
||||
|
|
||||
@@ -21,7 +30,6 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
|
||||
17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509
|
||||
|
|
||||
|
||||
|
||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||
--> S509.py:16:1
|
||||
|
|
||||
@@ -32,7 +40,6 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
|
||||
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
||||
|
|
||||
|
||||
|
||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||
--> S509.py:17:1
|
||||
|
|
||||
@@ -43,7 +50,6 @@ S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv`
|
||||
18 | pysnmp.hlapi.auth.UsmUserData("user") # S509
|
||||
|
|
||||
|
||||
|
||||
S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure.
|
||||
--> S509.py:18:1
|
||||
|
|
||||
|
||||
@@ -25,11 +25,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// keyword-only argument, to force callers to be explicit when providing
|
||||
/// the argument.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override],
|
||||
/// since changing the signature of a subclass method that overrides a
|
||||
/// superclass method may cause type checkers to complain about a violation of
|
||||
/// the Liskov Substitution Principle.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
@@ -94,8 +89,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.127")]
|
||||
pub(crate) struct BooleanDefaultValuePositionalArgument;
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// the argument.
|
||||
///
|
||||
/// Dunder methods that define operators are exempt from this rule, as are
|
||||
/// setters and [`@override`][override] definitions.
|
||||
/// setters and `@override` definitions.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@@ -93,8 +93,6 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.127")]
|
||||
pub(crate) struct BooleanTypeHintPositionalArgument;
|
||||
|
||||
@@ -17,8 +17,6 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
/// non-obvious errors, as readers may mistake the argument for the
|
||||
/// builtin and vice versa.
|
||||
///
|
||||
/// Function definitions decorated with [`@override`][override] or
|
||||
/// [`@overload`][overload] are exempt from this rule by default.
|
||||
/// Builtins can be marked as exceptions to this rule via the
|
||||
/// [`lint.flake8-builtins.ignorelist`] configuration option.
|
||||
///
|
||||
@@ -50,9 +48,6 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
/// ## References
|
||||
/// - [_Is it bad practice to use a built-in function name as an attribute or method identifier?_](https://stackoverflow.com/questions/9109333/is-it-bad-practice-to-use-a-built-in-function-name-as-an-attribute-or-method-ide)
|
||||
/// - [_Why is it a bad idea to name a variable `id` in Python?_](https://stackoverflow.com/questions/77552/id-is-a-bad-variable-name-in-python)
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
/// [overload]: https://docs.python.org/3/library/typing.html#typing.overload
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.48")]
|
||||
pub(crate) struct BuiltinArgumentShadowing {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::whitespace::trailing_comment_start_offset;
|
||||
use ruff_python_ast::{Expr, ExprStringLiteral, Stmt, StmtExpr};
|
||||
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -99,7 +101,7 @@ pub(crate) fn unnecessary_placeholder(checker: &Checker, body: &[Stmt]) {
|
||||
// Ellipses are significant in protocol methods and abstract methods.
|
||||
// Specifically, Pyright uses the presence of an ellipsis to indicate that
|
||||
// a method is a stub, rather than a default implementation.
|
||||
if checker.semantic().in_protocol_or_abstract_method() {
|
||||
if in_protocol_or_abstract_method(checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
Placeholder::Ellipsis
|
||||
@@ -161,3 +163,21 @@ impl std::fmt::Display for Placeholder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the [`SemanticModel`] is in a `typing.Protocol` subclass or an abstract
|
||||
/// method.
|
||||
fn in_protocol_or_abstract_method(semantic: &SemanticModel) -> bool {
|
||||
semantic.current_scopes().any(|scope| match scope.kind {
|
||||
ScopeKind::Class(class_def) => class_def
|
||||
.bases()
|
||||
.iter()
|
||||
.any(|base| semantic.match_typing_expr(map_subscript(base), "Protocol")),
|
||||
ScopeKind::Function(function_def) => {
|
||||
ruff_python_semantic::analyze::visibility::is_abstract(
|
||||
&function_def.decorator_list,
|
||||
semantic,
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -60,16 +60,6 @@ impl Violation for UnusedFunctionArgument {
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override].
|
||||
/// Removing a parameter from a subclass method (or changing a parameter's
|
||||
/// name) may cause type checkers to complain about a violation of the Liskov
|
||||
/// Substitution Principle if it means that the method now incompatibly
|
||||
/// overrides a method defined on a superclass. Explicitly decorating an
|
||||
/// overriding method with `@override` signals to Ruff that the method is
|
||||
/// intended to override a superclass method and that a type checker will
|
||||
/// enforce that it does so; Ruff therefore knows that it should not enforce
|
||||
/// rules about unused arguments on such methods.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Class:
|
||||
@@ -86,8 +76,6 @@ impl Violation for UnusedFunctionArgument {
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.168")]
|
||||
pub(crate) struct UnusedMethodArgument {
|
||||
@@ -113,16 +101,6 @@ impl Violation for UnusedMethodArgument {
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override].
|
||||
/// Removing a parameter from a subclass method (or changing a parameter's
|
||||
/// name) may cause type checkers to complain about a violation of the Liskov
|
||||
/// Substitution Principle if it means that the method now incompatibly
|
||||
/// overrides a method defined on a superclass. Explicitly decorating an
|
||||
/// overriding method with `@override` signals to Ruff that the method is
|
||||
/// intended to override a superclass method and that a type checker will
|
||||
/// enforce that it does so; Ruff therefore knows that it should not enforce
|
||||
/// rules about unused arguments on such methods.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Class:
|
||||
@@ -141,8 +119,6 @@ impl Violation for UnusedMethodArgument {
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.168")]
|
||||
pub(crate) struct UnusedClassMethodArgument {
|
||||
@@ -168,16 +144,6 @@ impl Violation for UnusedClassMethodArgument {
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override].
|
||||
/// Removing a parameter from a subclass method (or changing a parameter's
|
||||
/// name) may cause type checkers to complain about a violation of the Liskov
|
||||
/// Substitution Principle if it means that the method now incompatibly
|
||||
/// overrides a method defined on a superclass. Explicitly decorating an
|
||||
/// overriding method with `@override` signals to Ruff that the method is
|
||||
/// intended to override a superclass method, and that a type checker will
|
||||
/// enforce that it does so; Ruff therefore knows that it should not enforce
|
||||
/// rules about unused arguments on such methods.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Class:
|
||||
@@ -196,8 +162,6 @@ impl Violation for UnusedClassMethodArgument {
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.168")]
|
||||
pub(crate) struct UnusedStaticMethodArgument {
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::checkers::ast::Checker;
|
||||
/// > mixedCase is allowed only in contexts where that’s already the
|
||||
/// > prevailing style (e.g. threading.py), to retain backwards compatibility.
|
||||
///
|
||||
/// Methods decorated with [`@typing.override`][override] are ignored.
|
||||
/// Methods decorated with `@typing.override` are ignored.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -43,8 +43,6 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#function-and-method-arguments
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.77")]
|
||||
pub(crate) struct InvalidArgumentName {
|
||||
|
||||
@@ -24,11 +24,6 @@ use crate::rules::pep8_naming::settings::IgnoreNames;
|
||||
/// to ignore all functions starting with `test_` from this rule, set the
|
||||
/// [`lint.pep8-naming.extend-ignore-names`] option to `["test_*"]`.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override].
|
||||
/// Explicitly decorating a method with `@override` signals to Ruff that the method is intended
|
||||
/// to override a superclass method, and that a type checker will enforce that it does so. Ruff
|
||||
/// therefore knows that it should not enforce naming conventions on such methods.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def myFunction():
|
||||
@@ -46,7 +41,6 @@ use crate::rules::pep8_naming::settings::IgnoreNames;
|
||||
/// - `lint.pep8-naming.extend-ignore-names`
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#function-and-variable-names
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.77")]
|
||||
pub(crate) struct InvalidFunctionName {
|
||||
|
||||
@@ -169,12 +169,7 @@ impl Violation for UndocumentedPublicClass {
|
||||
/// If the codebase adheres to a standard format for method docstrings, follow
|
||||
/// that format for consistency.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override],
|
||||
/// since it is a common practice to document a method on a superclass but not
|
||||
/// on an overriding method in a subclass.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// class Cat(Animal):
|
||||
/// def greet(self, happy: bool = True):
|
||||
@@ -185,7 +180,6 @@ impl Violation for UndocumentedPublicClass {
|
||||
/// ```
|
||||
///
|
||||
/// Use instead (in the NumPy docstring format):
|
||||
///
|
||||
/// ```python
|
||||
/// class Cat(Animal):
|
||||
/// def greet(self, happy: bool = True):
|
||||
@@ -208,7 +202,6 @@ impl Violation for UndocumentedPublicClass {
|
||||
/// ```
|
||||
///
|
||||
/// Or (in the Google docstring format):
|
||||
///
|
||||
/// ```python
|
||||
/// class Cat(Animal):
|
||||
/// def greet(self, happy: bool = True):
|
||||
@@ -234,8 +227,6 @@ impl Violation for UndocumentedPublicClass {
|
||||
/// - [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
|
||||
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
|
||||
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.70")]
|
||||
pub(crate) struct UndocumentedPublicMethod;
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::rules::pylint::helpers::is_known_dunder_method;
|
||||
///
|
||||
/// This rule will detect all methods starting and ending with at least
|
||||
/// one underscore (e.g., `_str_`), but ignores known dunder methods (like
|
||||
/// `__init__`), as well as methods that are marked with [`@override`][override].
|
||||
/// `__init__`), as well as methods that are marked with `@override`.
|
||||
///
|
||||
/// Additional dunder methods names can be allowed via the
|
||||
/// [`lint.pylint.allow-dunder-method-names`] setting.
|
||||
@@ -42,8 +42,6 @@ use crate::rules::pylint::helpers::is_known_dunder_method;
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.pylint.allow-dunder-method-names`
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "v0.0.285")]
|
||||
pub(crate) struct BadDunderMethodName {
|
||||
|
||||
@@ -17,16 +17,6 @@ use crate::rules::flake8_unused_arguments::rules::is_not_implemented_stub_with_v
|
||||
/// Unused `self` parameters are usually a sign of a method that could be
|
||||
/// replaced by a function, class method, or static method.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override].
|
||||
/// Converting an instance method into a static method or class method may
|
||||
/// cause type checkers to complain about a violation of the Liskov
|
||||
/// Substitution Principle if it means that the method now incompatibly
|
||||
/// overrides a method defined on a superclass. Explicitly decorating an
|
||||
/// overriding method with `@override` signals to Ruff that the method is
|
||||
/// intended to override a superclass method and that a type checker will
|
||||
/// enforce that it does so; Ruff therefore knows that it should not enforce
|
||||
/// rules about unused `self` parameters on such methods.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Person:
|
||||
@@ -48,8 +38,6 @@ use crate::rules::flake8_unused_arguments::rules::is_not_implemented_stub_with_v
|
||||
/// def greeting():
|
||||
/// print("Greetings friend!")
|
||||
/// ```
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "v0.0.286")]
|
||||
pub(crate) struct NoSelfUse {
|
||||
|
||||
@@ -12,16 +12,6 @@ use crate::checkers::ast::Checker;
|
||||
/// By default, this rule allows up to five arguments, as configured by the
|
||||
/// [`lint.pylint.max-args`] option.
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override].
|
||||
/// Changing the signature of a subclass method may cause type checkers to
|
||||
/// complain about a violation of the Liskov Substitution Principle if it
|
||||
/// means that the method now incompatibly overrides a method defined on a
|
||||
/// superclass. Explicitly decorating an overriding method with `@override`
|
||||
/// signals to Ruff that the method is intended to override a superclass
|
||||
/// method and that a type checker will enforce that it does so; Ruff
|
||||
/// therefore knows that it should not enforce rules about methods having
|
||||
/// too many arguments.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Functions with many arguments are harder to understand, maintain, and call.
|
||||
/// Consider refactoring functions with many arguments into smaller functions
|
||||
@@ -53,8 +43,6 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.pylint.max-args`
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.238")]
|
||||
pub(crate) struct TooManyArguments {
|
||||
|
||||
@@ -21,16 +21,6 @@ use crate::checkers::ast::Checker;
|
||||
/// with fewer arguments, using objects to group related arguments, or migrating to
|
||||
/// [keyword-only arguments](https://docs.python.org/3/tutorial/controlflow.html#special-parameters).
|
||||
///
|
||||
/// This rule exempts methods decorated with [`@typing.override`][override].
|
||||
/// Changing the signature of a subclass method may cause type checkers to
|
||||
/// complain about a violation of the Liskov Substitution Principle if it
|
||||
/// means that the method now incompatibly overrides a method defined on a
|
||||
/// superclass. Explicitly decorating an overriding method with `@override`
|
||||
/// signals to Ruff that the method is intended to override a superclass
|
||||
/// method and that a type checker will enforce that it does so; Ruff
|
||||
/// therefore knows that it should not enforce rules about methods having
|
||||
/// too many arguments.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
@@ -51,8 +41,6 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.pylint.max-positional-args`
|
||||
///
|
||||
/// [override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "v0.1.7")]
|
||||
pub(crate) struct TooManyPositionalArguments {
|
||||
|
||||
@@ -115,7 +115,6 @@ mod tests {
|
||||
#[test_case(Rule::NonOctalPermissions, Path::new("RUF064.py"))]
|
||||
#[test_case(Rule::LoggingEagerConversion, Path::new("RUF065_0.py"))]
|
||||
#[test_case(Rule::LoggingEagerConversion, Path::new("RUF065_1.py"))]
|
||||
#[test_case(Rule::PropertyWithoutReturn, Path::new("RUF066.py"))]
|
||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
|
||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
|
||||
#[test_case(Rule::InvalidRuleCode, Path::new("RUF102.py"))]
|
||||
|
||||
@@ -35,7 +35,6 @@ pub(crate) use non_octal_permissions::*;
|
||||
pub(crate) use none_not_at_end_of_union::*;
|
||||
pub(crate) use parenthesize_chained_operators::*;
|
||||
pub(crate) use post_init_default::*;
|
||||
pub(crate) use property_without_return::*;
|
||||
pub(crate) use pytest_raises_ambiguous_pattern::*;
|
||||
pub(crate) use quadratic_list_summation::*;
|
||||
pub(crate) use redirected_noqa::*;
|
||||
@@ -100,7 +99,6 @@ mod non_octal_permissions;
|
||||
mod none_not_at_end_of_union;
|
||||
mod parenthesize_chained_operators;
|
||||
mod post_init_default;
|
||||
mod property_without_return;
|
||||
mod pytest_raises_ambiguous_pattern;
|
||||
mod quadratic_list_summation;
|
||||
mod redirected_noqa;
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt};
|
||||
use ruff_python_ast::{Expr, Stmt, StmtFunctionDef};
|
||||
use ruff_python_semantic::analyze::{function_type, visibility};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Detects class `@property` methods that does not have a `return` statement.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Property methods are expected to return a computed value, a missing return in a property usually indicates an implementation mistake.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class User:
|
||||
/// @property
|
||||
/// def full_name(self):
|
||||
/// f"{self.first_name} {self.last_name}"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class User:
|
||||
/// @property
|
||||
/// def full_name(self):
|
||||
/// return f"{self.first_name} {self.last_name}"
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The property class](https://docs.python.org/3/library/functions.html#property)
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "0.14.7")]
|
||||
pub(crate) struct PropertyWithoutReturn {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for PropertyWithoutReturn {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let Self { name } = self;
|
||||
format!("`{name}` is a property without a `return` statement")
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF066
|
||||
pub(crate) fn property_without_return(checker: &Checker, function_def: &StmtFunctionDef) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
if checker.source_type.is_stub() || semantic.in_protocol_or_abstract_method() {
|
||||
return;
|
||||
}
|
||||
|
||||
let StmtFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name,
|
||||
..
|
||||
} = function_def;
|
||||
|
||||
if !visibility::is_property(decorator_list, [], semantic)
|
||||
|| visibility::is_overload(decorator_list, semantic)
|
||||
|| function_type::is_stub(function_def, semantic)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut visitor = PropertyVisitor::default();
|
||||
visitor.visit_body(body);
|
||||
if visitor.found {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(
|
||||
PropertyWithoutReturn {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function_def.identifier(),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PropertyVisitor {
|
||||
found: bool,
|
||||
}
|
||||
|
||||
// NOTE: We are actually searching for the presence of
|
||||
// `yield`/`yield from`/`raise`/`return` statement/expression,
|
||||
// as having one of those indicates that there's likely no implementation mistake
|
||||
impl Visitor<'_> for PropertyVisitor {
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
if self.found {
|
||||
return;
|
||||
}
|
||||
|
||||
match expr {
|
||||
Expr::Yield(_) | Expr::YieldFrom(_) => self.found = true,
|
||||
_ => walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
if self.found {
|
||||
return;
|
||||
}
|
||||
|
||||
match stmt {
|
||||
Stmt::Return(_) | Stmt::Raise(_) => self.found = true,
|
||||
Stmt::FunctionDef(_) => {
|
||||
// Do not recurse into nested functions; they're evaluated separately.
|
||||
}
|
||||
_ => walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF066 `name` is a property without a `return` statement
|
||||
--> RUF066.py:7:9
|
||||
|
|
||||
5 | class User: # Test normal class properties
|
||||
6 | @property
|
||||
7 | def name(self): # ERROR: No return
|
||||
| ^^^^
|
||||
8 | f"{self.first_name} {self.last_name}"
|
||||
|
|
||||
|
||||
RUF066 `nested` is a property without a `return` statement
|
||||
--> RUF066.py:18:9
|
||||
|
|
||||
17 | @property
|
||||
18 | def nested(self): # ERROR: Property itself doesn't return
|
||||
| ^^^^^^
|
||||
19 | def inner():
|
||||
20 | return 0
|
||||
|
|
||||
|
||||
RUF066 `prop2` is a property without a `return` statement
|
||||
--> RUF066.py:43:9
|
||||
|
|
||||
42 | @property
|
||||
43 | def prop2(self): # ERROR: Not returning something (even when we are inside an ABC)
|
||||
| ^^^^^
|
||||
44 | 50
|
||||
|
|
||||
@@ -3,13 +3,12 @@ use std::path::Path;
|
||||
use bitflags::bitflags;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_python_ast::helpers::{from_relative_import, map_subscript};
|
||||
use ruff_python_ast::helpers::from_relative_import;
|
||||
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprContext, PySourceType, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::Imported;
|
||||
use crate::analyze::visibility;
|
||||
use crate::binding::{
|
||||
Binding, BindingFlags, BindingId, BindingKind, Bindings, Exceptions, FromImport, Import,
|
||||
SubmoduleImport,
|
||||
@@ -2154,21 +2153,6 @@ impl<'a> SemanticModel<'a> {
|
||||
function.range() == function_def.range()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the model is in a `typing.Protocol` subclass or an abstract
|
||||
/// method.
|
||||
pub fn in_protocol_or_abstract_method(&self) -> bool {
|
||||
self.current_scopes().any(|scope| match scope.kind {
|
||||
ScopeKind::Class(class_def) => class_def
|
||||
.bases()
|
||||
.iter()
|
||||
.any(|base| self.match_typing_expr(map_subscript(base), "Protocol")),
|
||||
ScopeKind::Function(function_def) => {
|
||||
visibility::is_abstract(&function_def.decorator_list, self)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShadowedBinding {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.7"
|
||||
version = "0.14.6"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
315
crates/ty/docs/rules.md
generated
315
crates/ty/docs/rules.md
generated
@@ -39,7 +39,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L134" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L127" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L178" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L171" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ f(int) # error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L204" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L197" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ a = 1
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L229" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L222" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L255" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L248" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -184,41 +184,13 @@ class B(A): ...
|
||||
|
||||
[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
|
||||
## `cyclic-type-alias-definition`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L281" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for type alias definitions that (directly or mutually) refer to themselves.
|
||||
|
||||
**Why is it bad?**
|
||||
|
||||
Although it is permitted to define a recursive type alias, it is not meaningful
|
||||
to have a type alias whose expansion can only result in itself, and is therefore not allowed.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
type Itself = Itself
|
||||
|
||||
type A = B
|
||||
type B = A
|
||||
```
|
||||
|
||||
## `duplicate-base`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L342" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L313" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -245,7 +217,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L334" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -357,7 +329,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L538" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -387,7 +359,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L591" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L562" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -413,7 +385,7 @@ t[3] # IndexError: tuple index out of range
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L395" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L366" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -502,7 +474,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L645" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L616" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +501,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L685" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -557,7 +529,7 @@ a: int = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1948" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1815" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -591,7 +563,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L707" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L678" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -627,7 +599,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L737" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L708" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -651,7 +623,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L788" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L759" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -678,7 +650,7 @@ with 1:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L780" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -707,7 +679,7 @@ a: str
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L832" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L803" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -745,55 +717,13 @@ except ZeroDivisionError:
|
||||
|
||||
This rule corresponds to Ruff's [`except-with-non-exception-classes` (`B030`)](https://docs.astral.sh/ruff/rules/except-with-non-exception-classes)
|
||||
|
||||
## `invalid-explicit-override`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1645" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for methods that are decorated with `@override` but do not override any method in a superclass.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Decorating a method with `@override` declares to the type checker that the intention is that it should
|
||||
override a method from a superclass.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```python
|
||||
from typing import override
|
||||
|
||||
class A:
|
||||
@override
|
||||
def foo(self): ... # Error raised here
|
||||
|
||||
class B(A):
|
||||
@override
|
||||
def ffooo(self): ... # Error raised here
|
||||
|
||||
class C:
|
||||
@override
|
||||
def __repr__(self): ... # fine: overrides `object.__repr__`
|
||||
|
||||
class D(A):
|
||||
@override
|
||||
def foo(self): ... # fine: overrides `A.foo`
|
||||
```
|
||||
|
||||
## `invalid-generic-class`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L868" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L839" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -826,7 +756,7 @@ class C[U](Generic[T]): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L612" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L583" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -865,7 +795,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L894" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L865" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -900,7 +830,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L991" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L962" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -934,7 +864,7 @@ class B(metaclass=f): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2076" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1943" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1012,28 +942,7 @@ classes in Python do indeed behave this way, the strongly held convention is tha
|
||||
be avoided wherever possible. As part of this check, therefore, ty enforces that `__eq__`
|
||||
and `__ne__` methods accept `object` as their second argument.
|
||||
|
||||
**Why does ty disagree with Ruff about how to write my method?**
|
||||
|
||||
|
||||
Ruff has several rules that will encourage you to rename a parameter, or change its type
|
||||
signature, if it thinks you're falling into a certain anti-pattern. For example, Ruff's
|
||||
[ARG002](https://docs.astral.sh/ruff/rules/unused-method-argument/) rule recommends that an
|
||||
unused parameter should either be removed or renamed to start with `_`. Applying either of
|
||||
these suggestions can cause ty to start reporting an `invalid-method-override` error if
|
||||
the function in question is a method on a subclass that overrides a method on a superclass,
|
||||
and the change would cause the subclass method to no longer accept all argument combinations
|
||||
that the superclass method accepts.
|
||||
|
||||
This can usually be resolved by adding [`@typing.override`][override] to your method
|
||||
definition. Ruff knows that a method decorated with `@typing.override` is intended to
|
||||
override a method by the same name on a superclass, and avoids reporting rules like ARG002
|
||||
for such methods; it knows that the changes recommended by ARG002 would violate the Liskov
|
||||
Substitution Principle.
|
||||
|
||||
Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
|
||||
[Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
|
||||
[override]: https://docs.python.org/3/library/typing.html#typing.override
|
||||
|
||||
## `invalid-named-tuple`
|
||||
|
||||
@@ -1041,7 +950,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L512" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1073,7 +982,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L967" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L938" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1103,7 +1012,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1018" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L989" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1153,7 +1062,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1117" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1088" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1179,7 +1088,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L922" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L893" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1210,7 +1119,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L477" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1244,7 +1153,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1137" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1108" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1293,7 +1202,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L666" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L637" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1318,7 +1227,7 @@ def func() -> int:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1180" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1376,7 +1285,7 @@ TODO #14889
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L946" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L917" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1397,60 +1306,13 @@ IntOrStr = TypeAliasType("IntOrStr", int | str) # okay
|
||||
NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name must be a string literal
|
||||
```
|
||||
|
||||
## `invalid-type-arguments`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1412" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for invalid type arguments in explicit type specialization.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Providing the wrong number of type arguments or type arguments that don't
|
||||
satisfy the type variable's bounds or constraints will lead to incorrect
|
||||
type inference and may indicate a misunderstanding of the generic type's
|
||||
interface.
|
||||
|
||||
**Examples**
|
||||
|
||||
|
||||
Using legacy type variables:
|
||||
```python
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T1 = TypeVar('T1', int, str)
|
||||
T2 = TypeVar('T2', bound=int)
|
||||
|
||||
class Foo1(Generic[T1]): ...
|
||||
class Foo2(Generic[T2]): ...
|
||||
|
||||
Foo1[bytes] # error: bytes does not satisfy T1's constraints
|
||||
Foo2[str] # error: str does not satisfy T2's bound
|
||||
```
|
||||
|
||||
Using PEP 695 type variables:
|
||||
```python
|
||||
class Foo[T]: ...
|
||||
class Bar[T, U]: ...
|
||||
|
||||
Foo[int, str] # error: too many arguments
|
||||
Bar[int] # error: too few arguments
|
||||
```
|
||||
|
||||
## `invalid-type-checking-constant`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1219" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1190" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1480,7 +1342,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1243" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1214" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1510,7 +1372,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1295" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1266" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1544,7 +1406,7 @@ f(10) # Error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1267" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1238" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1578,7 +1440,7 @@ class C:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1323" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1613,7 +1475,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1323" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1638,7 +1500,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2049" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1916" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1671,7 +1533,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1342" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1700,7 +1562,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1365" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1724,7 +1586,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1453" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1383" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1744,46 +1606,13 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
pass
|
||||
```
|
||||
|
||||
## `override-of-final-method`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1618" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for methods on subclasses that override superclass methods decorated with `@final`.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Decorating a method with `@final` declares to the type checker that it should not be
|
||||
overridden on any subclass.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```python
|
||||
from typing import final
|
||||
|
||||
class A:
|
||||
@final
|
||||
def foo(self): ...
|
||||
|
||||
class B(A):
|
||||
def foo(self): ... # Error raised here
|
||||
```
|
||||
|
||||
## `parameter-already-assigned`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1504" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1810,7 +1639,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1802" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1669" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1868,7 +1697,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1924" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1791" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1898,7 +1727,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1595" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1525" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1927,7 +1756,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1703" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1570" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1954,7 +1783,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1681" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1548" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1982,7 +1811,7 @@ def _(x: int):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1724" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1591" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2028,7 +1857,7 @@ class A:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1781" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1648" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2055,7 +1884,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1823" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1690" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2083,7 +1912,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1845" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1712" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2108,7 +1937,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1864" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1731" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2133,7 +1962,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1473" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1403" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2170,7 +1999,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1883" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1750" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2198,7 +2027,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1905" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1772" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2223,7 +2052,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L506" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L477" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2264,7 +2093,7 @@ class SubProto(BaseProto, Protocol):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L321" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L292" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2291,7 +2120,7 @@ old_func() # emits [deprecated] diagnostic
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ignore-comment-unknown-rule" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L47" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L40" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2322,7 +2151,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-ignore-comment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L72" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L65" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2352,7 +2181,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1525" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1455" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2380,7 +2209,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L152" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L145" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2412,7 +2241,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1547" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1477" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2444,7 +2273,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1976" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1843" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2471,7 +2300,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1763" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1630" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2495,7 +2324,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1997" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1864" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2553,7 +2382,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L755" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L726" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2592,7 +2421,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1032" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2655,7 +2484,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L303" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L274" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2679,7 +2508,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1573" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1503" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2707,7 +2536,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unused-ignore-comment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L22" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L15" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -313,8 +313,7 @@ impl MainLoop {
|
||||
let terminal_settings = db.project().settings(db).terminal();
|
||||
let display_config = DisplayDiagnosticConfig::default()
|
||||
.format(terminal_settings.output_format.into())
|
||||
.color(colored::control::SHOULD_COLORIZE.should_colorize())
|
||||
.show_fix_diff(true);
|
||||
.color(colored::control::SHOULD_COLORIZE.should_colorize());
|
||||
|
||||
if check_revision == revision {
|
||||
if db.project().files(db).is_empty() {
|
||||
|
||||
@@ -143,7 +143,7 @@ ABCDEFGHIJKLMNOP = 'https://api.example.com'
|
||||
|
||||
impl CursorTest {
|
||||
fn all_symbols(&self, query: &str) -> String {
|
||||
let symbols = all_symbols(&self.db, &QueryPattern::fuzzy(query));
|
||||
let symbols = all_symbols(&self.db, &QueryPattern::new(query));
|
||||
|
||||
if symbols.is_empty() {
|
||||
return "No symbols found".to_string();
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
use crate::{completion, find_node::covering_node};
|
||||
use ruff_db::{files::File, parsed::parsed_module};
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_text_size::TextRange;
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::types::UNRESOLVED_REFERENCE;
|
||||
|
||||
/// A `QuickFix` Code Action
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QuickFix {
|
||||
pub title: String,
|
||||
pub edits: Vec<Edit>,
|
||||
pub preferred: bool,
|
||||
}
|
||||
|
||||
pub fn code_actions(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
diagnostic_range: TextRange,
|
||||
diagnostic_id: &str,
|
||||
) -> Option<Vec<QuickFix>> {
|
||||
let registry = db.lint_registry();
|
||||
let Ok(lint_id) = registry.get(diagnostic_id) else {
|
||||
return None;
|
||||
};
|
||||
if lint_id.name() == UNRESOLVED_REFERENCE.name() {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let node = covering_node(parsed.syntax().into(), diagnostic_range).node();
|
||||
let symbol = &node.expr_name()?.id;
|
||||
|
||||
let fixes = completion::missing_imports(db, file, &parsed, symbol, node)
|
||||
.into_iter()
|
||||
.map(|import| QuickFix {
|
||||
title: import.label,
|
||||
edits: vec![import.edit],
|
||||
preferred: true,
|
||||
})
|
||||
.collect();
|
||||
Some(fixes)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ use ruff_db::files::File;
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::{Token, TokenAt, TokenKind, Tokens};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
@@ -17,7 +17,7 @@ use ty_python_semantic::{
|
||||
|
||||
use crate::docstring::Docstring;
|
||||
use crate::find_node::covering_node;
|
||||
use crate::goto::Definitions;
|
||||
use crate::goto::DefinitionsOrTargets;
|
||||
use crate::importer::{ImportRequest, Importer};
|
||||
use crate::symbols::QueryPattern;
|
||||
use crate::{Db, all_symbols};
|
||||
@@ -37,9 +37,9 @@ impl<'db> Completions<'db> {
|
||||
/// the user has typed as part of the next symbol they are writing.
|
||||
/// This collection will treat it as a query when present, and only
|
||||
/// add completions that match it.
|
||||
fn fuzzy(db: &'db dyn Db, typed: Option<&str>) -> Completions<'db> {
|
||||
fn new(db: &'db dyn Db, typed: Option<&str>) -> Completions<'db> {
|
||||
let query = typed
|
||||
.map(QueryPattern::fuzzy)
|
||||
.map(QueryPattern::new)
|
||||
.unwrap_or_else(QueryPattern::matches_all_symbols);
|
||||
Completions {
|
||||
db,
|
||||
@@ -48,15 +48,6 @@ impl<'db> Completions<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn exactly(db: &'db dyn Db, symbol: &str) -> Completions<'db> {
|
||||
let query = QueryPattern::exactly(symbol);
|
||||
Completions {
|
||||
db,
|
||||
items: vec![],
|
||||
query,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this collection into a simple
|
||||
/// sequence of completions.
|
||||
fn into_completions(mut self) -> Vec<Completion<'db>> {
|
||||
@@ -66,21 +57,6 @@ impl<'db> Completions<'db> {
|
||||
self.items
|
||||
}
|
||||
|
||||
fn into_imports(mut self) -> Vec<ImportEdit> {
|
||||
self.items.sort_by(compare_suggestions);
|
||||
self.items
|
||||
.dedup_by(|c1, c2| (&c1.name, c1.module_name) == (&c2.name, c2.module_name));
|
||||
self.items
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
Some(ImportEdit {
|
||||
label: format!("import {}.{}", item.module_name?, item.name),
|
||||
edit: item.import?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Attempts to adds the given completion to this collection.
|
||||
///
|
||||
/// When added, `true` is returned.
|
||||
@@ -220,7 +196,9 @@ impl<'db> Completion<'db> {
|
||||
db: &'db dyn Db,
|
||||
semantic: SemanticCompletion<'db>,
|
||||
) -> Completion<'db> {
|
||||
let definition = semantic.ty.and_then(|ty| Definitions::from_ty(db, ty));
|
||||
let definition = semantic
|
||||
.ty
|
||||
.and_then(|ty| DefinitionsOrTargets::from_ty(db, ty));
|
||||
let documentation = definition.and_then(|def| def.docstring(db));
|
||||
let is_type_check_only = semantic.is_type_check_only(db);
|
||||
Completion {
|
||||
@@ -391,7 +369,7 @@ pub fn completion<'db>(
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut completions = Completions::fuzzy(db, typed.as_deref());
|
||||
let mut completions = Completions::new(db, typed.as_deref());
|
||||
|
||||
if let Some(import) = ImportStatement::detect(db, file, &parsed, tokens, typed.as_deref()) {
|
||||
import.add_completions(db, file, &mut completions);
|
||||
@@ -439,25 +417,6 @@ pub fn completion<'db>(
|
||||
completions.into_completions()
|
||||
}
|
||||
|
||||
pub(crate) struct ImportEdit {
|
||||
pub label: String,
|
||||
pub edit: Edit,
|
||||
}
|
||||
|
||||
pub(crate) fn missing_imports(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
parsed: &ParsedModuleRef,
|
||||
symbol: &str,
|
||||
node: AnyNodeRef,
|
||||
) -> Vec<ImportEdit> {
|
||||
let mut completions = Completions::exactly(db, symbol);
|
||||
let scoped = ScopedTarget { node };
|
||||
add_unimported_completions(db, file, parsed, scoped, &mut completions);
|
||||
|
||||
completions.into_imports()
|
||||
}
|
||||
|
||||
/// Adds completions derived from keywords.
|
||||
///
|
||||
/// This should generally only be used when offering "scoped" completions.
|
||||
@@ -1356,8 +1315,7 @@ fn find_typed_text(
|
||||
if last.end() < offset || last.range().is_empty() {
|
||||
return None;
|
||||
}
|
||||
let range = TextRange::new(last.start(), offset);
|
||||
Some(source[range].to_string())
|
||||
Some(source[last.range()].to_string())
|
||||
}
|
||||
|
||||
/// Whether the last token is in a place where we should not provide completions.
|
||||
@@ -1448,24 +1406,6 @@ fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Opt
|
||||
type_param.name.range.contains_range(range)
|
||||
}
|
||||
ast::AnyNodeRef::StmtFor(stmt_for) => stmt_for.target.range().contains_range(range),
|
||||
// The AST does not produce `ast::AnyNodeRef::Parameter` nodes for keywords
|
||||
// or otherwise invalid syntax. Rather they are captured in a
|
||||
// `ast::AnyNodeRef::Parameters` node as "empty space". To ensure
|
||||
// we still suppress suggestions even when the syntax is technically
|
||||
// invalid we extract the token under the cursor and check if it makes
|
||||
// up that "empty space" inside the Parameters Node. If it does, we know
|
||||
// that we are still binding variables, just that the current state is
|
||||
// syntatically invalid. Hence we suppress autocomplete suggestons
|
||||
// also in those cases.
|
||||
ast::AnyNodeRef::Parameters(params) => {
|
||||
if !params.range.contains_range(range) {
|
||||
return false;
|
||||
}
|
||||
params
|
||||
.iter()
|
||||
.map(|param| param.range())
|
||||
.all(|r| !r.contains_range(range))
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
@@ -1693,21 +1633,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inside_token() {
|
||||
let test = completion_test_builder(
|
||||
"\
|
||||
foo_bar_baz = 1
|
||||
x = foo<CURSOR>bad
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
test.skip_builtins().build().snapshot(),
|
||||
@"foo_bar_baz",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_keyword_dedup() {
|
||||
let test = completion_test_builder(
|
||||
@@ -2896,7 +2821,7 @@ Answer.<CURSOR>
|
||||
__itemsize__ :: int
|
||||
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
|
||||
__len__ :: bound method <class 'Answer'>.__len__() -> int
|
||||
__members__ :: MappingProxyType[str, Answer]
|
||||
__members__ :: MappingProxyType[str, Unknown]
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[type, ...]
|
||||
__name__ :: str
|
||||
@@ -5422,45 +5347,6 @@ def foo(p<CURSOR>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_function_param_keyword() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def foo(in<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_function_param_multi_keyword() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def foo(param, in<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_function_param_multi_keyword_middle() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def foo(param, in<CURSOR>, param_two
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_function_type_param() {
|
||||
let builder = completion_test_builder(
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::files::File;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
|
||||
/// Find all document highlights for a symbol at the given position.
|
||||
/// Document highlights are limited to the current file only.
|
||||
@@ -14,10 +13,9 @@ pub fn document_highlights(
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
// Get the definitions for the symbol at the cursor position
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
// Use DocumentHighlights mode which limits search to current file only
|
||||
references(db, file, &goto_target, ReferencesMode::DocumentHighlights)
|
||||
|
||||
@@ -182,11 +182,6 @@ fn documentation_trim(docs: &str) -> String {
|
||||
/// </code>
|
||||
/// ```
|
||||
fn render_markdown(docstring: &str) -> String {
|
||||
// Here lies a monumemnt to robust parsing and escaping:
|
||||
// a codefence with SO MANY backticks that surely no one will ever accidentally
|
||||
// break out of it, even if they're writing python documentation about markdown
|
||||
// code fences and are showing off how you can use more than 3 backticks.
|
||||
const FENCE: &str = "```````````";
|
||||
// TODO: there is a convention that `singletick` is for items that can
|
||||
// be looked up in-scope while ``multitick`` is for opaque inline code.
|
||||
// While rendering this we should make note of all the `singletick` locations
|
||||
@@ -196,10 +191,9 @@ fn render_markdown(docstring: &str) -> String {
|
||||
let mut first_line = true;
|
||||
let mut block_indent = 0;
|
||||
let mut in_doctest = false;
|
||||
let mut starting_literal = None;
|
||||
let mut starting_literal = false;
|
||||
let mut in_literal = false;
|
||||
let mut in_any_code = false;
|
||||
let mut temp_owned_line;
|
||||
for untrimmed_line in docstring.lines() {
|
||||
// We can assume leading whitespace has been normalized
|
||||
let mut line = untrimmed_line.trim_start_matches(' ');
|
||||
@@ -213,7 +207,7 @@ fn render_markdown(docstring: &str) -> String {
|
||||
output.push_str(" ");
|
||||
}
|
||||
// Only push newlines if we're not scanning for a real line
|
||||
if starting_literal.is_none() {
|
||||
if !starting_literal {
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
@@ -225,23 +219,21 @@ fn render_markdown(docstring: &str) -> String {
|
||||
in_literal = false;
|
||||
in_any_code = false;
|
||||
block_indent = 0;
|
||||
output.push_str(FENCE);
|
||||
output.push('\n');
|
||||
output.push_str("```\n");
|
||||
}
|
||||
|
||||
// We previously entered a literal block and we just found our first non-blank line
|
||||
// So now we're actually in the literal block
|
||||
if let Some(literal) = starting_literal
|
||||
&& !line.is_empty()
|
||||
{
|
||||
starting_literal = None;
|
||||
if starting_literal && !line.is_empty() {
|
||||
starting_literal = false;
|
||||
in_literal = true;
|
||||
in_any_code = true;
|
||||
block_indent = line_indent;
|
||||
output.push('\n');
|
||||
output.push_str(FENCE);
|
||||
output.push_str(literal);
|
||||
output.push('\n');
|
||||
// TODO: I hope people don't have literal blocks about markdown code fence syntax
|
||||
// TODO: should we not be this aggressive? Let it autodetect?
|
||||
// TODO: respect `.. code-block::` directives:
|
||||
// <https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block>
|
||||
output.push_str("\n```python\n");
|
||||
}
|
||||
|
||||
// If we're not in a codeblock and we see something that signals a doctest, start one
|
||||
@@ -250,79 +242,25 @@ fn render_markdown(docstring: &str) -> String {
|
||||
in_doctest = true;
|
||||
in_any_code = true;
|
||||
// TODO: is there something more specific? `pycon`?
|
||||
output.push_str(FENCE);
|
||||
output.push_str("python\n");
|
||||
output.push_str("```python\n");
|
||||
}
|
||||
|
||||
// If we're not in a codeblock and we see something that signals a literal block, start one
|
||||
let parsed_lit = line
|
||||
// first check for a line ending with `::`
|
||||
.strip_suffix("::")
|
||||
.map(|prefix| (prefix, None))
|
||||
// if that fails, look for a line ending with `:: lang`
|
||||
.or_else(|| {
|
||||
let (prefix, lang) = line.rsplit_once(' ')?;
|
||||
let prefix = prefix.trim_end().strip_suffix("::")?;
|
||||
Some((prefix, Some(lang)))
|
||||
});
|
||||
if !in_any_code && let Some((without_lit, lang)) = parsed_lit {
|
||||
let mut without_directive = without_lit;
|
||||
let mut directive = None;
|
||||
// Parse out a directive like `.. warning::`
|
||||
if let Some((prefix, directive_str)) = without_lit.rsplit_once(' ')
|
||||
&& let Some(without_directive_str) = prefix.strip_suffix("..")
|
||||
{
|
||||
directive = Some(directive_str);
|
||||
without_directive = without_directive_str;
|
||||
}
|
||||
|
||||
// Whether the `::` should become `:` or be erased
|
||||
let include_colon = if let Some(character) = without_directive.chars().next_back() {
|
||||
// If lang is set then we're either deleting the whole line or
|
||||
// the special rendering below will add it itself
|
||||
lang.is_none() && !character.is_whitespace()
|
||||
if !in_any_code && let Some(without_lit) = line.strip_suffix("::") {
|
||||
let trimmed_without_lit = without_lit.trim();
|
||||
if let Some(character) = trimmed_without_lit.chars().next_back() {
|
||||
if character.is_whitespace() {
|
||||
// Remove the marker completely
|
||||
line = trimmed_without_lit;
|
||||
} else {
|
||||
// Only remove the first `:`
|
||||
line = line.strip_suffix(":").unwrap();
|
||||
}
|
||||
} else {
|
||||
// Delete whole line
|
||||
false
|
||||
};
|
||||
|
||||
if include_colon {
|
||||
line = line.strip_suffix(":").unwrap();
|
||||
} else {
|
||||
line = without_directive.trim_end();
|
||||
line = trimmed_without_lit;
|
||||
}
|
||||
|
||||
starting_literal = match directive {
|
||||
// Special directives that should be plaintext
|
||||
Some(
|
||||
"attention" | "caution" | "danger" | "error" | "hint" | "important" | "note"
|
||||
| "tip" | "warning" | "admonition" | "versionadded" | "version-added"
|
||||
| "versionchanged" | "version-changed" | "version-deprecated" | "deprecated"
|
||||
| "version-removed" | "versionremoved",
|
||||
) => {
|
||||
// Render the argument of things like `.. version-added:: 4.0`
|
||||
let suffix = if let Some(lang) = lang {
|
||||
format!(" *{lang}*")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
// We prepend without_directive here out of caution for preserving input.
|
||||
// This is probably gibberish/invalid syntax? But it's a no-op in normal cases.
|
||||
temp_owned_line =
|
||||
format!("**{without_directive}{}:**{suffix}", directive.unwrap());
|
||||
|
||||
line = temp_owned_line.as_str();
|
||||
None
|
||||
}
|
||||
// Things that just mean "it's code"
|
||||
Some(
|
||||
"code-block" | "sourcecode" | "code" | "testcode" | "testsetup" | "testcleanup",
|
||||
) => lang.or(Some("python")),
|
||||
// Unknown (python I guess?)
|
||||
Some(_) => lang.or(Some("python")),
|
||||
// default to python
|
||||
None => lang.or(Some("python")),
|
||||
};
|
||||
starting_literal = true;
|
||||
}
|
||||
|
||||
// Add this line's indentation.
|
||||
@@ -411,7 +349,7 @@ fn render_markdown(docstring: &str) -> String {
|
||||
block_indent = 0;
|
||||
in_any_code = false;
|
||||
in_literal = false;
|
||||
output.push_str(FENCE);
|
||||
output.push_str("```");
|
||||
}
|
||||
} else {
|
||||
// Print the line verbatim, it's in code
|
||||
@@ -422,8 +360,7 @@ fn render_markdown(docstring: &str) -> String {
|
||||
}
|
||||
// Flush codeblock
|
||||
if in_any_code {
|
||||
output.push('\n');
|
||||
output.push_str(FENCE);
|
||||
output.push_str("\n```");
|
||||
}
|
||||
|
||||
output
|
||||
@@ -793,6 +730,28 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_plaintext(), @r"
|
||||
Here _this_ and ___that__ should be escaped
|
||||
Here *this* and **that** should be untouched
|
||||
Here `this` and ``that`` should be untouched
|
||||
|
||||
Here `_this_` and ``__that__`` should be untouched
|
||||
Here `_this_` ``__that__`` should be untouched
|
||||
`_this_too_should_be_untouched_`
|
||||
|
||||
Here `_this_```__that__`` should be untouched but this_is_escaped
|
||||
Here ``_this_```__that__` should be untouched but this_is_escaped
|
||||
|
||||
Here `_this_ and _that_ should be escaped (but isn't)
|
||||
Here _this_ and _that_` should be escaped
|
||||
`Here _this_ and _that_ should be escaped (but isn't)
|
||||
Here _this_ and _that_ should be escaped`
|
||||
|
||||
Here ```_is_``__a__`_balanced_``_mess_```
|
||||
Here ```_is_`````__a__``_random_````_mess__````
|
||||
```_is_`````__a__``_random_````_mess__````
|
||||
");
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
Here \_this\_ and \_\_\_that\_\_ should be escaped
|
||||
Here *this* and **that** should be untouched
|
||||
@@ -837,9 +796,9 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code:
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r#"
|
||||
Check out this great example code::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
@@ -849,7 +808,22 @@ mod tests {
|
||||
|
||||
print("done")
|
||||
|
||||
```````````
|
||||
You love to see it.
|
||||
"#);
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code:
|
||||
```python
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
print("done")
|
||||
|
||||
```
|
||||
You love to see it.
|
||||
"#);
|
||||
}
|
||||
@@ -875,9 +849,9 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r#"
|
||||
Check out this great example code ::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
@@ -887,7 +861,22 @@ mod tests {
|
||||
|
||||
print("done")
|
||||
|
||||
```````````
|
||||
You love to see it.
|
||||
"#);
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code :
|
||||
```python
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
print("done")
|
||||
|
||||
```
|
||||
You love to see it.
|
||||
"#);
|
||||
}
|
||||
@@ -914,10 +903,10 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code
|
||||
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r#"
|
||||
Check out this great example code
|
||||
::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
@@ -927,7 +916,23 @@ mod tests {
|
||||
|
||||
print("done")
|
||||
|
||||
```````````
|
||||
You love to see it.
|
||||
"#);
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code
|
||||
|
||||
```python
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
print("done")
|
||||
|
||||
```
|
||||
You love to see it.
|
||||
"#);
|
||||
}
|
||||
@@ -951,9 +956,8 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code:
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r#"
|
||||
Check out this great example code::
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
@@ -962,7 +966,21 @@ mod tests {
|
||||
print("too short :(")
|
||||
|
||||
print("done")
|
||||
```````````
|
||||
You love to see it.
|
||||
"#);
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code:
|
||||
```python
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
print("done")
|
||||
```
|
||||
You love to see it.
|
||||
"#);
|
||||
}
|
||||
@@ -985,9 +1003,9 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Check out this great example code:
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r#"
|
||||
Check out this great example code::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
if len(x_y) > 4:
|
||||
@@ -996,224 +1014,20 @@ mod tests {
|
||||
print("too short :(")
|
||||
|
||||
print("done")
|
||||
```````````
|
||||
"#);
|
||||
}
|
||||
|
||||
// `warning` and several other directives are special languages that should actually
|
||||
// still be shown as text and not ```code```.
|
||||
#[test]
|
||||
fn warning_block() {
|
||||
let docstring = r#"
|
||||
The thing you need to understand is that computers are hard.
|
||||
|
||||
.. warning::
|
||||
Now listen here buckaroo you might have seen me say computers are hard,
|
||||
and though "yeah I know computers are hard but NO you DON'T KNOW.
|
||||
|
||||
Listen:
|
||||
|
||||
- Computers
|
||||
- Are
|
||||
- Hard
|
||||
|
||||
Ok!?!?!?
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
The thing you need to understand is that computers are hard.
|
||||
|
||||
**warning:**
|
||||
Now listen here buckaroo you might have seen me say computers are hard,
|
||||
and though "yeah I know computers are hard but NO you DON'T KNOW.
|
||||
|
||||
Listen:
|
||||
|
||||
- Computers
|
||||
- Are
|
||||
- Hard
|
||||
|
||||
Ok!?!?!?
|
||||
"#);
|
||||
}
|
||||
Check out this great example code:
|
||||
```python
|
||||
x_y = "hello"
|
||||
|
||||
// `warning` and several other directives are special languages that should actually
|
||||
// still be shown as text and not ```code```.
|
||||
#[test]
|
||||
fn version_blocks() {
|
||||
let docstring = r#"
|
||||
Some much-updated docs
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
.. version-added:: 3.0
|
||||
Function added
|
||||
|
||||
.. version-changed:: 4.0
|
||||
The `spam` argument was added
|
||||
.. version-changed:: 4.1
|
||||
The `spam` argument is considered evil now.
|
||||
|
||||
You really shouldnt use it
|
||||
|
||||
And that's the docs
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
Some much-updated docs
|
||||
|
||||
**version-added:** *3.0*
|
||||
Function added
|
||||
|
||||
**version-changed:** *4.0*
|
||||
The `spam` argument was added
|
||||
**version-changed:** *4.1*
|
||||
The `spam` argument is considered evil now.
|
||||
|
||||
You really shouldnt use it
|
||||
|
||||
And that's the docs
|
||||
");
|
||||
}
|
||||
|
||||
// I don't know if this is valid syntax but we preserve stuff before non-code blocks like
|
||||
// `..deprecated ::`
|
||||
#[test]
|
||||
fn deprecated_prefix_gunk() {
|
||||
let docstring = r#"
|
||||
wow this is some changes .. deprecated:: 1.2.3
|
||||
x = 2
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
**wow this is some changes deprecated:** *1.2.3*
|
||||
x = 2
|
||||
");
|
||||
}
|
||||
|
||||
// `.. code::` is a literal block and the `.. code::` should be deleted
|
||||
#[test]
|
||||
fn code_block() {
|
||||
let docstring = r#"
|
||||
Here's some code!
|
||||
|
||||
.. code::
|
||||
def main() {
|
||||
print("hello world!")
|
||||
}
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Here's some code!
|
||||
|
||||
|
||||
```````````python
|
||||
def main() {
|
||||
print("hello world!")
|
||||
}
|
||||
```````````
|
||||
"#);
|
||||
}
|
||||
|
||||
// `.. code:: rust` is a literal block with rust syntax highlighting
|
||||
#[test]
|
||||
fn code_block_lang() {
|
||||
let docstring = r#"
|
||||
Here's some Rust code!
|
||||
|
||||
.. code:: rust
|
||||
fn main() {
|
||||
println!("hello world!");
|
||||
}
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Here's some Rust code!
|
||||
|
||||
|
||||
```````````rust
|
||||
fn main() {
|
||||
println!("hello world!");
|
||||
}
|
||||
```````````
|
||||
"#);
|
||||
}
|
||||
|
||||
// I don't know if this is valid syntax but we preserve stuff before `..code ::`
|
||||
#[test]
|
||||
fn code_block_prefix_gunk() {
|
||||
let docstring = r#"
|
||||
wow this is some code.. code:: abc
|
||||
x = 2
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
wow this is some code
|
||||
```````````abc
|
||||
x = 2
|
||||
```````````
|
||||
");
|
||||
}
|
||||
|
||||
// `.. asdgfhjkl-unknown::` is treated the same as `.. code::`
|
||||
#[test]
|
||||
fn unknown_block() {
|
||||
let docstring = r#"
|
||||
Here's some code!
|
||||
|
||||
.. asdgfhjkl-unknown::
|
||||
fn main() {
|
||||
println!("hello world!");
|
||||
}
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Here's some code!
|
||||
|
||||
|
||||
```````````python
|
||||
fn main() {
|
||||
println!("hello world!");
|
||||
}
|
||||
```````````
|
||||
"#);
|
||||
}
|
||||
|
||||
// `.. asdgfhjkl-unknown:: rust` is treated the same as `.. code:: rust`
|
||||
#[test]
|
||||
fn unknown_block_lang() {
|
||||
let docstring = r#"
|
||||
Here's some Rust code!
|
||||
|
||||
.. asdgfhjkl-unknown:: rust
|
||||
fn main() {
|
||||
print("hello world!")
|
||||
}
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
Here's some Rust code!
|
||||
|
||||
|
||||
```````````rust
|
||||
fn main() {
|
||||
print("hello world!")
|
||||
}
|
||||
```````````
|
||||
print("done")
|
||||
```
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -1233,15 +1047,26 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
This is a function description
|
||||
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r"
|
||||
This is a function description
|
||||
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```````````
|
||||
|
||||
As you can see it did the thing!
|
||||
");
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
This is a function description
|
||||
|
||||
```python
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```
|
||||
As you can see it did the thing!
|
||||
");
|
||||
}
|
||||
@@ -1262,15 +1087,26 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
This is a function description
|
||||
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r"
|
||||
This is a function description
|
||||
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```````````
|
||||
|
||||
As you can see it did the thing!
|
||||
");
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
This is a function description
|
||||
|
||||
```python
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```
|
||||
As you can see it did the thing!
|
||||
");
|
||||
}
|
||||
@@ -1285,13 +1121,20 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r"
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```````````
|
||||
");
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
```python
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```
|
||||
");
|
||||
}
|
||||
|
||||
@@ -1311,15 +1154,26 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
This is a function description:
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r"
|
||||
This is a function description::
|
||||
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
|
||||
```````````
|
||||
As you can see it did the thing!
|
||||
");
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
This is a function description:
|
||||
```python
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
|
||||
```
|
||||
As you can see it did the thing!
|
||||
");
|
||||
}
|
||||
@@ -1335,14 +1189,22 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
And so you can see that
|
||||
```````````python
|
||||
assert_snapshot!(docstring.render_plaintext(), @r"
|
||||
And so you can see that
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```````````
|
||||
");
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
And so you can see that
|
||||
```python
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
>>> thing.do_other_thing()
|
||||
it sure did the thing
|
||||
```
|
||||
");
|
||||
}
|
||||
|
||||
@@ -1521,14 +1383,14 @@ mod tests {
|
||||
This is a continuation of param2 description.
|
||||
'param3' -- A parameter without type annotation
|
||||
|
||||
```````````python
|
||||
```python
|
||||
>>> print repr(foo.__doc__)
|
||||
'\n This is the second line of the docstring.\n '
|
||||
>>> foo.__doc__.splitlines()
|
||||
['', ' This is the second line of the docstring.', ' ']
|
||||
>>> trim(foo.__doc__)
|
||||
'This is the second line of the docstring.'
|
||||
```````````
|
||||
```
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -190,31 +190,20 @@ pub(crate) enum GotoTarget<'a> {
|
||||
/// The call of the callable
|
||||
call: &'a ast::ExprCall,
|
||||
},
|
||||
|
||||
/// Go to on a sub-expression of a string annotation's sub-AST
|
||||
///
|
||||
/// ```py
|
||||
/// x: "int | None"
|
||||
/// ^^^^
|
||||
/// ```
|
||||
///
|
||||
/// This is equivalent to `GotoTarget::Expression` but the expression
|
||||
/// isn't actually in the AST.
|
||||
StringAnnotationSubexpr {
|
||||
/// The string literal that is a string annotation.
|
||||
string_expr: &'a ast::ExprStringLiteral,
|
||||
/// The range to query in the sub-AST for the sub-expression.
|
||||
subrange: TextRange,
|
||||
/// If the expression is a Name of some kind this is the name (just a cached result).
|
||||
name: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The resolved definitions for a `GotoTarget`
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Definitions<'db>(pub Vec<ResolvedDefinition<'db>>);
|
||||
pub(crate) enum DefinitionsOrTargets<'db> {
|
||||
/// We computed actual Definitions we can do followup queries on.
|
||||
Definitions(Vec<ResolvedDefinition<'db>>),
|
||||
/// We directly computed a navigation.
|
||||
///
|
||||
/// We can't get docs or usefully compute goto-definition for this.
|
||||
Targets(crate::NavigationTargets),
|
||||
}
|
||||
|
||||
impl<'db> Definitions<'db> {
|
||||
impl<'db> DefinitionsOrTargets<'db> {
|
||||
pub(crate) fn from_ty(db: &'db dyn crate::Db, ty: Type<'db>) -> Option<Self> {
|
||||
let ty_def = ty.definition(db)?;
|
||||
let resolved = match ty_def {
|
||||
@@ -230,7 +219,7 @@ impl<'db> Definitions<'db> {
|
||||
ResolvedDefinition::Definition(definition)
|
||||
}
|
||||
};
|
||||
Some(Definitions(vec![resolved]))
|
||||
Some(DefinitionsOrTargets::Definitions(vec![resolved]))
|
||||
}
|
||||
|
||||
/// Get the "goto-declaration" interpretation of this definition
|
||||
@@ -238,9 +227,14 @@ impl<'db> Definitions<'db> {
|
||||
/// In this case it basically returns exactly what was found.
|
||||
pub(crate) fn declaration_targets(
|
||||
self,
|
||||
db: &'db dyn ty_python_semantic::Db,
|
||||
db: &'db dyn crate::Db,
|
||||
) -> Option<crate::NavigationTargets> {
|
||||
definitions_to_navigation_targets(db, None, self.0)
|
||||
match self {
|
||||
DefinitionsOrTargets::Definitions(definitions) => {
|
||||
definitions_to_navigation_targets(db, None, definitions)
|
||||
}
|
||||
DefinitionsOrTargets::Targets(targets) => Some(targets),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the "goto-definition" interpretation of this definition
|
||||
@@ -249,9 +243,14 @@ impl<'db> Definitions<'db> {
|
||||
/// if the definition we have is found in a stub file.
|
||||
pub(crate) fn definition_targets(
|
||||
self,
|
||||
db: &'db dyn ty_python_semantic::Db,
|
||||
db: &'db dyn crate::Db,
|
||||
) -> Option<crate::NavigationTargets> {
|
||||
definitions_to_navigation_targets(db, Some(&StubMapper::new(db)), self.0)
|
||||
match self {
|
||||
DefinitionsOrTargets::Definitions(definitions) => {
|
||||
definitions_to_navigation_targets(db, Some(&StubMapper::new(db)), definitions)
|
||||
}
|
||||
DefinitionsOrTargets::Targets(targets) => Some(targets),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the docstring for this definition
|
||||
@@ -260,7 +259,13 @@ impl<'db> Definitions<'db> {
|
||||
/// so this will check both the goto-declarations and goto-definitions (in that order)
|
||||
/// and return the first one found.
|
||||
pub(crate) fn docstring(self, db: &'db dyn crate::Db) -> Option<Docstring> {
|
||||
for definition in &self.0 {
|
||||
let definitions = match self {
|
||||
DefinitionsOrTargets::Definitions(definitions) => definitions,
|
||||
// Can't find docs for these
|
||||
// (make more cases DefinitionOrTargets::Definitions to get more docs!)
|
||||
DefinitionsOrTargets::Targets(_) => return None,
|
||||
};
|
||||
for definition in &definitions {
|
||||
// If we got a docstring from the original definition, use it
|
||||
if let Some(docstring) = definition.docstring(db) {
|
||||
return Some(Docstring::new(docstring));
|
||||
@@ -273,7 +278,7 @@ impl<'db> Definitions<'db> {
|
||||
let stub_mapper = StubMapper::new(db);
|
||||
|
||||
// Try to find the corresponding implementation definition
|
||||
for definition in stub_mapper.map_definitions(self.0) {
|
||||
for definition in stub_mapper.map_definitions(definitions) {
|
||||
if let Some(docstring) = definition.docstring(db) {
|
||||
return Some(Docstring::new(docstring));
|
||||
}
|
||||
@@ -308,32 +313,6 @@ impl GotoTarget<'_> {
|
||||
let module = import_name(module_name, *component_index);
|
||||
model.resolve_module_type(Some(module), *level)?
|
||||
}
|
||||
GotoTarget::StringAnnotationSubexpr {
|
||||
string_expr,
|
||||
subrange,
|
||||
..
|
||||
} => {
|
||||
let (subast, _submodel) = model.enter_string_annotation(string_expr)?;
|
||||
let submod = subast.syntax();
|
||||
let subnode = covering_node(submod.into(), *subrange).node();
|
||||
|
||||
// The type checker knows the type of the full annotation but nothing else
|
||||
if AnyNodeRef::from(&*submod.body) == subnode {
|
||||
string_expr.inferred_type(model)
|
||||
} else {
|
||||
// TODO: force the typechecker to tell us its secrets
|
||||
// (it computes but then immediately discards these types)
|
||||
return None;
|
||||
}
|
||||
}
|
||||
GotoTarget::BinOp { expression, .. } => {
|
||||
let (_, ty) = ty_python_semantic::definitions_for_bin_op(model, expression)?;
|
||||
ty
|
||||
}
|
||||
GotoTarget::UnaryOp { expression, .. } => {
|
||||
let (_, ty) = ty_python_semantic::definitions_for_unary_op(model, expression)?;
|
||||
ty
|
||||
}
|
||||
// TODO: Support identifier targets
|
||||
GotoTarget::PatternMatchRest(_)
|
||||
| GotoTarget::PatternKeywordArgument(_)
|
||||
@@ -343,6 +322,16 @@ impl GotoTarget<'_> {
|
||||
| GotoTarget::TypeParamTypeVarTupleName(_)
|
||||
| GotoTarget::NonLocal { .. }
|
||||
| GotoTarget::Globals { .. } => return None,
|
||||
GotoTarget::BinOp { expression, .. } => {
|
||||
let (_, ty) =
|
||||
ty_python_semantic::definitions_for_bin_op(model.db(), model, expression)?;
|
||||
ty
|
||||
}
|
||||
GotoTarget::UnaryOp { expression, .. } => {
|
||||
let (_, ty) =
|
||||
ty_python_semantic::definitions_for_unary_op(model.db(), model, expression)?;
|
||||
ty
|
||||
}
|
||||
};
|
||||
|
||||
Some(ty)
|
||||
@@ -354,7 +343,7 @@ impl GotoTarget<'_> {
|
||||
model: &SemanticModel,
|
||||
) -> Option<String> {
|
||||
if let GotoTarget::Call { call, .. } = self {
|
||||
call_type_simplified_by_overloads(model, call)
|
||||
call_type_simplified_by_overloads(model.db(), model, call)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -376,32 +365,42 @@ impl GotoTarget<'_> {
|
||||
&self,
|
||||
model: &SemanticModel<'db>,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Option<Definitions<'db>> {
|
||||
let definitions = match self {
|
||||
GotoTarget::Expression(expression) => definitions_for_expression(model, *expression),
|
||||
) -> Option<DefinitionsOrTargets<'db>> {
|
||||
use crate::NavigationTarget;
|
||||
let db = model.db();
|
||||
let file = model.file();
|
||||
|
||||
match self {
|
||||
GotoTarget::Expression(expression) => {
|
||||
definitions_for_expression(model, expression).map(DefinitionsOrTargets::Definitions)
|
||||
}
|
||||
|
||||
// For already-defined symbols, they are their own definitions
|
||||
GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition(
|
||||
function.definition(model),
|
||||
)]),
|
||||
GotoTarget::FunctionDef(function) => Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(function.definition(model)),
|
||||
])),
|
||||
|
||||
GotoTarget::ClassDef(class) => Some(vec![ResolvedDefinition::Definition(
|
||||
class.definition(model),
|
||||
)]),
|
||||
GotoTarget::ClassDef(class) => Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(class.definition(model)),
|
||||
])),
|
||||
|
||||
GotoTarget::Parameter(parameter) => Some(vec![ResolvedDefinition::Definition(
|
||||
parameter.definition(model),
|
||||
)]),
|
||||
GotoTarget::Parameter(parameter) => Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(parameter.definition(model)),
|
||||
])),
|
||||
|
||||
// For import aliases (offset within 'y' or 'z' in "from x import y as z")
|
||||
GotoTarget::ImportSymbolAlias {
|
||||
alias, import_from, ..
|
||||
} => {
|
||||
let symbol_name = alias.name.as_str();
|
||||
Some(definitions_for_imported_symbol(
|
||||
model,
|
||||
import_from,
|
||||
symbol_name,
|
||||
alias_resolution,
|
||||
Some(DefinitionsOrTargets::Definitions(
|
||||
definitions_for_imported_symbol(
|
||||
db,
|
||||
file,
|
||||
import_from,
|
||||
symbol_name,
|
||||
alias_resolution,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -421,9 +420,14 @@ impl GotoTarget<'_> {
|
||||
if alias_resolution == ImportAliasResolution::ResolveAliases {
|
||||
definitions_for_module(model, Some(alias.name.as_str()), 0)
|
||||
} else {
|
||||
alias.asname.as_ref().map(|name| {
|
||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
||||
})
|
||||
let alias_range = alias.asname.as_ref().unwrap().range;
|
||||
Some(DefinitionsOrTargets::Targets(
|
||||
crate::NavigationTargets::single(NavigationTarget {
|
||||
file,
|
||||
focus_range: alias_range,
|
||||
full_range: alias.range(),
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,44 +435,39 @@ impl GotoTarget<'_> {
|
||||
GotoTarget::KeywordArgument {
|
||||
keyword,
|
||||
call_expression,
|
||||
} => Some(definitions_for_keyword_argument(
|
||||
model,
|
||||
keyword,
|
||||
call_expression,
|
||||
} => Some(DefinitionsOrTargets::Definitions(
|
||||
definitions_for_keyword_argument(db, file, keyword, call_expression),
|
||||
)),
|
||||
|
||||
// For exception variables, they are their own definitions (like parameters)
|
||||
GotoTarget::ExceptVariable(except_handler) => {
|
||||
Some(vec![ResolvedDefinition::Definition(
|
||||
except_handler.definition(model),
|
||||
)])
|
||||
Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(except_handler.definition(model)),
|
||||
]))
|
||||
}
|
||||
|
||||
// Patterns are glorified assignments but we have to look them up by ident
|
||||
// because they're not expressions
|
||||
// For pattern match rest variables, they are their own definitions
|
||||
GotoTarget::PatternMatchRest(pattern_mapping) => {
|
||||
pattern_mapping.rest.as_ref().map(|name| {
|
||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
||||
})
|
||||
if let Some(rest_name) = &pattern_mapping.rest {
|
||||
let range = rest_name.range;
|
||||
Some(DefinitionsOrTargets::Targets(
|
||||
crate::NavigationTargets::single(NavigationTarget::new(file, range)),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| {
|
||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
||||
}),
|
||||
|
||||
GotoTarget::PatternKeywordArgument(pattern_keyword) => {
|
||||
let name = &pattern_keyword.attr;
|
||||
Some(definitions_for_name(
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
))
|
||||
}
|
||||
|
||||
GotoTarget::PatternMatchStarName(pattern_star) => {
|
||||
pattern_star.name.as_ref().map(|name| {
|
||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
||||
})
|
||||
// For pattern match as names, they are their own definitions
|
||||
GotoTarget::PatternMatchAsName(pattern_as) => {
|
||||
if let Some(name) = &pattern_as.name {
|
||||
let range = name.range;
|
||||
Some(DefinitionsOrTargets::Targets(
|
||||
crate::NavigationTargets::single(NavigationTarget::new(file, range)),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// For callables, both the definition of the callable and the actual function impl are relevant.
|
||||
@@ -477,82 +476,32 @@ impl GotoTarget<'_> {
|
||||
GotoTarget::Call { callable, call } => {
|
||||
let mut definitions = definitions_for_callable(model, call);
|
||||
let expr_definitions =
|
||||
definitions_for_expression(model, *callable).unwrap_or_default();
|
||||
definitions_for_expression(model, callable).unwrap_or_default();
|
||||
definitions.extend(expr_definitions);
|
||||
|
||||
if definitions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(definitions)
|
||||
Some(DefinitionsOrTargets::Definitions(definitions))
|
||||
}
|
||||
}
|
||||
|
||||
GotoTarget::BinOp { expression, .. } => {
|
||||
let (definitions, _) =
|
||||
ty_python_semantic::definitions_for_bin_op(model, expression)?;
|
||||
ty_python_semantic::definitions_for_bin_op(db, model, expression)?;
|
||||
|
||||
Some(definitions)
|
||||
Some(DefinitionsOrTargets::Definitions(definitions))
|
||||
}
|
||||
|
||||
GotoTarget::UnaryOp { expression, .. } => {
|
||||
let (definitions, _) =
|
||||
ty_python_semantic::definitions_for_unary_op(model, expression)?;
|
||||
ty_python_semantic::definitions_for_unary_op(db, model, expression)?;
|
||||
|
||||
Some(definitions)
|
||||
Some(DefinitionsOrTargets::Definitions(definitions))
|
||||
}
|
||||
|
||||
// String annotations sub-expressions require us to recurse into the sub-AST
|
||||
GotoTarget::StringAnnotationSubexpr {
|
||||
string_expr,
|
||||
subrange,
|
||||
..
|
||||
} => {
|
||||
let (subast, submodel) = model.enter_string_annotation(string_expr)?;
|
||||
let subexpr = covering_node(subast.syntax().into(), *subrange)
|
||||
.node()
|
||||
.as_expr_ref()?;
|
||||
definitions_for_expression(&submodel, subexpr)
|
||||
}
|
||||
|
||||
// nonlocal and global are essentially loads, but again they're statements,
|
||||
// so we need to look them up by ident
|
||||
GotoTarget::NonLocal { identifier } | GotoTarget::Globals { identifier } => {
|
||||
Some(definitions_for_name(
|
||||
model,
|
||||
identifier.as_str(),
|
||||
AnyNodeRef::Identifier(identifier),
|
||||
))
|
||||
}
|
||||
|
||||
// These are declarations of sorts, but they're stmts and not exprs, so look up by ident.
|
||||
GotoTarget::TypeParamTypeVarName(type_var) => {
|
||||
let name = &type_var.name;
|
||||
Some(definitions_for_name(
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
))
|
||||
}
|
||||
|
||||
GotoTarget::TypeParamParamSpecName(name) => {
|
||||
let name = &name.name;
|
||||
Some(definitions_for_name(
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
))
|
||||
}
|
||||
|
||||
GotoTarget::TypeParamTypeVarTupleName(name) => {
|
||||
let name = &name.name;
|
||||
Some(definitions_for_name(
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
))
|
||||
}
|
||||
};
|
||||
definitions.map(Definitions)
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the text representation of this goto target.
|
||||
@@ -570,7 +519,6 @@ impl GotoTarget<'_> {
|
||||
ast::ExprRef::Attribute(attr) => Some(Cow::Borrowed(attr.attr.as_str())),
|
||||
_ => None,
|
||||
},
|
||||
GotoTarget::StringAnnotationSubexpr { name, .. } => name.as_deref().map(Cow::Borrowed),
|
||||
GotoTarget::FunctionDef(function) => Some(Cow::Borrowed(function.name.as_str())),
|
||||
GotoTarget::ClassDef(class) => Some(Cow::Borrowed(class.name.as_str())),
|
||||
GotoTarget::Parameter(parameter) => Some(Cow::Borrowed(parameter.name.as_str())),
|
||||
@@ -631,7 +579,6 @@ impl GotoTarget<'_> {
|
||||
|
||||
/// Creates a `GotoTarget` from a `CoveringNode` and an offset within the node
|
||||
pub(crate) fn from_covering_node<'a>(
|
||||
model: &SemanticModel,
|
||||
covering_node: &crate::find_node::CoveringNode<'a>,
|
||||
offset: TextSize,
|
||||
tokens: &Tokens,
|
||||
@@ -831,31 +778,6 @@ impl GotoTarget<'_> {
|
||||
Some(GotoTarget::Expression(unary.into()))
|
||||
}
|
||||
|
||||
node @ AnyNodeRef::ExprStringLiteral(string_expr) => {
|
||||
// Check if we've clicked on a sub-GotoTarget inside a string annotation's sub-AST
|
||||
if let Some((subast, submodel)) = model.enter_string_annotation(string_expr)
|
||||
&& let Some(GotoTarget::Expression(subexpr)) = find_goto_target_impl(
|
||||
&submodel,
|
||||
subast.tokens(),
|
||||
subast.syntax().into(),
|
||||
offset,
|
||||
)
|
||||
{
|
||||
let name = match subexpr {
|
||||
ast::ExprRef::Name(name) => Some(name.id.to_string()),
|
||||
ast::ExprRef::Attribute(attr) => Some(attr.attr.to_string()),
|
||||
_ => None,
|
||||
};
|
||||
Some(GotoTarget::StringAnnotationSubexpr {
|
||||
string_expr,
|
||||
subrange: subexpr.range(),
|
||||
name,
|
||||
})
|
||||
} else {
|
||||
node.as_expr_ref().map(GotoTarget::Expression)
|
||||
}
|
||||
}
|
||||
|
||||
node => {
|
||||
// Check if this is seemingly a callable being invoked (the `x` in `x(...)`)
|
||||
let parent = covering_node.parent();
|
||||
@@ -891,7 +813,6 @@ impl Ranged for GotoTarget<'_> {
|
||||
GotoTarget::ImportModuleComponent {
|
||||
component_range, ..
|
||||
} => *component_range,
|
||||
GotoTarget::StringAnnotationSubexpr { subrange, .. } => *subrange,
|
||||
GotoTarget::ImportModuleAlias { alias } => alias.asname.as_ref().unwrap().range,
|
||||
GotoTarget::ExceptVariable(except) => except.name.as_ref().unwrap().range,
|
||||
GotoTarget::KeywordArgument { keyword, .. } => keyword.arg.as_ref().unwrap().range,
|
||||
@@ -911,9 +832,9 @@ impl Ranged for GotoTarget<'_> {
|
||||
}
|
||||
|
||||
/// Converts a collection of `ResolvedDefinition` items into `NavigationTarget` items.
|
||||
fn convert_resolved_definitions_to_targets<'db>(
|
||||
db: &'db dyn ty_python_semantic::Db,
|
||||
definitions: Vec<ty_python_semantic::ResolvedDefinition<'db>>,
|
||||
fn convert_resolved_definitions_to_targets(
|
||||
db: &dyn crate::Db,
|
||||
definitions: Vec<ty_python_semantic::ResolvedDefinition<'_>>,
|
||||
) -> Vec<crate::NavigationTarget> {
|
||||
definitions
|
||||
.into_iter()
|
||||
@@ -948,16 +869,14 @@ fn convert_resolved_definitions_to_targets<'db>(
|
||||
/// Shared helper to get definitions for an expr (that is presumably a name/attr)
|
||||
fn definitions_for_expression<'db>(
|
||||
model: &SemanticModel<'db>,
|
||||
expression: ruff_python_ast::ExprRef<'_>,
|
||||
expression: &ruff_python_ast::ExprRef<'_>,
|
||||
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
||||
match expression {
|
||||
ast::ExprRef::Name(name) => Some(definitions_for_name(
|
||||
model,
|
||||
name.id.as_str(),
|
||||
expression.into(),
|
||||
)),
|
||||
ast::ExprRef::Name(name) => Some(definitions_for_name(model.db(), model.file(), name)),
|
||||
ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute(
|
||||
model, attribute,
|
||||
model.db(),
|
||||
model.file(),
|
||||
attribute,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
@@ -968,7 +887,7 @@ fn definitions_for_callable<'db>(
|
||||
call: &ast::ExprCall,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
// Attempt to refine to a specific call
|
||||
let signature_info = call_signature_details(model, call);
|
||||
let signature_info = call_signature_details(model.db(), model, call);
|
||||
signature_info
|
||||
.into_iter()
|
||||
.filter_map(|signature| signature.definition.map(ResolvedDefinition::Definition))
|
||||
@@ -977,7 +896,7 @@ fn definitions_for_callable<'db>(
|
||||
|
||||
/// Shared helper to map and convert resolved definitions into navigation targets.
|
||||
fn definitions_to_navigation_targets<'db>(
|
||||
db: &dyn ty_python_semantic::Db,
|
||||
db: &dyn crate::Db,
|
||||
stub_mapper: Option<&StubMapper<'db>>,
|
||||
mut definitions: Vec<ty_python_semantic::ResolvedDefinition<'db>>,
|
||||
) -> Option<crate::NavigationTargets> {
|
||||
@@ -992,21 +911,12 @@ fn definitions_to_navigation_targets<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn find_goto_target<'a>(
|
||||
model: &'a SemanticModel,
|
||||
parsed: &'a ParsedModuleRef,
|
||||
pub(crate) fn find_goto_target(
|
||||
parsed: &ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
) -> Option<GotoTarget<'a>> {
|
||||
find_goto_target_impl(model, parsed.tokens(), parsed.syntax().into(), offset)
|
||||
}
|
||||
|
||||
pub(crate) fn find_goto_target_impl<'a>(
|
||||
model: &'a SemanticModel,
|
||||
tokens: &'a Tokens,
|
||||
syntax: AnyNodeRef<'a>,
|
||||
offset: TextSize,
|
||||
) -> Option<GotoTarget<'a>> {
|
||||
let token = tokens
|
||||
) -> Option<GotoTarget<'_>> {
|
||||
let token = parsed
|
||||
.tokens()
|
||||
.at_offset(offset)
|
||||
.max_by_key(|token| match token.kind() {
|
||||
TokenKind::Name
|
||||
@@ -1027,24 +937,26 @@ pub(crate) fn find_goto_target_impl<'a>(
|
||||
return None;
|
||||
}
|
||||
|
||||
let covering_node = covering_node(syntax, token.range())
|
||||
let covering_node = covering_node(parsed.syntax().into(), token.range())
|
||||
.find_first(|node| {
|
||||
node.is_identifier() || node.is_expression() || node.is_stmt_import_from()
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
GotoTarget::from_covering_node(model, &covering_node, offset, tokens)
|
||||
GotoTarget::from_covering_node(&covering_node, offset, parsed.tokens())
|
||||
}
|
||||
|
||||
/// Helper function to resolve a module name and create a navigation target.
|
||||
fn definitions_for_module<'db>(
|
||||
model: &SemanticModel<'db>,
|
||||
model: &SemanticModel,
|
||||
module: Option<&str>,
|
||||
level: u32,
|
||||
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
||||
) -> Option<DefinitionsOrTargets<'db>> {
|
||||
let module = model.resolve_module(module, level)?;
|
||||
let file = module.file(model.db())?;
|
||||
Some(vec![ResolvedDefinition::Module(file)])
|
||||
Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Module(file),
|
||||
]))
|
||||
}
|
||||
|
||||
/// Helper function to extract module component information from a dotted module name
|
||||
|
||||
@@ -16,9 +16,9 @@ pub fn goto_declaration(
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let declaration_targets = goto_target
|
||||
.get_definition_targets(&model, ImportAliasResolution::ResolveAliases)?
|
||||
.declaration_targets(db)?;
|
||||
@@ -889,190 +889,6 @@ def another_helper(path):
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCla<CURSOR>ss" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyCl<CURSOR>ass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None |<CURSOR> MyClass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation4() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass<CURSOR>" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation5() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass"<CURSOR> = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation_dangling1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass |" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation_dangling2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass | No" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "MyClass | No" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass | No" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation_dangling3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyClass | N<CURSOR>o" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_nested_instance_attribute() {
|
||||
let test = cursor_test(
|
||||
@@ -1254,45 +1070,6 @@ def outer():
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_nonlocal_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def outer():
|
||||
xy = "outer_value"
|
||||
|
||||
def inner():
|
||||
nonlocal x<CURSOR>y
|
||||
xy = "modified"
|
||||
return x # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:3:5
|
||||
|
|
||||
2 | def outer():
|
||||
3 | xy = "outer_value"
|
||||
| ^^
|
||||
4 |
|
||||
5 | def inner():
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:18
|
||||
|
|
||||
5 | def inner():
|
||||
6 | nonlocal xy
|
||||
| ^^
|
||||
7 | xy = "modified"
|
||||
8 | return x # Should find the nonlocal x declaration in outer scope
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_global_binding() {
|
||||
let test = cursor_test(
|
||||
@@ -1327,41 +1104,6 @@ def function():
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_global_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
global_var = "global_value"
|
||||
|
||||
def function():
|
||||
global global_<CURSOR>var
|
||||
global_var = "modified"
|
||||
return global_var # Should find the global variable declaration
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the global variable declaration, not the global statement
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | global_var = "global_value"
|
||||
| ^^^^^^^^^^
|
||||
3 |
|
||||
4 | def function():
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:12
|
||||
|
|
||||
4 | def function():
|
||||
5 | global global_var
|
||||
| ^^^^^^^^^^
|
||||
6 | global_var = "modified"
|
||||
7 | return global_var # Should find the global variable declaration
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_inherited_attribute() {
|
||||
let test = cursor_test(
|
||||
@@ -1397,486 +1139,6 @@ def function():
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:22
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:22
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:22
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_rest_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:23
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:23
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_rest_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:23
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_as_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:37
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:37
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_as_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:37
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_keyword_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:10:30
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^
|
||||
11 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:10:30
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^
|
||||
11 | x = ab
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_keyword_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:10:30
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^
|
||||
11 | x = ab
|
||||
|
|
||||
info: Source
|
||||
--> main.py:11:17
|
||||
|
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
11 | x = ab
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_class_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Click:
|
||||
| ^^^^^
|
||||
3 | __match_args__ = ("position", "button")
|
||||
4 | def __init__(self, pos, btn):
|
||||
|
|
||||
info: Source
|
||||
--> main.py:10:14
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^^^^
|
||||
11 | x = ab
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_match_class_field_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_typevar_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_typevar_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:37
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_typevar_spec_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:3:15
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:15
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_typevar_spec_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:3:15
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:43
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_typevar_tuple_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:14
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:14
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_typevar_tuple_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:14
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:38
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_property_getter_setter() {
|
||||
let test = cursor_test(
|
||||
|
||||
@@ -17,8 +17,8 @@ pub fn goto_definition(
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let definition_targets = goto_target
|
||||
.get_definition_targets(&model, ImportAliasResolution::ResolveAliases)?
|
||||
.definition_targets(db)?;
|
||||
|
||||
@@ -3,11 +3,10 @@ use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::files::File;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
|
||||
/// Find all references to a symbol at the given position.
|
||||
/// Search for references across all files in the project.
|
||||
pub fn find_references(
|
||||
pub fn goto_references(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
offset: TextSize,
|
||||
@@ -15,10 +14,9 @@ pub fn find_references(
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
// Get the definitions for the symbol at the cursor position
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
let mode = if include_declaration {
|
||||
ReferencesMode::References
|
||||
@@ -41,7 +39,7 @@ mod tests {
|
||||
impl CursorTest {
|
||||
fn references(&self) -> String {
|
||||
let Some(mut reference_results) =
|
||||
find_references(&self.db, self.cursor.file, self.cursor.offset, true)
|
||||
goto_references(&self.db, self.cursor.file, self.cursor.offset, true)
|
||||
else {
|
||||
return "No references found".to_string();
|
||||
};
|
||||
@@ -84,7 +82,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parameter_references_in_function() {
|
||||
fn test_parameter_references_in_function() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
def calculate_sum(<CURSOR>value: int) -> int:
|
||||
@@ -149,28 +147,29 @@ result = calculate_sum(value=42)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonlocal_variable_references() {
|
||||
#[ignore] // TODO: Enable when nonlocal support is fully implemented in goto.rs
|
||||
fn test_nonlocal_variable_references() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
def outer_function():
|
||||
coun<CURSOR>ter = 0
|
||||
|
||||
|
||||
def increment():
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
return counter
|
||||
|
||||
|
||||
def decrement():
|
||||
nonlocal counter
|
||||
counter -= 1
|
||||
return counter
|
||||
|
||||
|
||||
# Use counter in outer scope
|
||||
initial = counter
|
||||
increment()
|
||||
decrement()
|
||||
final = counter
|
||||
|
||||
|
||||
return increment, decrement
|
||||
",
|
||||
);
|
||||
@@ -182,7 +181,7 @@ def outer_function():
|
||||
2 | def outer_function():
|
||||
3 | counter = 0
|
||||
| ^^^^^^^
|
||||
4 |
|
||||
4 |
|
||||
5 | def increment():
|
||||
|
|
||||
|
||||
@@ -213,7 +212,7 @@ def outer_function():
|
||||
7 | counter += 1
|
||||
8 | return counter
|
||||
| ^^^^^^^
|
||||
9 |
|
||||
9 |
|
||||
10 | def decrement():
|
||||
|
|
||||
|
||||
@@ -244,7 +243,7 @@ def outer_function():
|
||||
12 | counter -= 1
|
||||
13 | return counter
|
||||
| ^^^^^^^
|
||||
14 |
|
||||
14 |
|
||||
15 | # Use counter in outer scope
|
||||
|
|
||||
|
||||
@@ -265,14 +264,15 @@ def outer_function():
|
||||
18 | decrement()
|
||||
19 | final = counter
|
||||
| ^^^^^^^
|
||||
20 |
|
||||
20 |
|
||||
21 | return increment, decrement
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_variable_references() {
|
||||
#[ignore] // TODO: Enable when global support is fully implemented in goto.rs
|
||||
fn test_global_variable_references() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
glo<CURSOR>bal_counter = 0
|
||||
@@ -389,7 +389,7 @@ final_value = global_counter
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn except_handler_variable_references() {
|
||||
fn test_except_handler_variable_references() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
try:
|
||||
@@ -450,7 +450,7 @@ except ValueError as err:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_match_as_references() {
|
||||
fn test_pattern_match_as_references() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
match x:
|
||||
@@ -498,7 +498,7 @@ match x:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_match_mapping_rest_references() {
|
||||
fn test_pattern_match_mapping_rest_references() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
match data:
|
||||
@@ -553,7 +553,7 @@ match data:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_definition_references() {
|
||||
fn test_function_definition_references() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
def my_func<CURSOR>tion():
|
||||
@@ -632,7 +632,7 @@ value = my_function
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_definition_references() {
|
||||
fn test_class_definition_references() {
|
||||
let test = cursor_test(
|
||||
"
|
||||
class My<CURSOR>Class:
|
||||
@@ -711,741 +711,7 @@ cls = MyClass
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCla<CURSOR>ss" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyCl<CURSOR>ass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None |<CURSOR> MyClass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation4() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass<CURSOR>" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation5() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass"<CURSOR> = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation_dangling1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass |" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation_dangling2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass | No" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass | No" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "MyClass | No" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation_dangling3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyClass | N<CURSOR>o" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:4:22
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:4:22
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_rest_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:4:23
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_rest_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:4:23
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_as_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:4:37
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_as_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:4:37
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
5 | x = ab
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_keyword_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:10:30
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^
|
||||
11 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:11:17
|
||||
|
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
11 | x = ab
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_keyword_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:10:30
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^
|
||||
11 | x = ab
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:11:17
|
||||
|
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
11 | x = ab
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_class_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Click:
|
||||
| ^^^^^
|
||||
3 | __match_args__ = ("position", "button")
|
||||
4 | def __init__(self, pos, btn):
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:8:20
|
||||
|
|
||||
6 | self.button: str = btn
|
||||
7 |
|
||||
8 | def my_func(event: Click):
|
||||
| ^^^^^
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:10:14
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^^^^
|
||||
11 | x = ab
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_class_field_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_typevar_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:2:37
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:2:46
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_typevar_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:2:37
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:2:46
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_typevar_spec_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:3:15
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:3:43
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:3:53
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_typevar_spec_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:3:15
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:3:43
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:3:53
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_typevar_tuple_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:14
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:2:38
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:2:50
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_typevar_tuple_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:14
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:2:38
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:2:50
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_file_function_references() {
|
||||
fn test_multi_file_function_references() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"utils.py",
|
||||
@@ -1471,7 +737,7 @@ from utils import func
|
||||
class DataProcessor:
|
||||
def __init__(self):
|
||||
self.multiplier = func
|
||||
|
||||
|
||||
def process(self, value):
|
||||
return func(value)
|
||||
",
|
||||
@@ -1535,14 +801,14 @@ class DataProcessor:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_file_class_attribute_references() {
|
||||
fn test_multi_file_class_attribute_references() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"models.py",
|
||||
"
|
||||
class MyModel:
|
||||
a<CURSOR>ttr = 42
|
||||
|
||||
|
||||
def get_attribute(self):
|
||||
return MyModel.attr
|
||||
",
|
||||
@@ -1613,7 +879,7 @@ def process_model():
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_alias_references_should_not_resolve_to_original() {
|
||||
fn test_import_alias_references_should_not_resolve_to_original() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"original.py",
|
||||
@@ -11,9 +11,9 @@ pub fn goto_type_definition(
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let ty = goto_target.inferred_type(&model)?;
|
||||
|
||||
tracing::debug!("Inferred type of covering node is {}", ty.display(db));
|
||||
@@ -744,502 +744,6 @@ mod tests {
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCla<CURSOR>ss" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyCl<CURSOR>ass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None |<CURSOR> MyClass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:4
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/types.pyi:950:11
|
||||
|
|
||||
948 | if sys.version_info >= (3, 10):
|
||||
949 | @final
|
||||
950 | class NoneType:
|
||||
| ^^^^^^^^
|
||||
951 | """The type of the None singleton."""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:4
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation4() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass<CURSOR>" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation5() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass"<CURSOR> = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:7
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| ^^^^^^^
|
||||
5 | """some docs"""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:4
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/types.pyi:950:11
|
||||
|
|
||||
948 | if sys.version_info >= (3, 10):
|
||||
949 | @final
|
||||
950 | class NoneType:
|
||||
| ^^^^^^^^
|
||||
951 | """The type of the None singleton."""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:4
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation_dangling1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass |" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/ty_extensions.pyi:20:1
|
||||
|
|
||||
19 | # Types
|
||||
20 | Unknown = object()
|
||||
| ^^^^^^^
|
||||
21 | AlwaysTruthy = object()
|
||||
22 | AlwaysFalsy = object()
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:4
|
||||
|
|
||||
2 | a: "MyClass |" = 1
|
||||
| ^^^^^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation_dangling2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass | No" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation_dangling3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyClass | N<CURSOR>o" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_rest_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_rest_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_as_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_as_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_keyword_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_keyword_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_class_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Click:
|
||||
| ^^^^^
|
||||
3 | __match_args__ = ("position", "button")
|
||||
4 | def __init__(self, pos, btn):
|
||||
|
|
||||
info: Source
|
||||
--> main.py:10:14
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^^^^
|
||||
11 | x = ab
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_class_field_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_typevar_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_typevar_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:37
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_typevar_spec_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_typevar_spec_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_typevar_tuple_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_typevar_tuple_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_on_keyword_argument() {
|
||||
let test = cursor_test(
|
||||
@@ -1338,118 +842,6 @@ f(**kwargs<CURSOR>)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_nonlocal_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def outer():
|
||||
x = "outer_value"
|
||||
|
||||
def inner():
|
||||
nonlocal x
|
||||
x = "modified"
|
||||
return x<CURSOR> # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:915:7
|
||||
|
|
||||
914 | @disjoint_base
|
||||
915 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
916 | """str(object='') -> str
|
||||
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:8:16
|
||||
|
|
||||
6 | nonlocal x
|
||||
7 | x = "modified"
|
||||
8 | return x # Should find the nonlocal x declaration in outer scope
|
||||
| ^
|
||||
9 |
|
||||
10 | return inner
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_nonlocal_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def outer():
|
||||
xy = "outer_value"
|
||||
|
||||
def inner():
|
||||
nonlocal x<CURSOR>y
|
||||
xy = "modified"
|
||||
return x # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_global_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
global_var = "global_value"
|
||||
|
||||
def function():
|
||||
global global_var
|
||||
global_var = "modified"
|
||||
return global_<CURSOR>var # Should find the global variable declaration
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the global variable declaration, not the global statement
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:915:7
|
||||
|
|
||||
914 | @disjoint_base
|
||||
915 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
916 | """str(object='') -> str
|
||||
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:7:12
|
||||
|
|
||||
5 | global global_var
|
||||
6 | global_var = "modified"
|
||||
7 | return global_var # Should find the global variable declaration
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_global_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
global_var = "global_value"
|
||||
|
||||
def function():
|
||||
global global_<CURSOR>var
|
||||
global_var = "modified"
|
||||
return global_var # Should find the global variable declaration
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the global variable declaration, not the global statement
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_expression_with_builtin() {
|
||||
let test = cursor_test(
|
||||
|
||||
@@ -11,8 +11,7 @@ use ty_python_semantic::{DisplaySettings, SemanticModel};
|
||||
|
||||
pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &parsed, offset)?;
|
||||
let goto_target = find_goto_target(&parsed, offset)?;
|
||||
|
||||
if let GotoTarget::Expression(expr) = goto_target {
|
||||
if expr.is_literal_expr() {
|
||||
@@ -20,6 +19,7 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
|
||||
}
|
||||
}
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let docs = goto_target
|
||||
.get_definition_targets(
|
||||
&model,
|
||||
@@ -904,191 +904,6 @@ mod tests {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCla<CURSOR>ss" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
MyClass
|
||||
---------------------------------------------
|
||||
some docs
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
MyClass
|
||||
```
|
||||
---
|
||||
some docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
| ^^^^^-^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyCl<CURSOR>ass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
some docs
|
||||
|
||||
---------------------------------------------
|
||||
some docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None |<CURSOR> MyClass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation4() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass<CURSOR>" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
some docs
|
||||
|
||||
---------------------------------------------
|
||||
some docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation5() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass"<CURSOR> = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation_dangling1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass |" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation_dangling2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass | No" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
some docs
|
||||
|
||||
---------------------------------------------
|
||||
some docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass | No" = 1
|
||||
| ^^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation_dangling3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyClass | N<CURSOR>o" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_overload_type_disambiguated1() {
|
||||
let test = CursorTest::builder()
|
||||
@@ -1648,509 +1463,6 @@ def ab(a: int, *, c: int):
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_nonlocal_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def outer():
|
||||
x = "outer_value"
|
||||
|
||||
def inner():
|
||||
nonlocal x
|
||||
x = "modified"
|
||||
return x<CURSOR> # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
Literal["modified"]
|
||||
---------------------------------------------
|
||||
```python
|
||||
Literal["modified"]
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:8:16
|
||||
|
|
||||
6 | nonlocal x
|
||||
7 | x = "modified"
|
||||
8 | return x # Should find the nonlocal x declaration in outer scope
|
||||
| ^- Cursor offset
|
||||
| |
|
||||
| source
|
||||
9 |
|
||||
10 | return inner
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_nonlocal_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def outer():
|
||||
xy = "outer_value"
|
||||
|
||||
def inner():
|
||||
nonlocal x<CURSOR>y
|
||||
xy = "modified"
|
||||
return x # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_global_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
global_var = "global_value"
|
||||
|
||||
def function():
|
||||
global global_var
|
||||
global_var = "modified"
|
||||
return global_<CURSOR>var # Should find the global variable declaration
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the global variable declaration, not the global statement
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
Literal["modified"]
|
||||
---------------------------------------------
|
||||
```python
|
||||
Literal["modified"]
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:7:12
|
||||
|
|
||||
5 | global global_var
|
||||
6 | global_var = "modified"
|
||||
7 | return global_var # Should find the global variable declaration
|
||||
| ^^^^^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_global_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
global_var = "global_value"
|
||||
|
||||
def function():
|
||||
global global_<CURSOR>var
|
||||
global_var = "modified"
|
||||
return global_var # Should find the global variable declaration
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should find the global variable declaration, not the global statement
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
@Todo
|
||||
---------------------------------------------
|
||||
```python
|
||||
@Todo
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
5 | x = ab
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_rest_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_rest_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
@Todo
|
||||
---------------------------------------------
|
||||
```python
|
||||
@Todo
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
5 | x = ab
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_as_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_as_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
@Todo
|
||||
---------------------------------------------
|
||||
```python
|
||||
@Todo
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
5 | x = ab
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_keyword_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_keyword_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
@Todo
|
||||
---------------------------------------------
|
||||
```python
|
||||
@Todo
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:11:17
|
||||
|
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
11 | x = ab
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_class_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<class 'Click'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
<class 'Click'>
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:10:14
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
11 | x = ab
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_match_class_field_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_typevar_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
AB@Alias1 (invariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
AB@Alias1 (invariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_typevar_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
AB@Alias1 (invariant)
|
||||
---------------------------------------------
|
||||
```python
|
||||
AB@Alias1 (invariant)
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:37
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_typevar_spec_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_typevar_spec_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(
|
||||
...
|
||||
) -> tuple[typing.ParamSpec]
|
||||
---------------------------------------------
|
||||
```python
|
||||
(
|
||||
...
|
||||
) -> tuple[typing.ParamSpec]
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:3:43
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_typevar_tuple_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_typevar_tuple_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
@Todo
|
||||
---------------------------------------------
|
||||
```python
|
||||
@Todo
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:38
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_module_import() {
|
||||
let mut test = cursor_test(
|
||||
|
||||
@@ -1946,131 +1946,6 @@ mod tests {
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_name_binding() {
|
||||
let mut test = inlay_hint_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ab]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ab]:
|
||||
x[: @Todo] = ab
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_rest_binding() {
|
||||
let mut test = inlay_hint_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *ab]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *ab]:
|
||||
x[: @Todo] = ab
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_as_binding() {
|
||||
let mut test = inlay_hint_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as ab]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as ab]:
|
||||
x[: @Todo] = ab
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_keyword_binding() {
|
||||
let mut test = inlay_hint_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
x[: @Todo] = ab
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typevar_name_binding() {
|
||||
let mut test = inlay_hint_test(
|
||||
r#"
|
||||
type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @"type Alias1[AB: int = bool] = tuple[AB, list[AB]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typevar_spec_binding() {
|
||||
let mut test = inlay_hint_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing import Callable
|
||||
type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typevar_tuple_binding() {
|
||||
let mut test = inlay_hint_test(
|
||||
r#"
|
||||
type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @"type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_many_literals() {
|
||||
let mut test = inlay_hint_test(
|
||||
@@ -6419,22 +6294,6 @@ mod tests {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_literal_type_alias_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
from typing import Literal
|
||||
|
||||
a = Literal['a', 'b', 'c']",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing import Literal
|
||||
|
||||
a[: <typing.Literal special form>] = Literal['a', 'b', 'c']
|
||||
");
|
||||
}
|
||||
|
||||
struct InlayHintLocationDiagnostic {
|
||||
source: FileRange,
|
||||
target: FileRange,
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
reason = "Prefer System trait methods over std methods in ty crates"
|
||||
)]
|
||||
mod all_symbols;
|
||||
mod code_action;
|
||||
mod completion;
|
||||
mod doc_highlights;
|
||||
mod docstring;
|
||||
mod document_symbols;
|
||||
mod find_node;
|
||||
mod find_references;
|
||||
mod goto;
|
||||
mod goto_declaration;
|
||||
mod goto_definition;
|
||||
mod goto_references;
|
||||
mod goto_type_definition;
|
||||
mod hover;
|
||||
mod importer;
|
||||
@@ -28,12 +27,11 @@ mod symbols;
|
||||
mod workspace_symbols;
|
||||
|
||||
pub use all_symbols::{AllSymbolInfo, all_symbols};
|
||||
pub use code_action::{QuickFix, code_actions};
|
||||
pub use completion::{Completion, CompletionKind, CompletionSettings, completion};
|
||||
pub use doc_highlights::document_highlights;
|
||||
pub use document_symbols::document_symbols;
|
||||
pub use find_references::find_references;
|
||||
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
||||
pub use goto_references::goto_references;
|
||||
pub use hover::hover;
|
||||
pub use inlay_hints::{
|
||||
InlayHintKind, InlayHintLabel, InlayHintSettings, InlayHintTextEdit, inlay_hints,
|
||||
|
||||
@@ -122,10 +122,10 @@ fn references_for_file(
|
||||
) {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
let mut finder = LocalReferencesFinder {
|
||||
model: &model,
|
||||
db,
|
||||
file,
|
||||
target_definitions,
|
||||
references,
|
||||
mode,
|
||||
@@ -157,7 +157,8 @@ fn is_symbol_externally_visible(goto_target: &GotoTarget<'_>) -> bool {
|
||||
|
||||
/// AST visitor to find all references to a specific symbol by comparing semantic definitions
|
||||
struct LocalReferencesFinder<'a> {
|
||||
model: &'a SemanticModel<'a>,
|
||||
db: &'a dyn Db,
|
||||
file: File,
|
||||
tokens: &'a Tokens,
|
||||
target_definitions: &'a [NavigationTarget],
|
||||
references: &'a mut Vec<ReferenceTarget>,
|
||||
@@ -219,11 +220,6 @@ impl<'a> SourceOrderVisitor<'a> for LocalReferencesFinder<'a> {
|
||||
self.check_identifier_reference(name);
|
||||
}
|
||||
}
|
||||
AnyNodeRef::PatternMatchStar(pattern_star) if self.should_include_declaration() => {
|
||||
if let Some(name) = &pattern_star.name {
|
||||
self.check_identifier_reference(name);
|
||||
}
|
||||
}
|
||||
AnyNodeRef::PatternMatchMapping(pattern_mapping)
|
||||
if self.should_include_declaration() =>
|
||||
{
|
||||
@@ -231,31 +227,6 @@ impl<'a> SourceOrderVisitor<'a> for LocalReferencesFinder<'a> {
|
||||
self.check_identifier_reference(rest_name);
|
||||
}
|
||||
}
|
||||
AnyNodeRef::TypeParamParamSpec(param_spec) if self.should_include_declaration() => {
|
||||
self.check_identifier_reference(¶m_spec.name);
|
||||
}
|
||||
AnyNodeRef::TypeParamTypeVarTuple(param_tuple) if self.should_include_declaration() => {
|
||||
self.check_identifier_reference(¶m_tuple.name);
|
||||
}
|
||||
AnyNodeRef::TypeParamTypeVar(param_var) if self.should_include_declaration() => {
|
||||
self.check_identifier_reference(¶m_var.name);
|
||||
}
|
||||
AnyNodeRef::ExprStringLiteral(string_expr) if self.should_include_declaration() => {
|
||||
// Highlight the sub-AST of a string annotation
|
||||
if let Some((sub_ast, sub_model)) = self.model.enter_string_annotation(string_expr)
|
||||
{
|
||||
let mut sub_finder = LocalReferencesFinder {
|
||||
model: &sub_model,
|
||||
target_definitions: self.target_definitions,
|
||||
references: self.references,
|
||||
mode: self.mode,
|
||||
tokens: sub_ast.tokens(),
|
||||
target_text: self.target_text,
|
||||
ancestors: Vec::new(),
|
||||
};
|
||||
sub_finder.visit_expr(sub_ast.expr());
|
||||
}
|
||||
}
|
||||
AnyNodeRef::Alias(alias) if self.should_include_declaration() => {
|
||||
// Handle import alias declarations
|
||||
if let Some(asname) = &alias.asname {
|
||||
@@ -314,13 +285,15 @@ impl LocalReferencesFinder<'_> {
|
||||
// the node is fine here. Offsets matter only for import statements
|
||||
// where the identifier might be a multi-part module name.
|
||||
let offset = covering_node.node().start();
|
||||
|
||||
if let Some(goto_target) =
|
||||
GotoTarget::from_covering_node(self.model, covering_node, offset, self.tokens)
|
||||
GotoTarget::from_covering_node(covering_node, offset, self.tokens)
|
||||
{
|
||||
// Get the definitions for this goto target
|
||||
let model = SemanticModel::new(self.db, self.file);
|
||||
if let Some(current_definitions_nav) = goto_target
|
||||
.get_definition_targets(self.model, ImportAliasResolution::PreserveAliases)
|
||||
.and_then(|definitions| definitions.declaration_targets(self.model.db()))
|
||||
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)
|
||||
.and_then(|definitions| definitions.declaration_targets(self.db))
|
||||
{
|
||||
let current_definitions: Vec<NavigationTarget> =
|
||||
current_definitions_nav.into_iter().collect();
|
||||
@@ -329,7 +302,7 @@ impl LocalReferencesFinder<'_> {
|
||||
// Determine if this is a read or write reference
|
||||
let kind = self.determine_reference_kind(covering_node);
|
||||
let target =
|
||||
ReferenceTarget::new(self.model.file(), covering_node.node().range(), kind);
|
||||
ReferenceTarget::new(self.file, covering_node.node().range(), kind);
|
||||
self.references.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
// Get the definitions for the symbol at the offset
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
// Don't allow renaming of import module components
|
||||
if matches!(
|
||||
@@ -59,10 +59,9 @@ pub fn rename(
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
// Get the definitions for the symbol at the offset
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
// Clients shouldn't call us with an empty new name, but just in case...
|
||||
if new_name.is_empty() {
|
||||
@@ -339,546 +338,6 @@ class DataProcessor:
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCla<CURSOR>ss" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| -------
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyCl<CURSOR>ass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| -------
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None |<CURSOR> MyClass" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation4() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass<CURSOR>" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | a: "None | MyClass" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| -------
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation5() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "None | MyClass"<CURSOR> = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation_dangling1() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass |" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation_dangling2() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyCl<CURSOR>ass | No" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | a: "MyClass | No" = 1
|
||||
| ^^^^^^^
|
||||
3 |
|
||||
4 | class MyClass:
|
||||
| -------
|
||||
5 | """some docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_string_annotation_dangling3() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a: "MyClass | N<CURSOR>o" = 1
|
||||
|
||||
class MyClass:
|
||||
"""some docs"""
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:4:22
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
| --
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:4:22
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
| --
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_rest_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:4:23
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
| --
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_rest_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", *ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:4:23
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", *ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
| --
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_as_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as a<CURSOR>b]:
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:4:37
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
| --
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_as_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def my_func(command: str):
|
||||
match command.split():
|
||||
case ["get", ("a" | "b") as ab]:
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r#"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:4:37
|
||||
|
|
||||
2 | def my_func(command: str):
|
||||
3 | match command.split():
|
||||
4 | case ["get", ("a" | "b") as ab]:
|
||||
| ^^
|
||||
5 | x = ab
|
||||
| --
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_keyword_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:10:30
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^
|
||||
11 | x = ab
|
||||
| --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_keyword_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
x = a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 2 locations)
|
||||
--> main.py:10:30
|
||||
|
|
||||
8 | def my_func(event: Click):
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| ^^
|
||||
11 | x = ab
|
||||
| --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_class_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r#"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Click:
|
||||
| ^^^^^
|
||||
3 | __match_args__ = ("position", "button")
|
||||
4 | def __init__(self, pos, btn):
|
||||
|
|
||||
::: main.py:8:20
|
||||
|
|
||||
6 | self.button: str = btn
|
||||
7 |
|
||||
8 | def my_func(event: Click):
|
||||
| -----
|
||||
9 | match event:
|
||||
10 | case Click(x, button=ab):
|
||||
| -----
|
||||
11 | x = ab
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_match_class_field_name() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class Click:
|
||||
__match_args__ = ("position", "button")
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
x = ab
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @"Cannot rename");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_typevar_name_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^ -- --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_typevar_name_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
|
||||
| ^^ -- --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_typevar_spec_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:3:15
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^ -- --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_typevar_spec_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Callable
|
||||
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:3:15
|
||||
|
|
||||
2 | from typing import Callable
|
||||
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
|
||||
| ^^ -- --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_typevar_tuple_stmt() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:2:14
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^ -- --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_typevar_tuple_binding() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.rename("XY"), @r"
|
||||
info[rename]: Rename symbol (found 3 locations)
|
||||
--> main.py:2:14
|
||||
|
|
||||
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
|
||||
| ^^ -- --
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cannot_rename_import_module_component() {
|
||||
// Test that we cannot rename parts of module names in import statements
|
||||
|
||||
@@ -187,9 +187,9 @@ impl Deref for SemanticTokens {
|
||||
/// Pass None to get tokens for the entire file.
|
||||
pub fn semantic_tokens(db: &dyn Db, file: File, range: Option<TextRange>) -> SemanticTokens {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let semantic_model = SemanticModel::new(db, file);
|
||||
|
||||
let mut visitor = SemanticTokenVisitor::new(&model, range);
|
||||
let mut visitor = SemanticTokenVisitor::new(&semantic_model, file, range);
|
||||
visitor.visit_body(parsed.suite());
|
||||
|
||||
SemanticTokens::new(visitor.tokens)
|
||||
@@ -197,7 +197,8 @@ pub fn semantic_tokens(db: &dyn Db, file: File, range: Option<TextRange>) -> Sem
|
||||
|
||||
/// AST visitor that collects semantic tokens.
|
||||
struct SemanticTokenVisitor<'db> {
|
||||
model: &'db SemanticModel<'db>,
|
||||
semantic_model: &'db SemanticModel<'db>,
|
||||
file: File,
|
||||
tokens: Vec<SemanticToken>,
|
||||
in_class_scope: bool,
|
||||
in_type_annotation: bool,
|
||||
@@ -206,9 +207,14 @@ struct SemanticTokenVisitor<'db> {
|
||||
}
|
||||
|
||||
impl<'db> SemanticTokenVisitor<'db> {
|
||||
fn new(model: &'db SemanticModel<'db>, range_filter: Option<TextRange>) -> Self {
|
||||
fn new(
|
||||
semantic_model: &'db SemanticModel<'db>,
|
||||
file: File,
|
||||
range_filter: Option<TextRange>,
|
||||
) -> Self {
|
||||
Self {
|
||||
model,
|
||||
semantic_model,
|
||||
file,
|
||||
tokens: Vec::new(),
|
||||
in_class_scope: false,
|
||||
in_target_creating_definition: false,
|
||||
@@ -259,7 +265,7 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
|
||||
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
|
||||
// First try to classify the token based on its definition kind.
|
||||
let definition = definition_for_name(self.model, name);
|
||||
let definition = definition_for_name(self.semantic_model.db(), self.file, name);
|
||||
|
||||
if let Some(definition) = definition {
|
||||
let name_str = name.id.as_str();
|
||||
@@ -269,7 +275,7 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
}
|
||||
|
||||
// Fall back to type-based classification.
|
||||
let ty = name.inferred_type(self.model);
|
||||
let ty = name.inferred_type(self.semantic_model);
|
||||
let name_str = name.id.as_str();
|
||||
self.classify_from_type_and_name_str(ty, name_str)
|
||||
}
|
||||
@@ -280,7 +286,7 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
name_str: &str,
|
||||
) -> Option<(SemanticTokenType, SemanticTokenModifier)> {
|
||||
let mut modifiers = SemanticTokenModifier::empty();
|
||||
let db = self.model.db();
|
||||
let db = self.semantic_model.db();
|
||||
let model = SemanticModel::new(db, definition.file(db));
|
||||
|
||||
match definition.kind(db) {
|
||||
@@ -706,12 +712,12 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
for alias in &import.names {
|
||||
if let Some(asname) = &alias.asname {
|
||||
// For aliased imports (from X import Y as Z), classify Z based on what Y is
|
||||
let ty = alias.inferred_type(self.model);
|
||||
let ty = alias.inferred_type(self.semantic_model);
|
||||
let (token_type, modifiers) = self.classify_from_alias_type(ty, asname);
|
||||
self.add_token(asname, token_type, modifiers);
|
||||
} else {
|
||||
// For direct imports (from X import Y), use semantic classification
|
||||
let ty = alias.inferred_type(self.model);
|
||||
let ty = alias.inferred_type(self.semantic_model);
|
||||
let (token_type, modifiers) =
|
||||
self.classify_from_alias_type(ty, &alias.name);
|
||||
self.add_token(&alias.name, token_type, modifiers);
|
||||
@@ -831,7 +837,7 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
self.visit_expr(&attr.value);
|
||||
|
||||
// Then add token for the attribute name (e.g., 'path' in 'os.path')
|
||||
let ty = expr.inferred_type(self.model);
|
||||
let ty = expr.inferred_type(self.semantic_model);
|
||||
let (token_type, modifiers) =
|
||||
Self::classify_from_type_for_attribute(ty, &attr.attr);
|
||||
self.add_token(&attr.attr, token_type, modifiers);
|
||||
@@ -875,17 +881,6 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
|
||||
self.visit_expr(&named.value);
|
||||
}
|
||||
ast::Expr::StringLiteral(string_expr) => {
|
||||
// Highlight the sub-AST of a string annotation
|
||||
if let Some((sub_ast, sub_model)) = self.model.enter_string_annotation(string_expr)
|
||||
{
|
||||
let mut sub_visitor = SemanticTokenVisitor::new(&sub_model, None);
|
||||
sub_visitor.visit_expr(sub_ast.expr());
|
||||
self.tokens.extend(sub_visitor.tokens);
|
||||
} else {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// For all other expression types, let the default visitor handle them
|
||||
walk_expr(self, expr);
|
||||
@@ -1060,33 +1055,12 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
);
|
||||
}
|
||||
}
|
||||
ast::Pattern::MatchStar(pattern_star) => {
|
||||
// Just the one ident here
|
||||
if let Some(rest_name) = &pattern_star.name {
|
||||
self.add_token(
|
||||
rest_name.range(),
|
||||
SemanticTokenType::Variable,
|
||||
SemanticTokenModifier::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// For all other pattern types, use the default walker
|
||||
ruff_python_ast::visitor::source_order::walk_pattern(self, pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_comprehension(&mut self, comp: &ast::Comprehension) {
|
||||
self.in_target_creating_definition = true;
|
||||
self.visit_expr(&comp.target);
|
||||
self.in_target_creating_definition = false;
|
||||
|
||||
self.visit_expr(&comp.iter);
|
||||
for if_clause in &comp.ifs {
|
||||
self.visit_expr(if_clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1590,50 +1564,6 @@ from mymodule import CONSTANT, my_function, MyClass
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_str_annotation() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
x: int = 1
|
||||
y: "int" = 1
|
||||
z = "int"
|
||||
w1: "int | str" = "hello"
|
||||
w2: "int | sr" = "hello"
|
||||
w3: "int | " = "hello"
|
||||
w4: "float"
|
||||
w5: "float
|
||||
"#,
|
||||
);
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
|
||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
||||
"x" @ 1..2: Variable [definition]
|
||||
"int" @ 4..7: Class
|
||||
"1" @ 10..11: Number
|
||||
"y" @ 12..13: Variable [definition]
|
||||
"int" @ 16..19: Class
|
||||
"1" @ 23..24: Number
|
||||
"z" @ 25..26: Variable [definition]
|
||||
"\"int\"" @ 29..34: String
|
||||
"w1" @ 35..37: Variable [definition]
|
||||
"int" @ 40..43: Class
|
||||
"str" @ 46..49: Class
|
||||
"\"hello\"" @ 53..60: String
|
||||
"w2" @ 61..63: Variable [definition]
|
||||
"int" @ 66..69: Class
|
||||
"sr" @ 72..74: Variable
|
||||
"\"hello\"" @ 78..85: String
|
||||
"w3" @ 86..88: Variable [definition]
|
||||
"\"int | \"" @ 90..98: String
|
||||
"\"hello\"" @ 101..108: String
|
||||
"w4" @ 109..111: Variable [definition]
|
||||
"float" @ 114..119: Class
|
||||
"w5" @ 121..123: Variable [definition]
|
||||
"float" @ 126..131: Class
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attribute_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
@@ -2495,7 +2425,6 @@ def process_data(data):
|
||||
"rest" @ 154..158: Variable
|
||||
"person" @ 181..187: Variable
|
||||
"first" @ 202..207: Variable
|
||||
"remaining" @ 210..219: Variable
|
||||
"sequence" @ 224..232: Variable
|
||||
"print" @ 246..251: Function
|
||||
"First: " @ 254..261: String
|
||||
@@ -2666,50 +2595,6 @@ with open("file.txt") as f:
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comprehensions() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
list_comp = [x for x in range(10) if x % 2 == 0]
|
||||
set_comp = {x for x in range(10)}
|
||||
dict_comp = {k: v for k, v in zip(["a", "b"], [1, 2])}
|
||||
generator = (x for x in range(10))
|
||||
"#,
|
||||
);
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
||||
"list_comp" @ 1..10: Variable [definition]
|
||||
"x" @ 14..15: Variable
|
||||
"x" @ 20..21: Variable [definition]
|
||||
"range" @ 25..30: Class
|
||||
"10" @ 31..33: Number
|
||||
"x" @ 38..39: Variable
|
||||
"2" @ 42..43: Number
|
||||
"0" @ 47..48: Number
|
||||
"set_comp" @ 50..58: Variable [definition]
|
||||
"x" @ 62..63: Variable
|
||||
"x" @ 68..69: Variable [definition]
|
||||
"range" @ 73..78: Class
|
||||
"10" @ 79..81: Number
|
||||
"dict_comp" @ 84..93: Variable [definition]
|
||||
"k" @ 97..98: Variable
|
||||
"v" @ 100..101: Variable
|
||||
"k" @ 106..107: Variable [definition]
|
||||
"v" @ 109..110: Variable [definition]
|
||||
"zip" @ 114..117: Class
|
||||
"\"a\"" @ 119..122: String
|
||||
"\"b\"" @ 124..127: String
|
||||
"1" @ 131..132: Number
|
||||
"2" @ 134..135: Number
|
||||
"generator" @ 139..148: Variable [definition]
|
||||
"x" @ 152..153: Variable
|
||||
"x" @ 158..159: Variable [definition]
|
||||
"range" @ 163..168: Class
|
||||
"10" @ 169..171: Number
|
||||
"#);
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ty/issues/1406>
|
||||
#[test]
|
||||
fn test_invalid_kwargs() {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! and overloads.
|
||||
|
||||
use crate::docstring::Docstring;
|
||||
use crate::goto::Definitions;
|
||||
use crate::goto::DefinitionsOrTargets;
|
||||
use crate::{Db, find_node::covering_node};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
@@ -74,7 +74,7 @@ pub fn signature_help(db: &dyn Db, file: File, offset: TextSize) -> Option<Signa
|
||||
|
||||
// Get signature details from the semantic analyzer.
|
||||
let signature_details: Vec<CallSignatureDetails<'_>> =
|
||||
call_signature_details(&model, call_expr);
|
||||
call_signature_details(db, &model, call_expr);
|
||||
|
||||
if signature_details.is_empty() {
|
||||
return None;
|
||||
@@ -214,7 +214,8 @@ fn get_callable_documentation(
|
||||
db: &dyn crate::Db,
|
||||
definition: Option<Definition>,
|
||||
) -> Option<Docstring> {
|
||||
Definitions(vec![ResolvedDefinition::Definition(definition?)]).docstring(db)
|
||||
DefinitionsOrTargets::Definitions(vec![ResolvedDefinition::Definition(definition?)])
|
||||
.docstring(db)
|
||||
}
|
||||
|
||||
/// Create `ParameterDetails` objects from parameter label offsets.
|
||||
|
||||
@@ -11,12 +11,12 @@ use crate::cached_vendored_root;
|
||||
/// other language server providers (like hover, completion, and signature help) to find
|
||||
/// docstrings for functions that resolve to stubs.
|
||||
pub(crate) struct StubMapper<'db> {
|
||||
db: &'db dyn ty_python_semantic::Db,
|
||||
db: &'db dyn crate::Db,
|
||||
cached_vendored_root: Option<SystemPathBuf>,
|
||||
}
|
||||
|
||||
impl<'db> StubMapper<'db> {
|
||||
pub(crate) fn new(db: &'db dyn ty_python_semantic::Db) -> Self {
|
||||
pub(crate) fn new(db: &'db dyn crate::Db) -> Self {
|
||||
let cached_vendored_root = cached_vendored_root(db);
|
||||
Self {
|
||||
db,
|
||||
|
||||
@@ -23,12 +23,11 @@ use crate::completion::CompletionKind;
|
||||
pub struct QueryPattern {
|
||||
re: Option<Regex>,
|
||||
original: String,
|
||||
original_is_exact: bool,
|
||||
}
|
||||
|
||||
impl QueryPattern {
|
||||
/// Create a new query pattern from a literal search string given.
|
||||
pub fn fuzzy(literal_query_string: &str) -> QueryPattern {
|
||||
pub fn new(literal_query_string: &str) -> QueryPattern {
|
||||
let mut pattern = "(?i)".to_string();
|
||||
for ch in literal_query_string.chars() {
|
||||
pattern.push_str(®ex::escape(ch.encode_utf8(&mut [0; 4])));
|
||||
@@ -42,16 +41,6 @@ impl QueryPattern {
|
||||
QueryPattern {
|
||||
re: Regex::new(&pattern).ok(),
|
||||
original: literal_query_string.to_string(),
|
||||
original_is_exact: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new query
|
||||
pub fn exactly(symbol: &str) -> QueryPattern {
|
||||
QueryPattern {
|
||||
re: None,
|
||||
original: symbol.to_string(),
|
||||
original_is_exact: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +49,6 @@ impl QueryPattern {
|
||||
QueryPattern {
|
||||
re: None,
|
||||
original: String::new(),
|
||||
original_is_exact: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +59,6 @@ impl QueryPattern {
|
||||
pub fn is_match_symbol_name(&self, symbol_name: &str) -> bool {
|
||||
if let Some(ref re) = self.re {
|
||||
re.is_match(symbol_name)
|
||||
} else if self.original_is_exact {
|
||||
symbol_name == self.original
|
||||
} else {
|
||||
// This is a degenerate case. The only way
|
||||
// we should get here is if the query string
|
||||
@@ -89,13 +75,13 @@ impl QueryPattern {
|
||||
/// incorrectly. That is, it's possible that this query will match all
|
||||
/// inputs but this still returns `false`.
|
||||
pub fn will_match_everything(&self) -> bool {
|
||||
self.re.is_none() && self.original.is_empty()
|
||||
self.re.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for QueryPattern {
|
||||
fn from(literal_query_string: &str) -> QueryPattern {
|
||||
QueryPattern::fuzzy(literal_query_string)
|
||||
QueryPattern::new(literal_query_string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,7 +565,7 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn matches(query: &str, symbol: &str) -> bool {
|
||||
super::QueryPattern::fuzzy(query).is_match_symbol_name(symbol)
|
||||
super::QueryPattern::new(query).is_match_symbol_name(symbol)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn workspace_symbols(db: &dyn Db, query: &str) -> Vec<WorkspaceSymbolInfo> {
|
||||
|
||||
let project = db.project();
|
||||
|
||||
let query = QueryPattern::fuzzy(query);
|
||||
let query = QueryPattern::new(query);
|
||||
let files = project.files(db);
|
||||
let results = std::sync::Mutex::new(Vec::new());
|
||||
{
|
||||
|
||||
@@ -13,7 +13,6 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true }
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_index = { workspace = true, features = ["salsa"] }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_memory_usage = { workspace = true }
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
try:
|
||||
type name_4 = name_1
|
||||
finally:
|
||||
from .. import name_3
|
||||
|
||||
try:
|
||||
pass
|
||||
except* 0:
|
||||
pass
|
||||
else:
|
||||
def name_1() -> name_4:
|
||||
pass
|
||||
|
||||
@name_1
|
||||
def name_3():
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
pass
|
||||
except* 0:
|
||||
assert name_3
|
||||
finally:
|
||||
|
||||
@name_3
|
||||
class name_1:
|
||||
pass
|
||||
@@ -1,5 +0,0 @@
|
||||
class foo[_: foo](object): ...
|
||||
|
||||
[_] = (foo,) = foo
|
||||
|
||||
def foo(): ...
|
||||
@@ -1,20 +0,0 @@
|
||||
name_3: Foo = 0
|
||||
name_4 = 0
|
||||
|
||||
if _0:
|
||||
type name_3 = name_5
|
||||
type name_4 = name_3
|
||||
|
||||
_1: name_3
|
||||
|
||||
def name_1(_2: name_4):
|
||||
pass
|
||||
|
||||
match 0:
|
||||
case name_1._3:
|
||||
pass
|
||||
case 1:
|
||||
type name_5 = name_4
|
||||
case name_5:
|
||||
pass
|
||||
name_3 = name_5
|
||||
@@ -35,8 +35,6 @@ bad_nesting: Literal[LiteralString] # error: [invalid-type-form]
|
||||
|
||||
`LiteralString` cannot be parameterized.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
@@ -44,6 +42,7 @@ from typing_extensions import LiteralString
|
||||
a: LiteralString[str]
|
||||
|
||||
# error: [invalid-type-form]
|
||||
# error: [unresolved-reference] "Name `foo` used when not defined"
|
||||
b: LiteralString["foo"]
|
||||
```
|
||||
|
||||
|
||||
@@ -260,13 +260,15 @@ class Shape:
|
||||
|
||||
@classmethod
|
||||
def bar(cls: type[Self]) -> Self:
|
||||
reveal_type(cls) # revealed: type[Self@bar]
|
||||
# TODO: type[Shape]
|
||||
reveal_type(cls) # revealed: @Todo(unsupported type[X] special form)
|
||||
return cls()
|
||||
|
||||
class Circle(Shape): ...
|
||||
|
||||
reveal_type(Shape().foo()) # revealed: Shape
|
||||
reveal_type(Shape.bar()) # revealed: Shape
|
||||
# TODO: Shape
|
||||
reveal_type(Shape.bar()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
@@ -222,10 +222,10 @@ reveal_type(r) # revealed: dict[int | str, int | str]
|
||||
## Incorrect collection literal assignments are complained about
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Object of type `list[str | int]` is not assignable to `list[str]`"
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[str]`"
|
||||
a: list[str] = [1, 2, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `set[int | str]` is not assignable to `set[int]`"
|
||||
# error: [invalid-assignment] "Object of type `set[Unknown | int | str]` is not assignable to `set[int]`"
|
||||
b: set[int] = {1, 2, "3"}
|
||||
```
|
||||
|
||||
@@ -422,7 +422,7 @@ reveal_type(d) # revealed: list[int | tuple[int, int]]
|
||||
e: list[int] = f(True)
|
||||
reveal_type(e) # revealed: list[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `list[int | str]` is not assignable to `list[int]`"
|
||||
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `list[int]`"
|
||||
g: list[int] = f("a")
|
||||
|
||||
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `tuple[int]`"
|
||||
@@ -459,12 +459,12 @@ reveal_type(b) # revealed: TD
|
||||
|
||||
# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
|
||||
# error: [invalid-key] "Unknown key "y" for TypedDict `TD`"
|
||||
# error: [invalid-assignment] "Object of type `TD | dict[Unknown | str, Unknown | int]` is not assignable to `TD`"
|
||||
# error: [invalid-assignment] "Object of type `Unknown | dict[Unknown | str, Unknown | int]` is not assignable to `TD`"
|
||||
c: TD = f([{"y": 0}, {"x": 1}])
|
||||
|
||||
# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
|
||||
# error: [invalid-key] "Unknown key "y" for TypedDict `TD`"
|
||||
# error: [invalid-assignment] "Object of type `TD | None | dict[Unknown | str, Unknown | int]` is not assignable to `TD | None`"
|
||||
# error: [invalid-assignment] "Object of type `Unknown | dict[Unknown | str, Unknown | int]` is not assignable to `TD | None`"
|
||||
c: TD | None = f([{"y": 0}, {"x": 1}])
|
||||
```
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ async def main():
|
||||
|
||||
result = await task
|
||||
|
||||
reveal_type(result) # revealed: int
|
||||
# TODO: this should be `int`
|
||||
reveal_type(result) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `asyncio.gather`
|
||||
|
||||
@@ -2383,34 +2383,6 @@ class B:
|
||||
|
||||
reveal_type(B().x) # revealed: Unknown | Literal[1]
|
||||
reveal_type(A().x) # revealed: Unknown | Literal[1]
|
||||
|
||||
class Base:
|
||||
def flip(self) -> "Sub":
|
||||
return Sub()
|
||||
|
||||
class Sub(Base):
|
||||
# error: [invalid-method-override]
|
||||
def flip(self) -> "Base":
|
||||
return Base()
|
||||
|
||||
class C2:
|
||||
def __init__(self, x: Sub):
|
||||
self.x = x
|
||||
|
||||
def replace_with(self, other: "C2"):
|
||||
self.x = other.x.flip()
|
||||
|
||||
reveal_type(C2(Sub()).x) # revealed: Unknown | Base
|
||||
|
||||
class C3:
|
||||
def __init__(self, x: Sub):
|
||||
self.x = [x]
|
||||
|
||||
def replace_with(self, other: "C3"):
|
||||
self.x = [self.x[0].flip()]
|
||||
|
||||
# TODO: should be `Unknown | list[Unknown | Sub] | list[Unknown | Base]`
|
||||
reveal_type(C3(Sub()).x) # revealed: Unknown | list[Unknown | Sub] | list[Divergent]
|
||||
```
|
||||
|
||||
And cycles between many attributes:
|
||||
@@ -2460,30 +2432,6 @@ class ManyCycles:
|
||||
reveal_type(self.x5) # revealed: Unknown | int
|
||||
reveal_type(self.x6) # revealed: Unknown | int
|
||||
reveal_type(self.x7) # revealed: Unknown | int
|
||||
|
||||
class ManyCycles2:
|
||||
def __init__(self: "ManyCycles2"):
|
||||
self.x1 = [0]
|
||||
self.x2 = [1]
|
||||
self.x3 = [1]
|
||||
|
||||
def f1(self: "ManyCycles2"):
|
||||
# TODO: should be Unknown | list[Unknown | int] | list[Divergent]
|
||||
reveal_type(self.x3) # revealed: Unknown | list[Unknown | int] | list[Divergent] | list[Divergent]
|
||||
|
||||
self.x1 = [self.x2] + [self.x3]
|
||||
self.x2 = [self.x1] + [self.x3]
|
||||
self.x3 = [self.x1] + [self.x2]
|
||||
|
||||
def f2(self: "ManyCycles2"):
|
||||
self.x1 = self.x2 + self.x3
|
||||
self.x2 = self.x1 + self.x3
|
||||
self.x3 = self.x1 + self.x2
|
||||
|
||||
def f3(self: "ManyCycles2"):
|
||||
self.x1 = self.x2 + self.x3
|
||||
self.x2 = self.x1 + self.x3
|
||||
self.x3 = self.x1 + self.x2
|
||||
```
|
||||
|
||||
This case additionally tests our union/intersection simplification logic:
|
||||
@@ -2650,7 +2598,7 @@ reveal_type(C().x) # revealed: int
|
||||
```py
|
||||
import enum
|
||||
|
||||
reveal_type(enum.Enum.__members__) # revealed: MappingProxyType[str, Enum]
|
||||
reveal_type(enum.Enum.__members__) # revealed: MappingProxyType[str, Unknown]
|
||||
|
||||
class Answer(enum.Enum):
|
||||
NO = 0
|
||||
@@ -2658,23 +2606,17 @@ class Answer(enum.Enum):
|
||||
|
||||
reveal_type(Answer.NO) # revealed: Literal[Answer.NO]
|
||||
reveal_type(Answer.NO.value) # revealed: Literal[0]
|
||||
reveal_type(Answer.__members__) # revealed: MappingProxyType[str, Answer]
|
||||
reveal_type(Answer.__members__) # revealed: MappingProxyType[str, Unknown]
|
||||
```
|
||||
|
||||
## Divergent inferred implicit instance attribute types
|
||||
|
||||
If an implicit attribute is defined recursively and type inference diverges, the divergent part is
|
||||
filled in with the dynamic type `Divergent`. Types containing `Divergent` can be seen as "cheap"
|
||||
recursive types: they are not true recursive types based on recursive type theory, so no unfolding
|
||||
is performed when you use them.
|
||||
|
||||
```py
|
||||
class C:
|
||||
def f(self, other: "C"):
|
||||
self.x = (other.x, 1)
|
||||
|
||||
reveal_type(C().x) # revealed: Unknown | tuple[Divergent, Literal[1]]
|
||||
reveal_type(C().x[0]) # revealed: Unknown | Divergent
|
||||
```
|
||||
|
||||
This also works if the tuple is not constructed directly:
|
||||
@@ -2713,11 +2655,11 @@ And it also works for homogeneous tuples:
|
||||
def make_homogeneous_tuple(x: T) -> tuple[T, ...]:
|
||||
return (x, x)
|
||||
|
||||
class F:
|
||||
def f(self, other: "F"):
|
||||
class E:
|
||||
def f(self, other: "E"):
|
||||
self.x = make_homogeneous_tuple(other.x)
|
||||
|
||||
reveal_type(F().x) # revealed: Unknown | tuple[Divergent, ...]
|
||||
reveal_type(E().x) # revealed: Unknown | tuple[Divergent, ...]
|
||||
```
|
||||
|
||||
## Attributes of standard library modules that aren't yet defined
|
||||
|
||||
@@ -268,8 +268,8 @@ class A:
|
||||
|
||||
A(f(1))
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `list[int | str]`, found `list[int | None | list[Unknown]] & list[int | str | list[Unknown]] & list[list[Unknown]]`"
|
||||
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `list[int | None]`, found `list[int | None | list[Unknown]] & list[int | str | list[Unknown]] & list[list[Unknown]]`"
|
||||
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `list[int | str]`, found `list[list[Unknown]]`"
|
||||
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `list[int | None]`, found `list[list[Unknown]]`"
|
||||
A(f([]))
|
||||
```
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ type("Foo", Base, {})
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
type("Foo", (1, 2), {})
|
||||
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `dict[str, Any]`, found `dict[str | bytes, Any]`"
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `dict[str, Any]`, found `dict[Unknown | bytes, Unknown | int]`"
|
||||
type("Foo", (Base,), {b"attr": 1})
|
||||
```
|
||||
|
||||
|
||||
@@ -277,6 +277,6 @@ def _(flag: bool):
|
||||
x = f({"x": 1})
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `T`, found `dict[str, int] & dict[Unknown | str, Unknown | int]`"
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `T`, found `dict[Unknown | str, Unknown | int]`"
|
||||
f({"y": 1})
|
||||
```
|
||||
|
||||
@@ -210,7 +210,9 @@ class BuilderMeta2(type):
|
||||
) -> BuilderMeta2:
|
||||
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
||||
s = reveal_type(super())
|
||||
return reveal_type(s.__new__(cls, name, bases, dct)) # revealed: BuilderMeta2
|
||||
# TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
||||
# revealed: Unknown
|
||||
return reveal_type(s.__new__(cls, name, bases, dct))
|
||||
|
||||
class Foo[T]:
|
||||
x: T
|
||||
@@ -393,14 +395,6 @@ class E(Enum):
|
||||
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
|
||||
```
|
||||
|
||||
## `type[Self]`
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def method(self):
|
||||
super(self.__class__, self)
|
||||
```
|
||||
|
||||
## Descriptor Behavior with Super
|
||||
|
||||
Accessing attributes through `super` still invokes descriptor protocol. However, the behavior can
|
||||
|
||||
@@ -32,39 +32,6 @@ reveal_type(p.x) # revealed: Unknown | int
|
||||
reveal_type(p.y) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
## Self-referential bare type alias
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12" # typing.TypeAliasType
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Union, TypeAliasType, Sequence, Mapping
|
||||
|
||||
A = list["A" | None]
|
||||
|
||||
def f(x: A):
|
||||
# TODO: should be `list[A | None]`?
|
||||
reveal_type(x) # revealed: list[Divergent]
|
||||
# TODO: should be `A | None`?
|
||||
reveal_type(x[0]) # revealed: Divergent
|
||||
|
||||
JSONPrimitive = Union[str, int, float, bool, None]
|
||||
JSONValue = TypeAliasType("JSONValue", 'Union[JSONPrimitive, Sequence["JSONValue"], Mapping[str, "JSONValue"]]')
|
||||
```
|
||||
|
||||
## Self-referential legacy type variables
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
B = TypeVar("B", bound="Base")
|
||||
|
||||
class Base(Generic[B]):
|
||||
pass
|
||||
```
|
||||
|
||||
## Parameter default values
|
||||
|
||||
This is a regression test for <https://github.com/astral-sh/ty/issues/1402>. When a parameter has a
|
||||
|
||||
@@ -15,8 +15,10 @@ reveal_type(Color.RED) # revealed: Literal[Color.RED]
|
||||
reveal_type(Color.RED.name) # revealed: Literal["RED"]
|
||||
reveal_type(Color.RED.value) # revealed: Literal[1]
|
||||
|
||||
# TODO: Should be `Color` or `Literal[Color.RED]`
|
||||
reveal_type(Color["RED"]) # revealed: Unknown
|
||||
|
||||
# TODO: Could be `Literal[Color.RED]` to be more precise
|
||||
reveal_type(Color["RED"]) # revealed: Color
|
||||
reveal_type(Color(1)) # revealed: Color
|
||||
|
||||
reveal_type(Color.RED in Color) # revealed: bool
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tests for the `@typing(_extensions).final` decorator
|
||||
|
||||
## Cannot subclass a class decorated with `@final`
|
||||
## Cannot subclass
|
||||
|
||||
Don't do this:
|
||||
|
||||
@@ -29,456 +29,3 @@ class H(
|
||||
G,
|
||||
): ...
|
||||
```
|
||||
|
||||
## Cannot override a method decorated with `@final`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```pyi
|
||||
from typing_extensions import final, Callable, TypeVar
|
||||
|
||||
def lossy_decorator(fn: Callable) -> Callable: ...
|
||||
|
||||
class Parent:
|
||||
@final
|
||||
def foo(self): ...
|
||||
|
||||
@final
|
||||
@property
|
||||
def my_property1(self) -> int: ...
|
||||
|
||||
@property
|
||||
@final
|
||||
def my_property2(self) -> int: ...
|
||||
|
||||
@final
|
||||
@classmethod
|
||||
def class_method1(cls) -> int: ...
|
||||
|
||||
@classmethod
|
||||
@final
|
||||
def class_method2(cls) -> int: ...
|
||||
|
||||
@final
|
||||
@staticmethod
|
||||
def static_method1() -> int: ...
|
||||
|
||||
@staticmethod
|
||||
@final
|
||||
def static_method2() -> int: ...
|
||||
|
||||
@lossy_decorator
|
||||
@final
|
||||
def decorated_1(self): ...
|
||||
|
||||
@final
|
||||
@lossy_decorator
|
||||
def decorated_2(self): ...
|
||||
|
||||
class Child(Parent):
|
||||
# explicitly test the concise diagnostic message,
|
||||
# which is different to the verbose diagnostic summary message:
|
||||
#
|
||||
# error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
|
||||
def foo(self): ...
|
||||
@property
|
||||
def my_property1(self) -> int: ... # error: [override-of-final-method]
|
||||
|
||||
@property
|
||||
def my_property2(self) -> int: ... # error: [override-of-final-method]
|
||||
|
||||
@classmethod
|
||||
def class_method1(cls) -> int: ... # error: [override-of-final-method]
|
||||
|
||||
@staticmethod
|
||||
def static_method1() -> int: ... # error: [override-of-final-method]
|
||||
|
||||
@classmethod
|
||||
def class_method2(cls) -> int: ... # error: [override-of-final-method]
|
||||
|
||||
@staticmethod
|
||||
def static_method2() -> int: ... # error: [override-of-final-method]
|
||||
|
||||
def decorated_1(self): ... # TODO: should emit [override-of-final-method]
|
||||
|
||||
@lossy_decorator
|
||||
def decorated_2(self): ... # TODO: should emit [override-of-final-method]
|
||||
|
||||
class OtherChild(Parent): ...
|
||||
|
||||
class Grandchild(OtherChild):
|
||||
@staticmethod
|
||||
# TODO: we should emit a Liskov violation here too
|
||||
# error: [override-of-final-method]
|
||||
def foo(): ...
|
||||
@property
|
||||
# TODO: we should emit a Liskov violation here too
|
||||
# error: [override-of-final-method]
|
||||
def my_property1(self) -> str: ...
|
||||
# TODO: we should emit a Liskov violation here too
|
||||
# error: [override-of-final-method]
|
||||
class_method1 = None
|
||||
|
||||
# Diagnostic edge case: `final` is very far away from the method definition in the source code:
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def identity(x: T) -> T: ...
|
||||
|
||||
class Foo:
|
||||
@final
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
def bar(self): ...
|
||||
|
||||
class Baz(Foo):
|
||||
def bar(self): ... # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
## Diagnostic edge case: superclass with `@final` method has the same name as the subclass
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`module1.py`:
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
class Foo:
|
||||
@final
|
||||
def f(self): ...
|
||||
```
|
||||
|
||||
`module2.py`:
|
||||
|
||||
```py
|
||||
import module1
|
||||
|
||||
class Foo(module1.Foo):
|
||||
def f(self): ... # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
## Overloaded methods decorated with `@final`
|
||||
|
||||
In a stub file, `@final` should be applied to the first overload. In a runtime file, `@final` should
|
||||
only be applied to the implementation function.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`stub.pyi`:
|
||||
|
||||
```pyi
|
||||
from typing import final, overload
|
||||
|
||||
class Good:
|
||||
@overload
|
||||
@final
|
||||
def bar(self, x: str) -> str: ...
|
||||
@overload
|
||||
def bar(self, x: int) -> int: ...
|
||||
|
||||
@final
|
||||
@overload
|
||||
def baz(self, x: str) -> str: ...
|
||||
@overload
|
||||
def baz(self, x: int) -> int: ...
|
||||
|
||||
class ChildOfGood(Good):
|
||||
@overload
|
||||
def bar(self, x: str) -> str: ...
|
||||
@overload
|
||||
def bar(self, x: int) -> int: ... # error: [override-of-final-method]
|
||||
|
||||
@overload
|
||||
def baz(self, x: str) -> str: ...
|
||||
@overload
|
||||
def baz(self, x: int) -> int: ... # error: [override-of-final-method]
|
||||
|
||||
class Bad:
|
||||
@overload
|
||||
def bar(self, x: str) -> str: ...
|
||||
@overload
|
||||
@final
|
||||
# error: [invalid-overload]
|
||||
def bar(self, x: int) -> int: ...
|
||||
|
||||
@overload
|
||||
def baz(self, x: str) -> str: ...
|
||||
@final
|
||||
@overload
|
||||
# error: [invalid-overload]
|
||||
def baz(self, x: int) -> int: ...
|
||||
|
||||
class ChildOfBad(Bad):
|
||||
@overload
|
||||
def bar(self, x: str) -> str: ...
|
||||
@overload
|
||||
def bar(self, x: int) -> int: ... # error: [override-of-final-method]
|
||||
|
||||
@overload
|
||||
def baz(self, x: str) -> str: ...
|
||||
@overload
|
||||
def baz(self, x: int) -> int: ... # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from typing import overload, final
|
||||
|
||||
class Good:
|
||||
@overload
|
||||
def f(self, x: str) -> str: ...
|
||||
@overload
|
||||
def f(self, x: int) -> int: ...
|
||||
@final
|
||||
def f(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class ChildOfGood(Good):
|
||||
@overload
|
||||
def f(self, x: str) -> str: ...
|
||||
@overload
|
||||
def f(self, x: int) -> int: ...
|
||||
# error: [override-of-final-method]
|
||||
def f(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Bad:
|
||||
@overload
|
||||
@final
|
||||
def f(self, x: str) -> str: ...
|
||||
@overload
|
||||
def f(self, x: int) -> int: ...
|
||||
# error: [invalid-overload]
|
||||
def f(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
@final
|
||||
@overload
|
||||
def g(self, x: str) -> str: ...
|
||||
@overload
|
||||
def g(self, x: int) -> int: ...
|
||||
# error: [invalid-overload]
|
||||
def g(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
@overload
|
||||
def h(self, x: str) -> str: ...
|
||||
@overload
|
||||
@final
|
||||
def h(self, x: int) -> int: ...
|
||||
# error: [invalid-overload]
|
||||
def h(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
@overload
|
||||
def i(self, x: str) -> str: ...
|
||||
@final
|
||||
@overload
|
||||
def i(self, x: int) -> int: ...
|
||||
# error: [invalid-overload]
|
||||
def i(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class ChildOfBad(Bad):
|
||||
# TODO: these should all cause us to emit Liskov violations as well
|
||||
f = None # error: [override-of-final-method]
|
||||
g = None # error: [override-of-final-method]
|
||||
h = None # error: [override-of-final-method]
|
||||
i = None # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
## Edge case: the function is decorated with `@final` but originally defined elsewhere
|
||||
|
||||
As of 2025-11-26, pyrefly emits a diagnostic on this, but mypy and pyright do not. For mypy and
|
||||
pyright to emit a diagnostic, the superclass definition decorated with `@final` must be a literal
|
||||
function definition: an assignment definition where the right-hand side of the assignment is a
|
||||
`@final-decorated` function is not sufficient for them to consider the superclass definition as
|
||||
being `@final`.
|
||||
|
||||
For now, we choose to follow mypy's and pyright's behaviour here, in order to maximise compatibility
|
||||
with other type checkers. We may decide to change this in the future, however, as it would simplify
|
||||
our implementation. Mypy's and pyright's behaviour here is also arguably inconsistent with their
|
||||
treatment of other type qualifiers such as `Final`. As discussed in
|
||||
<https://discuss.python.org/t/imported-final-variable/82429>, both type checkers view the `Final`
|
||||
type qualifier as travelling *across* scopes.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
class A:
|
||||
@final
|
||||
def method(self) -> None: ...
|
||||
|
||||
class B:
|
||||
method = A.method
|
||||
|
||||
class C(B):
|
||||
def method(self) -> None: ... # no diagnostic here (see prose discussion above)
|
||||
```
|
||||
|
||||
## Constructor methods are also checked
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
class A:
|
||||
@final
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
class B(A):
|
||||
def __init__(self) -> None: ... # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
## Only the first `@final` violation is reported
|
||||
|
||||
(Don't do this.)
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
class A:
|
||||
@final
|
||||
def f(self): ...
|
||||
|
||||
class B(A):
|
||||
@final
|
||||
def f(self): ... # error: [override-of-final-method]
|
||||
|
||||
class C(B):
|
||||
@final
|
||||
# we only emit one error here, not two
|
||||
def f(self): ... # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
## For when you just really want to drive the point home
|
||||
|
||||
```py
|
||||
from typing import final, Final
|
||||
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
class A:
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
def method(self): ...
|
||||
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
@final
|
||||
class B:
|
||||
method: Final = A.method
|
||||
|
||||
class C(A): # error: [subclass-of-final-class]
|
||||
def method(self): ... # error: [override-of-final-method]
|
||||
|
||||
class D(B): # error: [subclass-of-final-class]
|
||||
# TODO: we should emit a diagnostic here
|
||||
def method(self): ...
|
||||
```
|
||||
|
||||
## An `@final` method is overridden by an implicit instance attribute
|
||||
|
||||
```py
|
||||
from typing import final, Any
|
||||
|
||||
class Parent:
|
||||
@final
|
||||
def method(self) -> None: ...
|
||||
|
||||
class Child(Parent):
|
||||
def __init__(self) -> None:
|
||||
self.method: Any = 42 # TODO: we should emit `[override-of-final-method]` here
|
||||
```
|
||||
|
||||
## A possibly-undefined `@final` method is overridden
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
def coinflip() -> bool:
|
||||
return False
|
||||
|
||||
class A:
|
||||
if coinflip():
|
||||
@final
|
||||
def method1(self) -> None: ...
|
||||
else:
|
||||
def method1(self) -> None: ...
|
||||
|
||||
if coinflip():
|
||||
def method2(self) -> None: ...
|
||||
else:
|
||||
@final
|
||||
def method2(self) -> None: ...
|
||||
|
||||
if coinflip():
|
||||
@final
|
||||
def method3(self) -> None: ...
|
||||
else:
|
||||
@final
|
||||
def method3(self) -> None: ...
|
||||
|
||||
if coinflip():
|
||||
def method4(self) -> None: ...
|
||||
elif coinflip():
|
||||
@final
|
||||
def method4(self) -> None: ...
|
||||
else:
|
||||
def method4(self) -> None: ...
|
||||
|
||||
class B(A):
|
||||
def method1(self) -> None: ... # error: [override-of-final-method]
|
||||
def method2(self) -> None: ... # error: [override-of-final-method]
|
||||
def method3(self) -> None: ... # error: [override-of-final-method]
|
||||
def method4(self) -> None: ... # error: [override-of-final-method]
|
||||
|
||||
# Possible overrides of possibly `@final` methods...
|
||||
class C(A):
|
||||
if coinflip():
|
||||
# TODO: the autofix here introduces invalid syntax because there are now no
|
||||
# statements inside the `if:` branch
|
||||
# (but it might still be a useful autofix in an IDE context?)
|
||||
def method1(self) -> None: ... # error: [override-of-final-method]
|
||||
else:
|
||||
pass
|
||||
|
||||
if coinflip():
|
||||
def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
|
||||
else:
|
||||
def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
|
||||
|
||||
if coinflip():
|
||||
def method3(self) -> None: ... # error: [override-of-final-method]
|
||||
def method4(self) -> None: ... # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
@@ -145,8 +145,8 @@ reveal_type(C[Literal[5]]()) # revealed: C[Literal[5]]
|
||||
The specialization must match the generic types:
|
||||
|
||||
```py
|
||||
# error: [invalid-type-arguments] "Too many type arguments to class `C`: expected 1, got 2"
|
||||
reveal_type(C[int, int]()) # revealed: C[Unknown]
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to class `C`: expected 1, got 2"
|
||||
reveal_type(C[int, int]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
||||
@@ -164,11 +164,13 @@ class IntSubclass(int): ...
|
||||
reveal_type(Bounded[int]()) # revealed: Bounded[int]
|
||||
reveal_type(Bounded[IntSubclass]()) # revealed: Bounded[IntSubclass]
|
||||
|
||||
# error: [invalid-type-arguments] "Type `str` is not assignable to upper bound `int` of type variable `BoundedT@Bounded`"
|
||||
reveal_type(Bounded[str]()) # revealed: Bounded[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `str`"
|
||||
reveal_type(Bounded[str]()) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-arguments] "Type `int | str` is not assignable to upper bound `int` of type variable `BoundedT@Bounded`"
|
||||
reveal_type(Bounded[int | str]()) # revealed: Bounded[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `int | str`"
|
||||
reveal_type(Bounded[int | str]()) # revealed: Unknown
|
||||
|
||||
reveal_type(BoundedByUnion[int]()) # revealed: BoundedByUnion[int]
|
||||
reveal_type(BoundedByUnion[IntSubclass]()) # revealed: BoundedByUnion[IntSubclass]
|
||||
@@ -195,8 +197,9 @@ reveal_type(Constrained[str]()) # revealed: Constrained[str]
|
||||
# TODO: revealed: Unknown
|
||||
reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str]
|
||||
|
||||
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `ConstrainedT@Constrained`"
|
||||
reveal_type(Constrained[object]()) # revealed: Constrained[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to class `Constrained` is incorrect: Expected `int | str`, found `object`"
|
||||
reveal_type(Constrained[object]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has a default, it can be omitted:
|
||||
@@ -270,7 +273,7 @@ class C(Generic[T]):
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -286,7 +289,7 @@ class C(Generic[T]):
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -305,7 +308,7 @@ class C(Generic[T]):
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -324,7 +327,7 @@ class C(Generic[T]):
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
|
||||
class D(Generic[T]):
|
||||
@@ -335,7 +338,7 @@ class D(Generic[T]):
|
||||
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `D[int]`"
|
||||
# error: [invalid-assignment] "Object of type `D[str]` is not assignable to `D[int]`"
|
||||
wrong_innards: D[int] = D("five")
|
||||
```
|
||||
|
||||
@@ -451,7 +454,7 @@ reveal_type(C(1, 1)) # revealed: C[int]
|
||||
reveal_type(C(1, "string")) # revealed: C[int]
|
||||
reveal_type(C(1, True)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five", 1)
|
||||
```
|
||||
|
||||
@@ -729,14 +732,6 @@ class Base(Generic[T]): ...
|
||||
class Sub(Base["Sub"]): ...
|
||||
|
||||
reveal_type(Sub) # revealed: <class 'Sub'>
|
||||
|
||||
U = TypeVar("U")
|
||||
|
||||
class Base2(Generic[T, U]): ...
|
||||
|
||||
# TODO: no error
|
||||
# error: [unsupported-base] "Unsupported class base with type `<class 'Base2[Sub2, U@Sub2]'> | <class 'Base2[Sub2[Unknown], U@Sub2]'>`"
|
||||
class Sub2(Base2["Sub2", U]): ...
|
||||
```
|
||||
|
||||
#### Without string forward references
|
||||
@@ -761,8 +756,6 @@ from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# TODO: no error "Unsupported class base with type `<class 'list[Derived[T@Derived]]'> | <class 'list[@Todo]'>`"
|
||||
# error: [unsupported-base]
|
||||
class Derived(list[Derived[T]], Generic[T]): ...
|
||||
```
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||
def takes_in_type(x: type[T]) -> type[T]:
|
||||
return x
|
||||
|
||||
reveal_type(takes_in_type(int)) # revealed: type[int]
|
||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
||||
```
|
||||
|
||||
This also works when passing in arguments that are subclasses of the parameter type.
|
||||
|
||||
@@ -383,7 +383,8 @@ def constrained(f: T):
|
||||
|
||||
## Meta-type
|
||||
|
||||
The meta-type of a typevar is `type[T]`.
|
||||
The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the
|
||||
meta-types of the constraints:
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
@@ -391,22 +392,22 @@ from typing import TypeVar
|
||||
T_normal = TypeVar("T_normal")
|
||||
|
||||
def normal(x: T_normal):
|
||||
reveal_type(type(x)) # revealed: type[T_normal@normal]
|
||||
reveal_type(type(x)) # revealed: type
|
||||
|
||||
T_bound_object = TypeVar("T_bound_object", bound=object)
|
||||
|
||||
def bound_object(x: T_bound_object):
|
||||
reveal_type(type(x)) # revealed: type[T_bound_object@bound_object]
|
||||
reveal_type(type(x)) # revealed: type
|
||||
|
||||
T_bound_int = TypeVar("T_bound_int", bound=int)
|
||||
|
||||
def bound_int(x: T_bound_int):
|
||||
reveal_type(type(x)) # revealed: type[T_bound_int@bound_int]
|
||||
reveal_type(type(x)) # revealed: type[int]
|
||||
|
||||
T_constrained = TypeVar("T_constrained", int, str)
|
||||
|
||||
def constrained(x: T_constrained):
|
||||
reveal_type(type(x)) # revealed: type[T_constrained@constrained]
|
||||
reveal_type(type(x)) # revealed: type[int] | type[str]
|
||||
```
|
||||
|
||||
## Cycles
|
||||
@@ -443,22 +444,6 @@ class G(Generic[T]):
|
||||
reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
|
||||
```
|
||||
|
||||
An invalid specialization in a recursive bound doesn't cause a panic:
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
# error: [invalid-type-arguments]
|
||||
T = TypeVar("T", bound="Node[int]")
|
||||
|
||||
class Node(Generic[T]):
|
||||
pass
|
||||
|
||||
# error: [invalid-type-arguments]
|
||||
def _(n: Node[str]):
|
||||
reveal_type(n) # revealed: Node[Unknown]
|
||||
```
|
||||
|
||||
### Defaults
|
||||
|
||||
```toml
|
||||
|
||||
@@ -61,8 +61,8 @@ def _(a: C[int], b: C[Literal[5]]):
|
||||
The specialization must match the generic types:
|
||||
|
||||
```py
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 1, got 2"
|
||||
reveal_type(C[int, int]) # revealed: C[Unknown]
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
|
||||
reveal_type(C[int, int]) # revealed: Unknown
|
||||
```
|
||||
|
||||
And non-generic types cannot be specialized:
|
||||
@@ -88,11 +88,13 @@ class IntSubclass(int): ...
|
||||
reveal_type(Bounded[int]) # revealed: Bounded[int]
|
||||
reveal_type(Bounded[IntSubclass]) # revealed: Bounded[IntSubclass]
|
||||
|
||||
# error: [invalid-type-arguments] "Type `str` is not assignable to upper bound `int` of type variable `T@Bounded`"
|
||||
reveal_type(Bounded[str]) # revealed: Bounded[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `str`"
|
||||
reveal_type(Bounded[str]) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-arguments] "Type `int | str` is not assignable to upper bound `int` of type variable `T@Bounded`"
|
||||
reveal_type(Bounded[int | str]) # revealed: Bounded[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `int | str`"
|
||||
reveal_type(Bounded[int | str]) # revealed: Unknown
|
||||
|
||||
reveal_type(BoundedByUnion[int]) # revealed: BoundedByUnion[int]
|
||||
reveal_type(BoundedByUnion[IntSubclass]) # revealed: BoundedByUnion[IntSubclass]
|
||||
@@ -117,8 +119,9 @@ reveal_type(Constrained[str]) # revealed: Constrained[str]
|
||||
# TODO: revealed: Unknown
|
||||
reveal_type(Constrained[int | str]) # revealed: Constrained[int | str]
|
||||
|
||||
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `T@Constrained`"
|
||||
reveal_type(Constrained[object]) # revealed: Constrained[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `int | str`, found `object`"
|
||||
reveal_type(Constrained[object]) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has a default, it can be omitted:
|
||||
|
||||
@@ -135,8 +135,8 @@ reveal_type(C[Literal[5]]()) # revealed: C[Literal[5]]
|
||||
The specialization must match the generic types:
|
||||
|
||||
```py
|
||||
# error: [invalid-type-arguments] "Too many type arguments to class `C`: expected 1, got 2"
|
||||
reveal_type(C[int, int]()) # revealed: C[Unknown]
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to class `C`: expected 1, got 2"
|
||||
reveal_type(C[int, int]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
||||
@@ -149,11 +149,13 @@ class IntSubclass(int): ...
|
||||
reveal_type(Bounded[int]()) # revealed: Bounded[int]
|
||||
reveal_type(Bounded[IntSubclass]()) # revealed: Bounded[IntSubclass]
|
||||
|
||||
# error: [invalid-type-arguments] "Type `str` is not assignable to upper bound `int` of type variable `T@Bounded`"
|
||||
reveal_type(Bounded[str]()) # revealed: Bounded[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `str`"
|
||||
reveal_type(Bounded[str]()) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-arguments] "Type `int | str` is not assignable to upper bound `int` of type variable `T@Bounded`"
|
||||
reveal_type(Bounded[int | str]()) # revealed: Bounded[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to class `Bounded` is incorrect: Expected `int`, found `int | str`"
|
||||
reveal_type(Bounded[int | str]()) # revealed: Unknown
|
||||
|
||||
reveal_type(BoundedByUnion[int]()) # revealed: BoundedByUnion[int]
|
||||
reveal_type(BoundedByUnion[IntSubclass]()) # revealed: BoundedByUnion[IntSubclass]
|
||||
@@ -178,8 +180,9 @@ reveal_type(Constrained[str]()) # revealed: Constrained[str]
|
||||
# TODO: revealed: Unknown
|
||||
reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str]
|
||||
|
||||
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `T@Constrained`"
|
||||
reveal_type(Constrained[object]()) # revealed: Constrained[Unknown]
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to class `Constrained` is incorrect: Expected `int | str`, found `object`"
|
||||
reveal_type(Constrained[object]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has a default, it can be omitted:
|
||||
@@ -246,7 +249,7 @@ class C[T]:
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -260,7 +263,7 @@ class C[T]:
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -277,7 +280,7 @@ class C[T]:
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -294,7 +297,7 @@ class C[T]:
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
|
||||
class D[T]:
|
||||
@@ -307,7 +310,7 @@ class D[T]:
|
||||
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `D[int]`"
|
||||
# error: [invalid-assignment] "Object of type `D[str]` is not assignable to `D[int]`"
|
||||
wrong_innards: D[int] = D("five")
|
||||
```
|
||||
|
||||
@@ -392,7 +395,7 @@ reveal_type(C(1, 1)) # revealed: C[int]
|
||||
reveal_type(C(1, "string")) # revealed: C[int]
|
||||
reveal_type(C(1, True)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five", 1)
|
||||
```
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||
def takes_in_type[T](x: type[T]) -> type[T]:
|
||||
return x
|
||||
|
||||
reveal_type(takes_in_type(int)) # revealed: type[int]
|
||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
||||
```
|
||||
|
||||
This also works when passing in arguments that are subclasses of the parameter type.
|
||||
|
||||
@@ -754,20 +754,21 @@ def constrained[T: (Callable[[], int], Callable[[], str])](f: T):
|
||||
|
||||
## Meta-type
|
||||
|
||||
The meta-type of a typevar is `type[T]`.
|
||||
The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the
|
||||
meta-types of the constraints:
|
||||
|
||||
```py
|
||||
def normal[T](x: T):
|
||||
reveal_type(type(x)) # revealed: type[T@normal]
|
||||
reveal_type(type(x)) # revealed: type
|
||||
|
||||
def bound_object[T: object](x: T):
|
||||
reveal_type(type(x)) # revealed: type[T@bound_object]
|
||||
reveal_type(type(x)) # revealed: type
|
||||
|
||||
def bound_int[T: int](x: T):
|
||||
reveal_type(type(x)) # revealed: type[T@bound_int]
|
||||
reveal_type(type(x)) # revealed: type[int]
|
||||
|
||||
def constrained[T: (int, str)](x: T):
|
||||
reveal_type(type(x)) # revealed: type[T@constrained]
|
||||
reveal_type(type(x)) # revealed: type[int] | type[str]
|
||||
```
|
||||
|
||||
## Cycles
|
||||
@@ -833,18 +834,6 @@ class G[T: list[G]]:
|
||||
reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
|
||||
```
|
||||
|
||||
An invalid specialization in a recursive bound doesn't cause a panic:
|
||||
|
||||
```py
|
||||
# error: [invalid-type-arguments]
|
||||
class Node[T: "Node[int]"]:
|
||||
pass
|
||||
|
||||
# error: [invalid-type-arguments]
|
||||
def _(n: Node[str]):
|
||||
reveal_type(n) # revealed: Node[Unknown]
|
||||
```
|
||||
|
||||
### Defaults
|
||||
|
||||
Defaults can be generic, but can only refer to earlier typevars:
|
||||
|
||||
@@ -110,11 +110,6 @@ static_assert(not has_member(C(), "non_existent"))
|
||||
|
||||
### Class objects
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Class-level attributes can also be accessed through the class itself:
|
||||
|
||||
```py
|
||||
@@ -158,18 +153,6 @@ static_assert(has_member(D, "meta_base_attr"))
|
||||
static_assert(has_member(D, "meta_attr"))
|
||||
static_assert(has_member(D, "base_attr"))
|
||||
static_assert(has_member(D, "class_attr"))
|
||||
|
||||
def _(x: type[D]):
|
||||
static_assert(has_member(x, "meta_base_attr"))
|
||||
static_assert(has_member(x, "meta_attr"))
|
||||
static_assert(has_member(x, "base_attr"))
|
||||
static_assert(has_member(x, "class_attr"))
|
||||
|
||||
def _[T: D](x: type[T]):
|
||||
static_assert(has_member(x, "meta_base_attr"))
|
||||
static_assert(has_member(x, "meta_attr"))
|
||||
static_assert(has_member(x, "base_attr"))
|
||||
static_assert(has_member(x, "class_attr"))
|
||||
```
|
||||
|
||||
### Generic classes
|
||||
@@ -187,40 +170,6 @@ static_assert(has_member(C[int], "base_attr"))
|
||||
static_assert(has_member(C[int](), "base_attr"))
|
||||
```
|
||||
|
||||
Generic classes can also have metaclasses:
|
||||
|
||||
```py
|
||||
class Meta(type):
|
||||
FOO = 42
|
||||
|
||||
class E(Generic[T], metaclass=Meta): ...
|
||||
|
||||
static_assert(has_member(E[int], "FOO"))
|
||||
|
||||
def f(x: type[E[str]]):
|
||||
static_assert(has_member(x, "FOO"))
|
||||
```
|
||||
|
||||
### `type[Any]` and `Any`
|
||||
|
||||
`type[Any]` has all members of `type`.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
def f(x: type[Any]):
|
||||
static_assert(has_member(x, "__base__"))
|
||||
static_assert(has_member(x, "__qualname__"))
|
||||
```
|
||||
|
||||
`Any` has all members of `object`, since it is a subtype of `object`:
|
||||
|
||||
```py
|
||||
def f(x: Any):
|
||||
static_assert(has_member(x, "__repr__"))
|
||||
```
|
||||
|
||||
### Other instance-like types
|
||||
|
||||
```py
|
||||
|
||||
@@ -401,7 +401,7 @@ reveal_type(Pair) # revealed: <class 'tuple[T@Pair, T@Pair]'>
|
||||
reveal_type(Sum) # revealed: <class 'tuple[T@Sum, U@Sum]'>
|
||||
reveal_type(ListOrTuple) # revealed: types.UnionType
|
||||
reveal_type(ListOrTupleLegacy) # revealed: types.UnionType
|
||||
reveal_type(MyCallable) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
reveal_type(MyCallable) # revealed: GenericAlias
|
||||
reveal_type(AnnotatedType) # revealed: <typing.Annotated special form>
|
||||
reveal_type(TransparentAlias) # revealed: typing.TypeVar
|
||||
reveal_type(MyOptional) # revealed: types.UnionType
|
||||
@@ -415,6 +415,9 @@ def _(
|
||||
int_and_bytes: Sum[int, bytes],
|
||||
list_or_tuple: ListOrTuple[int],
|
||||
list_or_tuple_legacy: ListOrTupleLegacy[int],
|
||||
# TODO: no error here
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
|
||||
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `tuple[str, bytes]`?"
|
||||
my_callable: MyCallable[[str, bytes], int],
|
||||
annotated_int: AnnotatedType[int],
|
||||
transparent_alias: TransparentAlias[int],
|
||||
@@ -428,8 +431,9 @@ def _(
|
||||
reveal_type(int_and_bytes) # revealed: tuple[int, bytes]
|
||||
reveal_type(list_or_tuple) # revealed: list[int] | tuple[int, ...]
|
||||
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
|
||||
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
|
||||
# TODO: This should be `(str, bytes) -> int`
|
||||
reveal_type(my_callable) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
reveal_type(my_callable) # revealed: Unknown
|
||||
reveal_type(annotated_int) # revealed: int
|
||||
reveal_type(transparent_alias) # revealed: int
|
||||
reveal_type(optional_int) # revealed: int | None
|
||||
@@ -458,6 +462,9 @@ ListOfPairs = MyList[Pair[str]]
|
||||
ListOrTupleOfInts = ListOrTuple[int]
|
||||
AnnotatedInt = AnnotatedType[int]
|
||||
SubclassOfInt = MyType[int]
|
||||
# TODO: No error here
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
|
||||
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `list[int]`?"
|
||||
CallableIntToStr = MyCallable[[int], str]
|
||||
|
||||
reveal_type(IntsOrNone) # revealed: types.UnionType
|
||||
@@ -466,7 +473,7 @@ reveal_type(ListOfPairs) # revealed: <class 'list[tuple[str, str]]'>
|
||||
reveal_type(ListOrTupleOfInts) # revealed: types.UnionType
|
||||
reveal_type(AnnotatedInt) # revealed: <typing.Annotated special form>
|
||||
reveal_type(SubclassOfInt) # revealed: GenericAlias
|
||||
reveal_type(CallableIntToStr) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
reveal_type(CallableIntToStr) # revealed: Unknown
|
||||
|
||||
def _(
|
||||
ints_or_none: IntsOrNone,
|
||||
@@ -484,7 +491,7 @@ def _(
|
||||
reveal_type(annotated_int) # revealed: int
|
||||
reveal_type(subclass_of_int) # revealed: type[int]
|
||||
# TODO: This should be `(int, /) -> str`
|
||||
reveal_type(callable_int_to_str) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
reveal_type(callable_int_to_str) # revealed: Unknown
|
||||
```
|
||||
|
||||
A generic implicit type alias can also be used in another generic implicit type alias:
|
||||
@@ -509,64 +516,25 @@ def _(
|
||||
):
|
||||
reveal_type(list_of_ints) # revealed: list[int]
|
||||
reveal_type(subclass_of_int) # revealed: type[int]
|
||||
reveal_type(type_or_list) # revealed: type[Any] | list[Any]
|
||||
# TODO: Should be `type[Any] | list[Any]`
|
||||
reveal_type(type_or_list) # revealed: @Todo(type[T] for typevar T) | list[Any]
|
||||
```
|
||||
|
||||
If a generic implicit type alias is used unspecialized in a type expression, we use the default
|
||||
specialization. For type variables without defaults, this is `Unknown`:
|
||||
If a generic implicit type alias is used unspecialized in a type expression, we treat it as an
|
||||
`Unknown` specialization:
|
||||
|
||||
```py
|
||||
def _(
|
||||
list_unknown: MyList,
|
||||
dict_unknown: MyDict,
|
||||
subclass_of_unknown: MyType,
|
||||
int_and_unknown: IntAndType,
|
||||
pair_of_unknown: Pair,
|
||||
unknown_and_unknown: Sum,
|
||||
list_or_tuple: ListOrTuple,
|
||||
list_or_tuple_legacy: ListOrTupleLegacy,
|
||||
my_list: MyList,
|
||||
my_dict: MyDict,
|
||||
my_callable: MyCallable,
|
||||
annotated_unknown: AnnotatedType,
|
||||
optional_unknown: MyOptional,
|
||||
):
|
||||
# TODO: This should be `list[Unknown]`
|
||||
reveal_type(list_unknown) # revealed: list[T@MyList]
|
||||
# TODO: This should be `dict[Unknown, Unknown]`
|
||||
reveal_type(dict_unknown) # revealed: dict[T@MyDict, U@MyDict]
|
||||
# TODO: Should be `type[Unknown]`
|
||||
reveal_type(subclass_of_unknown) # revealed: type[T@MyType]
|
||||
# TODO: Should be `tuple[int, Unknown]`
|
||||
reveal_type(int_and_unknown) # revealed: tuple[int, T@IntAndType]
|
||||
# TODO: Should be `tuple[Unknown, Unknown]`
|
||||
reveal_type(pair_of_unknown) # revealed: tuple[T@Pair, T@Pair]
|
||||
# TODO: Should be `tuple[Unknown, Unknown]`
|
||||
reveal_type(unknown_and_unknown) # revealed: tuple[T@Sum, U@Sum]
|
||||
# TODO: Should be `list[Unknown] | tuple[Unknown, ...]`
|
||||
reveal_type(list_or_tuple) # revealed: list[T@ListOrTuple] | tuple[T@ListOrTuple, ...]
|
||||
# TODO: Should be `list[Unknown] | tuple[Unknown, ...]`
|
||||
reveal_type(list_or_tuple_legacy) # revealed: list[T@ListOrTupleLegacy] | tuple[T@ListOrTupleLegacy, ...]
|
||||
# TODO: Should be `list[Unknown]`
|
||||
reveal_type(my_list) # revealed: list[T@MyList]
|
||||
# TODO: Should be `dict[Unknown, Unknown]`
|
||||
reveal_type(my_dict) # revealed: dict[T@MyDict, U@MyDict]
|
||||
# TODO: Should be `(...) -> Unknown`
|
||||
reveal_type(my_callable) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
# TODO: Should be `Unknown`
|
||||
reveal_type(annotated_unknown) # revealed: T@AnnotatedType
|
||||
# TODO: Should be `Unknown | None`
|
||||
reveal_type(optional_unknown) # revealed: T@MyOptional | None
|
||||
```
|
||||
|
||||
For a type variable with a default, we use the default type:
|
||||
|
||||
```py
|
||||
T_default = TypeVar("T_default", default=int)
|
||||
|
||||
MyListWithDefault = list[T_default]
|
||||
|
||||
def _(
|
||||
list_of_str: MyListWithDefault[str],
|
||||
list_of_int: MyListWithDefault,
|
||||
):
|
||||
reveal_type(list_of_str) # revealed: list[str]
|
||||
# TODO: this should be `list[int]`
|
||||
reveal_type(list_of_int) # revealed: list[T_default@MyListWithDefault]
|
||||
reveal_type(my_callable) # revealed: (...) -> T@MyCallable
|
||||
```
|
||||
|
||||
(Generic) implicit type aliases can be used as base classes:
|
||||
@@ -637,27 +605,6 @@ def _(
|
||||
reveal_type(list_of_ints) # revealed: list[int]
|
||||
```
|
||||
|
||||
### Tuple unpacking
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
V = TypeVar("V")
|
||||
|
||||
X = tuple[T, *tuple[U, ...], V]
|
||||
Y = X[T, tuple[int, str, U], bytes]
|
||||
|
||||
def g(obj: Y[bool, range]):
|
||||
reveal_type(obj) # revealed: tuple[bool, *tuple[tuple[int, str, range], ...], bytes]
|
||||
```
|
||||
|
||||
### Error cases
|
||||
|
||||
A generic alias that is already fully specialized cannot be specialized again:
|
||||
@@ -665,10 +612,9 @@ A generic alias that is already fully specialized cannot be specialized again:
|
||||
```py
|
||||
ListOfInts = list[int]
|
||||
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 1"
|
||||
def _(doubly_specialized: ListOfInts[int]):
|
||||
# TODO: This should ideally be `list[Unknown]` or `Unknown`
|
||||
reveal_type(doubly_specialized) # revealed: list[int]
|
||||
reveal_type(doubly_specialized) # revealed: Unknown
|
||||
```
|
||||
|
||||
Specializing a generic implicit type alias with an incorrect number of type arguments also results
|
||||
@@ -684,13 +630,13 @@ MyList = list[T]
|
||||
MyDict = dict[T, U]
|
||||
|
||||
def _(
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 1, got 2"
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
|
||||
list_too_many_args: MyList[int, str],
|
||||
# error: [invalid-type-arguments] "No type argument provided for required type variable `U`"
|
||||
# error: [missing-argument] "No argument provided for required parameter `U`"
|
||||
dict_too_few_args: MyDict[int],
|
||||
):
|
||||
reveal_type(list_too_many_args) # revealed: list[Unknown]
|
||||
reveal_type(dict_too_few_args) # revealed: dict[Unknown, Unknown]
|
||||
reveal_type(list_too_many_args) # revealed: Unknown
|
||||
reveal_type(dict_too_few_args) # revealed: Unknown
|
||||
```
|
||||
|
||||
Trying to specialize a non-name node results in an error:
|
||||
@@ -704,23 +650,21 @@ def this_does_not_work() -> TypeOf[IntOrStr]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def _(
|
||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
# TODO: Better error message? `invalid-type-form`
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 1"
|
||||
specialized: this_does_not_work()[int],
|
||||
):
|
||||
reveal_type(specialized) # revealed: int | str
|
||||
reveal_type(specialized) # revealed: Unknown
|
||||
```
|
||||
|
||||
Similarly, if you try to specialize a union type without a binding context, we emit an error:
|
||||
|
||||
```py
|
||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
# error: [invalid-type-form] "`types.UnionType` is not subscriptable"
|
||||
x: (list[T] | set[T])[int]
|
||||
|
||||
def _():
|
||||
# TODO: `list[Unknown] | set[Unknown]` might be better
|
||||
reveal_type(x) # revealed: list[typing.TypeVar] | set[typing.TypeVar]
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Multiple definitions
|
||||
@@ -785,12 +729,9 @@ if flag():
|
||||
else:
|
||||
MyAlias = set[T]
|
||||
|
||||
# It is questionable whether this should be supported or not. It might also be reasonable to
|
||||
# emit an error here (e.g. "Invalid subscript of object of type `<class 'list[T@MyAlias]'> |
|
||||
# <class 'set[T@MyAlias]'>` in type expression"). If we ever choose to do so, the revealed
|
||||
# type should probably be `Unknown`.
|
||||
# error: [invalid-type-form] "Invalid subscript of object of type `<class 'list[T@MyAlias]'> | <class 'set[T@MyAlias]'>` in type expression"
|
||||
def _(x: MyAlias[int]):
|
||||
reveal_type(x) # revealed: list[int] | set[int]
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `Literal`s
|
||||
@@ -1538,18 +1479,11 @@ def _(
|
||||
|
||||
### Self-referential generic implicit type aliases
|
||||
|
||||
<!-- expect-panic: execute: too many cycle iterations -->
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
NestedDict = dict[str, "NestedDict[T] | T"]
|
||||
NestedList = list["NestedList[T] | None"]
|
||||
|
||||
def _(
|
||||
nested_dict_int: NestedDict[int],
|
||||
nested_list_str: NestedList[str],
|
||||
):
|
||||
reveal_type(nested_dict_int) # revealed: dict[str, Divergent]
|
||||
reveal_type(nested_list_str) # revealed: list[Divergent]
|
||||
```
|
||||
|
||||
@@ -38,7 +38,7 @@ See: <https://github.com/astral-sh/ty/issues/113>
|
||||
from pkg.sub import A
|
||||
|
||||
# TODO: This should be `<class 'A'>`
|
||||
reveal_type(A) # revealed: Divergent
|
||||
reveal_type(A) # revealed: Never
|
||||
```
|
||||
|
||||
`pkg/outer.py`:
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# numpy
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.14"
|
||||
```
|
||||
|
||||
## numpy's `dtype`
|
||||
|
||||
numpy functions often accept a `dtype` parameter. For example, one of `np.array`'s overloads accepts
|
||||
a `dtype` parameter of type `DTypeLike | None`. Here, we build up something that resembles numpy's
|
||||
internals in order to model the type `DTypeLike`. Many details have been left out.
|
||||
|
||||
`mini_numpy.py`:
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic, Any, Protocol, TypeAlias, runtime_checkable, final
|
||||
import builtins
|
||||
|
||||
_ItemT_co = TypeVar("_ItemT_co", default=Any, covariant=True)
|
||||
|
||||
class generic(Generic[_ItemT_co]):
|
||||
@property
|
||||
def dtype(self) -> _DTypeT_co:
|
||||
raise NotImplementedError
|
||||
|
||||
_BoolItemT_co = TypeVar("_BoolItemT_co", bound=builtins.bool, default=builtins.bool, covariant=True)
|
||||
|
||||
class bool(generic[_BoolItemT_co], Generic[_BoolItemT_co]): ...
|
||||
|
||||
@final
|
||||
class object_(generic): ...
|
||||
|
||||
_ScalarT = TypeVar("_ScalarT", bound=generic)
|
||||
_ScalarT_co = TypeVar("_ScalarT_co", bound=generic, default=Any, covariant=True)
|
||||
|
||||
@final
|
||||
class dtype(Generic[_ScalarT_co]): ...
|
||||
|
||||
_DTypeT_co = TypeVar("_DTypeT_co", bound=dtype, default=dtype, covariant=True)
|
||||
|
||||
@runtime_checkable
|
||||
class _SupportsDType(Protocol[_DTypeT_co]):
|
||||
@property
|
||||
def dtype(self) -> _DTypeT_co: ...
|
||||
|
||||
_DTypeLike: TypeAlias = type[_ScalarT] | dtype[_ScalarT] | _SupportsDType[dtype[_ScalarT]]
|
||||
|
||||
DTypeLike: TypeAlias = _DTypeLike[Any] | str | None
|
||||
```
|
||||
|
||||
Now we can make sure that a function which accepts `DTypeLike | None` works as expected:
|
||||
|
||||
```py
|
||||
import mini_numpy as np
|
||||
|
||||
def accepts_dtype(dtype: np.DTypeLike | None) -> None: ...
|
||||
|
||||
accepts_dtype(dtype=np.bool)
|
||||
accepts_dtype(dtype=np.dtype[np.bool])
|
||||
accepts_dtype(dtype=object)
|
||||
accepts_dtype(dtype=np.object_)
|
||||
accepts_dtype(dtype="U")
|
||||
```
|
||||
@@ -1,314 +0,0 @@
|
||||
# `typing.override`
|
||||
|
||||
## Basics
|
||||
|
||||
Decorating a method with `typing.override` decorator is an explicit indication to a type checker
|
||||
that the method is intended to override a method on a superclass. If the decorated method does not
|
||||
in fact override anything, a type checker should report a diagnostic on that method.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```pyi
|
||||
from typing_extensions import override, Callable, TypeVar
|
||||
|
||||
def lossy_decorator(fn: Callable) -> Callable: ...
|
||||
|
||||
class A:
|
||||
@override
|
||||
def __repr__(self): ... # fine: overrides `object.__repr__`
|
||||
|
||||
class Parent:
|
||||
def foo(self): ...
|
||||
@property
|
||||
def my_property1(self) -> int: ...
|
||||
@property
|
||||
def my_property2(self) -> int: ...
|
||||
baz = None
|
||||
@classmethod
|
||||
def class_method1(cls) -> int: ...
|
||||
@staticmethod
|
||||
def static_method1() -> int: ...
|
||||
@classmethod
|
||||
def class_method2(cls) -> int: ...
|
||||
@staticmethod
|
||||
def static_method2() -> int: ...
|
||||
@lossy_decorator
|
||||
def decorated_1(self): ...
|
||||
@lossy_decorator
|
||||
def decorated_2(self): ...
|
||||
@lossy_decorator
|
||||
def decorated_3(self): ...
|
||||
|
||||
class Child(Parent):
|
||||
@override
|
||||
def foo(self): ... # fine: overrides `Parent.foo`
|
||||
@property
|
||||
@override
|
||||
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
||||
@override
|
||||
@property
|
||||
def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
|
||||
@override
|
||||
def baz(self): ... # fine: overrides `Parent.baz`
|
||||
@classmethod
|
||||
@override
|
||||
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
||||
@staticmethod
|
||||
@override
|
||||
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
||||
@override
|
||||
@classmethod
|
||||
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
||||
@override
|
||||
@staticmethod
|
||||
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
||||
@override
|
||||
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
||||
@override
|
||||
@lossy_decorator
|
||||
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
||||
@lossy_decorator
|
||||
@override
|
||||
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
||||
|
||||
class OtherChild(Parent): ...
|
||||
|
||||
class Grandchild(OtherChild):
|
||||
@override
|
||||
def foo(self): ... # fine: overrides `Parent.foo`
|
||||
@override
|
||||
@property
|
||||
def bar(self) -> int: ... # fine: overrides `Parent.bar`
|
||||
@override
|
||||
def baz(self): ... # fine: overrides `Parent.baz`
|
||||
@classmethod
|
||||
@override
|
||||
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
||||
@staticmethod
|
||||
@override
|
||||
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
||||
@override
|
||||
@classmethod
|
||||
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
||||
@override
|
||||
@staticmethod
|
||||
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
||||
@override
|
||||
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
||||
@override
|
||||
@lossy_decorator
|
||||
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
||||
@lossy_decorator
|
||||
@override
|
||||
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
||||
|
||||
class Invalid:
|
||||
@override
|
||||
def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
||||
@override
|
||||
@classmethod
|
||||
def foo(self): ... # error: [invalid-explicit-override]
|
||||
@classmethod
|
||||
@override
|
||||
def bar(self): ... # error: [invalid-explicit-override]
|
||||
@staticmethod
|
||||
@override
|
||||
def baz(): ... # error: [invalid-explicit-override]
|
||||
@override
|
||||
@staticmethod
|
||||
def eggs(): ... # error: [invalid-explicit-override]
|
||||
@property
|
||||
@override
|
||||
def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
|
||||
@override
|
||||
@property
|
||||
def bad_property2(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
|
||||
@lossy_decorator
|
||||
@override
|
||||
def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
|
||||
@override
|
||||
@lossy_decorator
|
||||
def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
|
||||
|
||||
# TODO: all overrides in this class should cause us to emit *Liskov* violations,
|
||||
# but not `@override` violations
|
||||
class LiskovViolatingButNotOverrideViolating(Parent):
|
||||
@override
|
||||
@property
|
||||
def foo(self) -> int: ...
|
||||
@override
|
||||
def my_property1(self) -> int: ...
|
||||
@staticmethod
|
||||
@override
|
||||
def class_method1() -> int: ...
|
||||
@classmethod
|
||||
@override
|
||||
def static_method1(cls) -> int: ...
|
||||
|
||||
# Diagnostic edge case: `override` is very far away from the method definition in the source code:
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def identity(x: T) -> T: ...
|
||||
|
||||
class Foo:
|
||||
@override
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
@identity
|
||||
def bar(self): ... # error: [invalid-explicit-override]
|
||||
```
|
||||
|
||||
## Overloads
|
||||
|
||||
The typing spec states that for an overloaded method, `@override` should only be applied to the
|
||||
implementation function. However, we nonetheless respect the decorator in this situation, even
|
||||
though we also emit `invalid-overload` on these methods.
|
||||
|
||||
```py
|
||||
from typing_extensions import override, overload
|
||||
|
||||
class Spam:
|
||||
@overload
|
||||
def foo(self, x: str) -> str: ...
|
||||
@overload
|
||||
def foo(self, x: int) -> int: ...
|
||||
@override
|
||||
def foo(self, x: str | int) -> str | int: # error: [invalid-explicit-override]
|
||||
return x
|
||||
|
||||
@overload
|
||||
@override
|
||||
def bar(self, x: str) -> str: ...
|
||||
@overload
|
||||
@override
|
||||
def bar(self, x: int) -> int: ...
|
||||
@override
|
||||
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
|
||||
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
|
||||
# error: [invalid-explicit-override]
|
||||
def bar(self, x: str | int) -> str | int:
|
||||
return x
|
||||
|
||||
@overload
|
||||
@override
|
||||
def baz(self, x: str) -> str: ...
|
||||
@overload
|
||||
def baz(self, x: int) -> int: ...
|
||||
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
|
||||
# error: [invalid-explicit-override]
|
||||
def baz(self, x: str | int) -> str | int:
|
||||
return x
|
||||
```
|
||||
|
||||
In a stub file, `@override` should always be applied to the first overload. Even if it isn't, we
|
||||
always emit `invalid-explicit-override` diagnostics on the first overload.
|
||||
|
||||
`module.pyi`:
|
||||
|
||||
```pyi
|
||||
from typing_extensions import override, overload
|
||||
|
||||
class Spam:
|
||||
@overload
|
||||
def foo(self, x: str) -> str: ... # error: [invalid-explicit-override]
|
||||
@overload
|
||||
@override
|
||||
# error: [invalid-overload] "`@override` decorator should be applied only to the first overload"
|
||||
def foo(self, x: int) -> int: ...
|
||||
|
||||
@overload
|
||||
@override
|
||||
def bar(self, x: str) -> str: ... # error: [invalid-explicit-override]
|
||||
@overload
|
||||
@override
|
||||
# error: [invalid-overload] "`@override` decorator should be applied only to the first overload"
|
||||
def bar(self, x: int) -> int: ...
|
||||
|
||||
@overload
|
||||
@override
|
||||
def baz(self, x: str) -> str: ... # error: [invalid-explicit-override]
|
||||
@overload
|
||||
def baz(self, x: int) -> int: ...
|
||||
```
|
||||
|
||||
## Classes inheriting from `Any`
|
||||
|
||||
```py
|
||||
from typing_extensions import Any, override
|
||||
from does_not_exist import SomethingUnknown # error: [unresolved-import]
|
||||
|
||||
class Parent1(Any): ...
|
||||
class Parent2(SomethingUnknown): ...
|
||||
|
||||
class Child1(Parent1):
|
||||
@override
|
||||
def bar(self): ... # fine
|
||||
|
||||
class Child2(Parent2):
|
||||
@override
|
||||
def bar(self): ... # fine
|
||||
```
|
||||
|
||||
## Override of a synthesized method
|
||||
|
||||
```pyi
|
||||
from typing_extensions import NamedTuple, TypedDict, override, Any, Self
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(order=True)
|
||||
class ParentDataclass:
|
||||
x: int
|
||||
|
||||
class Child(ParentDataclass):
|
||||
@override
|
||||
def __lt__(self, other: ParentDataclass) -> bool: ... # fine
|
||||
|
||||
class MyNamedTuple(NamedTuple):
|
||||
x: int
|
||||
|
||||
@override
|
||||
# TODO: this raises an exception at runtime (which we should emit a diagnostic for).
|
||||
# It shouldn't be an `invalid-explicit-override` diagnostic, however.
|
||||
def _asdict(self, /) -> dict[str, Any]: ...
|
||||
|
||||
class MyNamedTupleParent(NamedTuple):
|
||||
x: int
|
||||
|
||||
class MyNamedTupleChild(MyNamedTupleParent):
|
||||
@override
|
||||
def _asdict(self, /) -> dict[str, Any]: ... # fine
|
||||
|
||||
class MyTypedDict(TypedDict):
|
||||
x: int
|
||||
|
||||
@override
|
||||
# TODO: it's invalid to define a method on a `TypedDict` class,
|
||||
# so we should emit a diagnostic here.
|
||||
# It shouldn't be an `invalid-explicit-override` diagnostic, however.
|
||||
def copy(self) -> Self: ...
|
||||
|
||||
class Grandparent(Any): ...
|
||||
|
||||
class Parent(Grandparent, NamedTuple): # error: [invalid-named-tuple]
|
||||
x: int
|
||||
|
||||
class Child(Parent):
|
||||
@override
|
||||
def foo(self): ... # fine because `Any` is in the MRO
|
||||
```
|
||||
@@ -98,12 +98,6 @@ def _(x: MyAlias):
|
||||
|
||||
## Generic aliases
|
||||
|
||||
A more comprehensive set of tests can be found in
|
||||
[`implicit_type_aliases.md`](./implicit_type_aliases.md). If the implementations ever diverge, we
|
||||
may need to duplicate more tests here.
|
||||
|
||||
### Basic
|
||||
|
||||
```py
|
||||
from typing import TypeAlias, TypeVar
|
||||
|
||||
@@ -120,21 +114,6 @@ def _(list_of_int: MyList[int], list_or_set_of_str: ListOrSet[str]):
|
||||
reveal_type(list_or_set_of_str) # revealed: list[str] | set[str]
|
||||
```
|
||||
|
||||
### Stringified generic alias
|
||||
|
||||
```py
|
||||
from typing import TypeAlias, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
TotallyStringifiedPEP613: TypeAlias = "dict[T, U]"
|
||||
TotallyStringifiedPartiallySpecialized: TypeAlias = "TotallyStringifiedPEP613[U, int]"
|
||||
|
||||
def f(x: "TotallyStringifiedPartiallySpecialized[str]"):
|
||||
reveal_type(x) # revealed: @Todo(Generic stringified PEP-613 type alias)
|
||||
```
|
||||
|
||||
## Subscripted generic alias in union
|
||||
|
||||
```py
|
||||
@@ -182,46 +161,17 @@ def _(x: IntOrStr):
|
||||
## Cyclic
|
||||
|
||||
```py
|
||||
from typing import TypeAlias, TypeVar, Union
|
||||
from types import UnionType
|
||||
from typing import TypeAlias
|
||||
|
||||
RecursiveTuple: TypeAlias = tuple[int | "RecursiveTuple", str]
|
||||
|
||||
def _(rec: RecursiveTuple):
|
||||
# TODO should be `tuple[int | RecursiveTuple, str]`
|
||||
reveal_type(rec) # revealed: tuple[Divergent, str]
|
||||
|
||||
RecursiveHomogeneousTuple: TypeAlias = tuple[int | "RecursiveHomogeneousTuple", ...]
|
||||
|
||||
def _(rec: RecursiveHomogeneousTuple):
|
||||
# TODO should be `tuple[int | RecursiveHomogeneousTuple, ...]`
|
||||
reveal_type(rec) # revealed: tuple[Divergent, ...]
|
||||
|
||||
ClassInfo: TypeAlias = type | UnionType | tuple["ClassInfo", ...]
|
||||
reveal_type(ClassInfo) # revealed: types.UnionType
|
||||
|
||||
def my_isinstance(obj: object, classinfo: ClassInfo) -> bool:
|
||||
# TODO should be `type | UnionType | tuple[ClassInfo, ...]`
|
||||
reveal_type(classinfo) # revealed: type | UnionType | tuple[Divergent, ...]
|
||||
return isinstance(obj, classinfo)
|
||||
|
||||
K = TypeVar("K")
|
||||
V = TypeVar("V")
|
||||
NestedDict: TypeAlias = dict[K, Union[V, "NestedDict[K, V]"]]
|
||||
|
||||
def _(nested: NestedDict[str, int]):
|
||||
# TODO should be `dict[str, int | NestedDict[str, int]]`
|
||||
reveal_type(nested) # revealed: dict[@Todo(specialized recursive generic type alias), Divergent]
|
||||
|
||||
my_isinstance(1, int)
|
||||
my_isinstance(1, int | str)
|
||||
my_isinstance(1, (int, str))
|
||||
my_isinstance(1, (int, (str, float)))
|
||||
my_isinstance(1, (int, (str | float)))
|
||||
# error: [invalid-argument-type]
|
||||
my_isinstance(1, 1)
|
||||
# TODO should be an invalid-argument-type error
|
||||
my_isinstance(1, (int, (str, 1)))
|
||||
```
|
||||
|
||||
## Conditionally imported on Python < 3.10
|
||||
|
||||
@@ -106,29 +106,6 @@ def _(flag: bool):
|
||||
```py
|
||||
type ListOrSet[T] = list[T] | set[T]
|
||||
reveal_type(ListOrSet.__type_params__) # revealed: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
type Tuple1[T] = tuple[T]
|
||||
|
||||
def _(cond: bool):
|
||||
Generic = ListOrSet if cond else Tuple1
|
||||
|
||||
def _(x: Generic[int]):
|
||||
reveal_type(x) # revealed: list[int] | set[int] | tuple[int]
|
||||
|
||||
try:
|
||||
class Foo[T]:
|
||||
x: T
|
||||
def foo(self) -> T:
|
||||
return self.x
|
||||
|
||||
...
|
||||
except Exception:
|
||||
class Foo[T]:
|
||||
x: T
|
||||
def foo(self) -> T:
|
||||
return self.x
|
||||
|
||||
def f(x: Foo[int]):
|
||||
reveal_type(x.foo()) # revealed: int
|
||||
```
|
||||
|
||||
## In unions and intersections
|
||||
@@ -267,47 +244,6 @@ def f(x: IntOr, y: OrInt):
|
||||
reveal_type(x) # revealed: Never
|
||||
if not isinstance(y, int):
|
||||
reveal_type(y) # revealed: Never
|
||||
|
||||
# error: [cyclic-type-alias-definition] "Cyclic definition of `Itself`"
|
||||
type Itself = Itself
|
||||
|
||||
def foo(
|
||||
# this is a very strange thing to do, but this is a regression test to ensure it doesn't panic
|
||||
Itself: Itself,
|
||||
):
|
||||
x: Itself
|
||||
reveal_type(Itself) # revealed: Divergent
|
||||
|
||||
# A type alias defined with invalid recursion behaves as a dynamic type.
|
||||
foo(42)
|
||||
foo("hello")
|
||||
|
||||
# error: [cyclic-type-alias-definition] "Cyclic definition of `A`"
|
||||
type A = B
|
||||
# error: [cyclic-type-alias-definition] "Cyclic definition of `B`"
|
||||
type B = A
|
||||
|
||||
def bar(B: B):
|
||||
x: B
|
||||
reveal_type(B) # revealed: Divergent
|
||||
|
||||
# error: [cyclic-type-alias-definition] "Cyclic definition of `G`"
|
||||
type G[T] = G[T]
|
||||
# error: [cyclic-type-alias-definition] "Cyclic definition of `H`"
|
||||
type H[T] = I[T]
|
||||
# error: [cyclic-type-alias-definition] "Cyclic definition of `I`"
|
||||
type I[T] = H[T]
|
||||
|
||||
# It's not possible to create an element of this type, but it's not an error for now
|
||||
type DirectRecursiveList[T] = list[DirectRecursiveList[T]]
|
||||
|
||||
# TODO: this should probably be a cyclic-type-alias-definition error
|
||||
type Foo[T] = list[T] | Bar[T]
|
||||
type Bar[T] = int | Foo[T]
|
||||
|
||||
def _(x: Bar[int]):
|
||||
# TODO: should be `int | list[int]`
|
||||
reveal_type(x) # revealed: int | list[int] | Any
|
||||
```
|
||||
|
||||
### With legacy generic
|
||||
@@ -391,7 +327,7 @@ class C(P[T]):
|
||||
pass
|
||||
|
||||
reveal_type(C[int]()) # revealed: C[int]
|
||||
reveal_type(C()) # revealed: C[C[Divergent]]
|
||||
reveal_type(C()) # revealed: C[Divergent]
|
||||
```
|
||||
|
||||
### Union inside generic
|
||||
|
||||
@@ -255,10 +255,12 @@ And it is also an error to use `Protocol` in type expressions:
|
||||
|
||||
def f(
|
||||
x: Protocol, # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
|
||||
y: type[Protocol], # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
|
||||
y: type[Protocol], # TODO: should emit `[invalid-type-form]` here too
|
||||
):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
reveal_type(y) # revealed: type[Unknown]
|
||||
|
||||
# TODO: should be `type[Unknown]`
|
||||
reveal_type(y) # revealed: @Todo(unsupported type[X] special form)
|
||||
|
||||
# fmt: on
|
||||
```
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
Regression test for <https://github.com/astral-sh/ty/issues/1377>.
|
||||
|
||||
The code is an excerpt from <https://github.com/Gobot1234/steam.py>.
|
||||
The code is an excerpt from <https://github.com/Gobot1234/steam.py> that is minimal enough to
|
||||
trigger the iteration count mismatch bug in Salsa.
|
||||
|
||||
<!-- expect-panic: execute: too many cycle iterations -->
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
|
||||
@@ -34,11 +34,5 @@ error[invalid-key]: Unknown key "Retries" for TypedDict `Config`
|
||||
| TypedDict `Config`
|
||||
|
|
||||
info: rule `invalid-key` is enabled by default
|
||||
4 | retries: int
|
||||
5 |
|
||||
6 | def _(config: Config) -> None:
|
||||
- config["Retries"] = 30.0 # error: [invalid-key]
|
||||
7 + config["retries"] = 30.0 # error: [invalid-key]
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
```
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user