Compare commits
46 Commits
david/self
...
david/unre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
500b9a2691 | ||
|
|
15476be531 | ||
|
|
e7a361699d | ||
|
|
a218e1901b | ||
|
|
f108043d2d | ||
|
|
77b45aeee9 | ||
|
|
c6f4c106b0 | ||
|
|
dc55b4c8a2 | ||
|
|
41d19c3c29 | ||
|
|
99d44299e8 | ||
|
|
32ad489d79 | ||
|
|
a3e7e7d8b6 | ||
|
|
aea4bbbb30 | ||
|
|
167e445243 | ||
|
|
dccfd6e4f8 | ||
|
|
eb4ae2b910 | ||
|
|
5be842b1c3 | ||
|
|
2a21d79ec4 | ||
|
|
1964ecdbb7 | ||
|
|
6d167672f1 | ||
|
|
ae45b897ea | ||
|
|
90f48f45b0 | ||
|
|
d9cbf2fe44 | ||
|
|
3f6c65e78c | ||
|
|
976c37a849 | ||
|
|
a378ff38dc | ||
|
|
d8bca0d3a2 | ||
|
|
6f1cf5b686 | ||
|
|
8639f8c1a6 | ||
|
|
f1b2e85339 | ||
|
|
6d61c8aa16 | ||
|
|
8a7ba5d2df | ||
|
|
6fcbe8efb4 | ||
|
|
c40b37aa36 | ||
|
|
ef0e2a6e1b | ||
|
|
4fb1416bf4 | ||
|
|
8a860b89b4 | ||
|
|
f96fa6b0e2 | ||
|
|
4cd2b9926e | ||
|
|
11a2929ed7 | ||
|
|
187974eff4 | ||
|
|
14ba469fc0 | ||
|
|
6fd10e2fe7 | ||
|
|
e0f3eaf1dd | ||
|
|
c84c690f1e | ||
|
|
0d649f9afd |
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -13,9 +13,10 @@
|
||||
# flake8-pyi
|
||||
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
||||
|
||||
# Script for fuzzing the parser
|
||||
/scripts/fuzz-parser/ @AlexWaygood
|
||||
# Script for fuzzing the parser/red-knot etc.
|
||||
/python/py-fuzzer/ @AlexWaygood
|
||||
|
||||
# red-knot
|
||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/scripts/knot_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
|
||||
21
.github/workflows/ci.yaml
vendored
21
.github/workflows/ci.yaml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_ast/**
|
||||
- crates/ruff_python_parser/**
|
||||
- scripts/fuzz-parser/**
|
||||
- python/py-fuzzer/**
|
||||
- .github/workflows/ci.yaml
|
||||
|
||||
linter:
|
||||
@@ -82,6 +82,7 @@ jobs:
|
||||
code:
|
||||
- "**/*"
|
||||
- "!**/*.md"
|
||||
- "crates/red_knot_python_semantic/resources/mdtest/**/*.md"
|
||||
- "!docs/**"
|
||||
- "!assets/**"
|
||||
|
||||
@@ -317,13 +318,7 @@ jobs:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install Python requirements
|
||||
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -335,7 +330,15 @@ jobs:
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ${{ steps.download-cached-binary.outputs.download-path }}/ruff
|
||||
|
||||
python scripts/fuzz-parser/fuzz.py --bin ruff 0-500 --test-executable ${{ steps.download-cached-binary.outputs.download-path }}/ruff
|
||||
(
|
||||
uvx \
|
||||
--python=${{ env.PYTHON_VERSION }} \
|
||||
--from=./python/py-fuzzer \
|
||||
fuzz \
|
||||
--test-executable=${{ steps.download-cached-binary.outputs.download-path }}/ruff \
|
||||
--bin=ruff \
|
||||
0-500
|
||||
)
|
||||
|
||||
scripts:
|
||||
name: "test scripts"
|
||||
|
||||
19
.github/workflows/daily_fuzz.yaml
vendored
19
.github/workflows/daily_fuzz.yaml
vendored
@@ -32,13 +32,7 @@ jobs:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install Python requirements
|
||||
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
@@ -49,7 +43,16 @@ jobs:
|
||||
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
|
||||
run: cargo build --locked
|
||||
- name: Fuzz
|
||||
run: python scripts/fuzz-parser/fuzz.py --bin ruff $(shuf -i 0-9999999999999999999 -n 1000) --test-executable target/debug/ruff
|
||||
run: |
|
||||
(
|
||||
uvx \
|
||||
--python=3.12 \
|
||||
--from=./python/py-fuzzer \
|
||||
fuzz \
|
||||
--test-executable=target/debug/ruff \
|
||||
--bin=ruff \
|
||||
$(shuf -i 0-9999999999999999999 -n 1000)
|
||||
)
|
||||
|
||||
create-issue-on-failure:
|
||||
name: Create an issue if the daily fuzz surfaced any bugs
|
||||
|
||||
38
CHANGELOG.md
38
CHANGELOG.md
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- Formatter: Avoid invalid syntax for format-spec with quotes for all Python versions ([#14625](https://github.com/astral-sh/ruff/pull/14625))
|
||||
- Formatter: Consider quotes inside format-specs when choosing the quotes for an f-string ([#14493](https://github.com/astral-sh/ruff/pull/14493))
|
||||
- Formatter: Do not consider f-strings with escaped newlines as multiline ([#14624](https://github.com/astral-sh/ruff/pull/14624))
|
||||
- Formatter: Fix f-string formatting in assignment statement ([#14454](https://github.com/astral-sh/ruff/pull/14454))
|
||||
- Formatter: Fix unnecessary space around power operator (`**`) in overlong f-string expressions ([#14489](https://github.com/astral-sh/ruff/pull/14489))
|
||||
- \[`airflow`\] Avoid implicit `schedule` argument to `DAG` and `@dag` (`AIR301`) ([#14581](https://github.com/astral-sh/ruff/pull/14581))
|
||||
- \[`flake8-builtins`\] Exempt private built-in modules (`A005`) ([#14505](https://github.com/astral-sh/ruff/pull/14505))
|
||||
- \[`flake8-pytest-style`\] Fix `pytest.mark.parametrize` rules to check calls instead of decorators ([#14515](https://github.com/astral-sh/ruff/pull/14515))
|
||||
- \[`flake8-type-checking`\] Implement `runtime-cast-value` (`TC006`) ([#14511](https://github.com/astral-sh/ruff/pull/14511))
|
||||
- \[`flake8-type-checking`\] Implement `unquoted-type-alias` (`TC007`) and `quoted-type-alias` (`TC008`) ([#12927](https://github.com/astral-sh/ruff/pull/12927))
|
||||
- \[`flake8-use-pathlib`\] Recommend `Path.iterdir()` over `os.listdir()` (`PTH208`) ([#14509](https://github.com/astral-sh/ruff/pull/14509))
|
||||
- \[`pylint`\] Extend `invalid-envvar-default` to detect `os.environ.get` (`PLW1508`) ([#14512](https://github.com/astral-sh/ruff/pull/14512))
|
||||
- \[`pylint`\] Implement `len-test` (`PLC1802`) ([#14309](https://github.com/astral-sh/ruff/pull/14309))
|
||||
- \[`refurb`\] Fix bug where methods defined using lambdas were flagged by `FURB118` ([#14639](https://github.com/astral-sh/ruff/pull/14639))
|
||||
- \[`ruff`\] Auto-add `r` prefix when string has no backslashes for `unraw-re-pattern` (`RUF039`) ([#14536](https://github.com/astral-sh/ruff/pull/14536))
|
||||
- \[`ruff`\] Implement `invalid-assert-message-literal-argument` (`RUF040`) ([#14488](https://github.com/astral-sh/ruff/pull/14488))
|
||||
- \[`ruff`\] Implement `unnecessary-nested-literal` (`RUF041`) ([#14323](https://github.com/astral-sh/ruff/pull/14323))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Ignore more rules for stub files ([#14541](https://github.com/astral-sh/ruff/pull/14541))
|
||||
- \[`pep8-naming`\] Eliminate false positives for single-letter names (`N811`, `N814`) ([#14584](https://github.com/astral-sh/ruff/pull/14584))
|
||||
- \[`pyflakes`\] Avoid false positives in `@no_type_check` contexts (`F821`, `F722`) ([#14615](https://github.com/astral-sh/ruff/pull/14615))
|
||||
- \[`ruff`\] Detect redirected-noqa in file-level comments (`RUF101`) ([#14635](https://github.com/astral-sh/ruff/pull/14635))
|
||||
- \[`ruff`\] Mark fixes for `unsorted-dunder-all` and `unsorted-dunder-slots` as unsafe when there are complex comments in the sequence (`RUF022`, `RUF023`) ([#14560](https://github.com/astral-sh/ruff/pull/14560))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid fixing code to `None | None` for `redundant-none-literal` (`PYI061`) and `never-union` (`RUF020`) ([#14583](https://github.com/astral-sh/ruff/pull/14583), [#14589](https://github.com/astral-sh/ruff/pull/14589))
|
||||
- \[`flake8-bugbear`\] Fix `mutable-contextvar-default` to resolve annotated function calls properly (`B039`) ([#14532](https://github.com/astral-sh/ruff/pull/14532))
|
||||
- \[`flake8-type-checking`\] Avoid syntax errors and type checking problem for quoted annotations autofix (`TC003`, `TC006`) ([#14634](https://github.com/astral-sh/ruff/pull/14634))
|
||||
- \[`pylint`\] Do not wrap function calls in parentheses in the fix for unnecessary-dunder-call (`PLC2801`) ([#14601](https://github.com/astral-sh/ruff/pull/14601))
|
||||
- \[`ruff`\] Handle `attrs`'s `auto_attribs` correctly (`RUF009`) ([#14520](https://github.com/astral-sh/ruff/pull/14520))
|
||||
|
||||
## 0.8.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.8.0) for a migration guide and overview of the changes!
|
||||
|
||||
@@ -139,7 +139,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
1. Create a file for your rule (e.g., `crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs`).
|
||||
|
||||
1. In that file, define a violation struct (e.g., `pub struct AssertFalse`). You can grep for
|
||||
`#[violation]` to see examples.
|
||||
`#[derive(ViolationMetadata)]` to see examples.
|
||||
|
||||
1. In that file, define a function that adds the violation to the diagnostic list as appropriate
|
||||
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2489,7 +2489,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2708,7 +2708,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -3023,7 +3023,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
|
||||
@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.8.0/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.8.0/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.8.1/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.8.1/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.8.0
|
||||
rev: v0.8.1
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
36
clippy.toml
36
clippy.toml
@@ -1,21 +1,25 @@
|
||||
doc-valid-idents = [
|
||||
"..",
|
||||
"CodeQL",
|
||||
"FastAPI",
|
||||
"IPython",
|
||||
"LangChain",
|
||||
"LibCST",
|
||||
"McCabe",
|
||||
"NumPy",
|
||||
"SCREAMING_SNAKE_CASE",
|
||||
"SQLAlchemy",
|
||||
"StackOverflow",
|
||||
"PyCharm",
|
||||
"..",
|
||||
"CodeQL",
|
||||
"FastAPI",
|
||||
"IPython",
|
||||
"LangChain",
|
||||
"LibCST",
|
||||
"McCabe",
|
||||
"NumPy",
|
||||
"SCREAMING_SNAKE_CASE",
|
||||
"SQLAlchemy",
|
||||
"StackOverflow",
|
||||
"PyCharm",
|
||||
"SNMPv1",
|
||||
"SNMPv2",
|
||||
"SNMPv3",
|
||||
"PyFlakes"
|
||||
]
|
||||
|
||||
ignore-interior-mutability = [
|
||||
# Interned is read-only. The wrapped `Rc` never gets updated.
|
||||
"ruff_formatter::format_element::Interned",
|
||||
# The expression is read-only.
|
||||
"ruff_python_ast::hashable::HashableExpr",
|
||||
# Interned is read-only. The wrapped `Rc` never gets updated.
|
||||
"ruff_formatter::format_element::Interned",
|
||||
# The expression is read-only.
|
||||
"ruff_python_ast::hashable::HashableExpr",
|
||||
]
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
pub enum TargetVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
#[default]
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
#[default]
|
||||
Py313,
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ def f7() -> "\x69nt":
|
||||
def f8() -> """int""":
|
||||
return 1
|
||||
|
||||
# error: [annotation-byte-string] "Type expressions cannot use bytes literal"
|
||||
def f9() -> "b'int'":
|
||||
return 1
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ if (x := 1) and bool_instance():
|
||||
if True or (x := 1):
|
||||
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if True and (x := 1):
|
||||
# TODO: infer that the second arm is always executed, do not raise a diagnostic
|
||||
|
||||
@@ -36,8 +36,6 @@ from typing import Callable
|
||||
def foo() -> int:
|
||||
return 42
|
||||
|
||||
# TODO: This should be resolved once we understand `Callable` annotations
|
||||
# error: [annotation-with-invalid-expression]
|
||||
def decorator(func) -> Callable[[], int]:
|
||||
return foo
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ Literal: _SpecialForm
|
||||
```py
|
||||
from other import Literal
|
||||
|
||||
# error: [annotation-with-invalid-expression]
|
||||
a1: Literal[26]
|
||||
|
||||
def f():
|
||||
|
||||
@@ -256,7 +256,7 @@ class O: ...
|
||||
class X(O): ...
|
||||
class Y(O): ...
|
||||
|
||||
if bool():
|
||||
if returns_bool():
|
||||
foo = Y
|
||||
else:
|
||||
foo = object
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Consolidating narrowed types after if statement
|
||||
|
||||
## After if-else statements, narrowing has no effect if the variable is not mutated in any branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int | None
|
||||
```
|
||||
|
||||
## Narrowing can have a persistent effect if the variable is mutated in one branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 10
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## An if statement without an explicit `else` branch is equivalent to one with a no-op `else` branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
y = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 0
|
||||
|
||||
if y is None:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
reveal_type(y) # revealed: int | None
|
||||
```
|
||||
|
||||
## An if-elif without an explicit else branch is equivalent to one with an empty else branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 0
|
||||
elif x > 50:
|
||||
x = 50
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
@@ -0,0 +1,303 @@
|
||||
# Statically-known branches
|
||||
|
||||
## Always false
|
||||
|
||||
### If
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Else
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
pass
|
||||
else:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Always true
|
||||
|
||||
### If
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
### Else
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
pass
|
||||
else:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Combination
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Nested
|
||||
|
||||
```py path=nested_if_true_if_true.py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
```py path=nested_if_true_if_false.py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
```py path=nested_if_true_if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if flag():
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
```
|
||||
|
||||
```py path=nested_if_bool_if_true.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if flag():
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 4]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_true.py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if True:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_false.py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if False:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[4]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if flag():
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3, 4]
|
||||
```
|
||||
|
||||
## If-expressions
|
||||
|
||||
### Always true
|
||||
|
||||
```py
|
||||
x = 1 if True else 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Always false
|
||||
|
||||
```py
|
||||
x = 1 if False else 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Boolean expressions
|
||||
|
||||
### Always true
|
||||
|
||||
```py
|
||||
(x := 1) == 1 or (x := 2)
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Always false
|
||||
|
||||
```py
|
||||
(x := 1) == 0 or (x := 2)
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Conditional declarations
|
||||
|
||||
```py path=if_false.py
|
||||
x: str
|
||||
|
||||
if False:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
```py path=if_true_else.py
|
||||
x: str
|
||||
|
||||
if True:
|
||||
pass
|
||||
else:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
```py path=if_true.py
|
||||
x: str
|
||||
|
||||
if True:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
```py path=if_false_else.py
|
||||
x: str
|
||||
|
||||
if False:
|
||||
pass
|
||||
else:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
```py path=if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x: str
|
||||
|
||||
if flag():
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
## Conditionally defined functions
|
||||
|
||||
```py
|
||||
def f() -> int: ...
|
||||
def g() -> int: ...
|
||||
|
||||
if True:
|
||||
def f() -> str: ...
|
||||
|
||||
else:
|
||||
def g() -> str: ...
|
||||
|
||||
reveal_type(f()) # revealed: str
|
||||
reveal_type(g()) # revealed: int
|
||||
```
|
||||
|
||||
## Conditionally defined class attributes
|
||||
|
||||
```py
|
||||
class C:
|
||||
if True:
|
||||
x: int = 1
|
||||
else:
|
||||
x: str = "a"
|
||||
|
||||
reveal_type(C.x) # revealed: int
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- declarations vs bindings => NoDefault: NoDefaultType
|
||||
- conditional imports
|
||||
- conditional class definitions
|
||||
- compare with tests in if.md=>Statically known branches
|
||||
- boundness
|
||||
- TODO in `issubclass.md`
|
||||
@@ -21,10 +21,11 @@ reveal_type(Identity[0]) # revealed: str
|
||||
## Class getitem union
|
||||
|
||||
```py
|
||||
flag = True
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class UnionClassGetItem:
|
||||
if flag:
|
||||
if bool_instance():
|
||||
|
||||
def __class_getitem__(cls, item: int) -> str:
|
||||
return item
|
||||
@@ -59,9 +60,10 @@ reveal_type(x[0]) # revealed: str | int
|
||||
## Class getitem with unbound method union
|
||||
|
||||
```py
|
||||
flag = True
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
if bool_instance():
|
||||
class Spam:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
@@ -77,9 +79,10 @@ reveal_type(Spam[42])
|
||||
## TODO: Class getitem non-class union
|
||||
|
||||
```py
|
||||
flag = True
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
if bool_instance():
|
||||
class Eggs:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
|
||||
@@ -30,10 +30,11 @@ reveal_type(Identity()[0]) # revealed: int
|
||||
## Getitem union
|
||||
|
||||
```py
|
||||
flag = True
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class Identity:
|
||||
if flag:
|
||||
if bool_instance():
|
||||
|
||||
def __getitem__(self, index: int) -> int:
|
||||
return index
|
||||
|
||||
@@ -49,14 +49,14 @@ sometimes not:
|
||||
```py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: Literal[True]
|
||||
|
||||
# TODO: While this won't fail at runtime, the user has probably made a mistake
|
||||
# if they're comparing a tuple of length >5 with `sys.version_info`
|
||||
# (`sys.version_info` is a tuple of length 5). It might be worth
|
||||
# emitting a lint diagnostic of some kind warning them about the probable error?
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: Literal[True]
|
||||
|
||||
reveal_type(sys.version_info == (3, 8, 1, "finallllll", 0)) # revealed: Literal[False]
|
||||
```
|
||||
@@ -102,8 +102,8 @@ The fields of `sys.version_info` can be accessed by name:
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info.major >= 3) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 9) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 10) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info.minor >= 13) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 14) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
But the `micro`, `releaselevel` and `serial` fields are inferred as `@Todo` until we support
|
||||
@@ -125,14 +125,14 @@ The fields of `sys.version_info` can be accessed by index or by slice:
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info[0] < 3) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[1] > 9) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[1] > 13) # revealed: Literal[False]
|
||||
|
||||
# revealed: tuple[Literal[3], Literal[9], int, Literal["alpha", "beta", "candidate", "final"], int]
|
||||
# revealed: tuple[Literal[3], Literal[13], int, Literal["alpha", "beta", "candidate", "final"], int]
|
||||
reveal_type(sys.version_info[:5])
|
||||
|
||||
reveal_type(sys.version_info[:2] >= (3, 9)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info[0:2] >= (3, 10)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:3] >= (3, 10, 1)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:2] >= (3, 13)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info[0:2] >= (3, 14)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:3] >= (3, 14, 1)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[3] == "final") # revealed: bool
|
||||
reveal_type(sys.version_info[3] == "finalllllll") # revealed: Literal[False]
|
||||
```
|
||||
|
||||
@@ -39,7 +39,7 @@ impl PythonVersion {
|
||||
|
||||
impl Default for PythonVersion {
|
||||
fn default() -> Self {
|
||||
Self::PY39
|
||||
Self::PY313 // TODO: temporarily changed to 3.13 to activate all sys.version_info branches
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1229,4 +1229,32 @@ match 1:
|
||||
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn if_statement() {
|
||||
let TestCase { db, file } = test_case(
|
||||
"
|
||||
x = False
|
||||
|
||||
if True:
|
||||
x: bool
|
||||
",
|
||||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
// let global_table = index.symbol_table(FileScopeId::global());
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
|
||||
// use_def
|
||||
|
||||
use_def.print(&db);
|
||||
|
||||
assert!(false);
|
||||
// let binding = use_def
|
||||
// .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
// .expect("Expected with item definition for {name}");
|
||||
// assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId,
|
||||
SymbolTableBuilder,
|
||||
};
|
||||
use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::use_def::{ActiveConstraintsSnapshot, FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::unpack::Unpack;
|
||||
use crate::Db;
|
||||
@@ -200,12 +200,20 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.current_use_def_map().snapshot()
|
||||
}
|
||||
|
||||
fn flow_restore(&mut self, state: FlowSnapshot) {
|
||||
self.current_use_def_map_mut().restore(state);
|
||||
fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
|
||||
self.current_use_def_map().constraints_snapshot()
|
||||
}
|
||||
|
||||
fn flow_merge(&mut self, state: FlowSnapshot) {
|
||||
fn flow_restore(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
|
||||
self.current_use_def_map_mut().restore(state);
|
||||
self.current_use_def_map_mut()
|
||||
.restore_constraints(active_constraints);
|
||||
}
|
||||
|
||||
fn flow_merge(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
|
||||
self.current_use_def_map_mut().merge(state);
|
||||
self.current_use_def_map_mut()
|
||||
.restore_constraints(active_constraints);
|
||||
}
|
||||
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
|
||||
@@ -765,37 +773,44 @@ where
|
||||
ast::Stmt::If(node) => {
|
||||
self.visit_expr(&node.test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
let pre_if_constraints = self.constraints_snapshot();
|
||||
let constraint = self.record_expression_constraint(&node.test);
|
||||
let mut constraints = vec![constraint];
|
||||
self.visit_body(&node.body);
|
||||
let mut post_clauses: Vec<FlowSnapshot> = vec![];
|
||||
for clause in &node.elif_else_clauses {
|
||||
let elif_else_clauses = node
|
||||
.elif_else_clauses
|
||||
.iter()
|
||||
.map(|clause| (clause.test.as_ref(), clause.body.as_slice()));
|
||||
let has_else = node
|
||||
.elif_else_clauses
|
||||
.last()
|
||||
.is_some_and(|clause| clause.test.is_none());
|
||||
let elif_else_clauses = elif_else_clauses.chain(if has_else {
|
||||
// if there's an `else` clause already, we don't need to add another
|
||||
None
|
||||
} else {
|
||||
// if there's no `else` branch, we should add a no-op `else` branch
|
||||
Some((None, Default::default()))
|
||||
});
|
||||
for (clause_test, clause_body) in elif_else_clauses {
|
||||
// snapshot after every block except the last; the last one will just become
|
||||
// the state that we merge the other snapshots into
|
||||
post_clauses.push(self.flow_snapshot());
|
||||
// we can only take an elif/else branch if none of the previous ones were
|
||||
// taken, so the block entry state is always `pre_if`
|
||||
self.flow_restore(pre_if.clone());
|
||||
self.flow_restore(pre_if.clone(), pre_if_constraints.clone());
|
||||
for constraint in &constraints {
|
||||
self.record_negated_constraint(*constraint);
|
||||
}
|
||||
if let Some(elif_test) = &clause.test {
|
||||
if let Some(elif_test) = clause_test {
|
||||
self.visit_expr(elif_test);
|
||||
constraints.push(self.record_expression_constraint(elif_test));
|
||||
}
|
||||
self.visit_body(&clause.body);
|
||||
self.visit_body(clause_body);
|
||||
}
|
||||
for post_clause_state in post_clauses {
|
||||
self.flow_merge(post_clause_state);
|
||||
}
|
||||
let has_else = node
|
||||
.elif_else_clauses
|
||||
.last()
|
||||
.is_some_and(|clause| clause.test.is_none());
|
||||
if !has_else {
|
||||
// if there's no else clause, then it's possible we took none of the branches,
|
||||
// and the pre_if state can reach here
|
||||
self.flow_merge(pre_if);
|
||||
self.flow_merge(post_clause_state, pre_if_constraints.clone());
|
||||
}
|
||||
}
|
||||
ast::Stmt::While(ast::StmtWhile {
|
||||
@@ -807,6 +822,7 @@ where
|
||||
self.visit_expr(test);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let pre_loop_constraints = self.constraints_snapshot();
|
||||
|
||||
// Save aside any break states from an outer loop
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
@@ -825,13 +841,13 @@ where
|
||||
|
||||
// We may execute the `else` clause without ever executing the body, so merge in
|
||||
// the pre-loop state before visiting `else`.
|
||||
self.flow_merge(pre_loop);
|
||||
self.flow_merge(pre_loop, pre_loop_constraints.clone());
|
||||
self.visit_body(orelse);
|
||||
|
||||
// Breaking out of a while loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
self.flow_merge(break_state);
|
||||
self.flow_merge(break_state, pre_loop_constraints.clone()); // TODO?
|
||||
}
|
||||
}
|
||||
ast::Stmt::With(ast::StmtWith {
|
||||
@@ -874,6 +890,7 @@ where
|
||||
self.visit_expr(iter);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let pre_loop_constraints = self.constraints_snapshot();
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
|
||||
debug_assert_eq!(&self.current_assignments, &[]);
|
||||
@@ -894,13 +911,13 @@ where
|
||||
|
||||
// We may execute the `else` clause without ever executing the body, so merge in
|
||||
// the pre-loop state before visiting `else`.
|
||||
self.flow_merge(pre_loop);
|
||||
self.flow_merge(pre_loop, pre_loop_constraints.clone());
|
||||
self.visit_body(orelse);
|
||||
|
||||
// Breaking out of a `for` loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
self.flow_merge(break_state);
|
||||
self.flow_merge(break_state, pre_loop_constraints.clone());
|
||||
}
|
||||
}
|
||||
ast::Stmt::Match(ast::StmtMatch {
|
||||
@@ -912,6 +929,7 @@ where
|
||||
self.visit_expr(subject);
|
||||
|
||||
let after_subject = self.flow_snapshot();
|
||||
let after_subject_cs = self.constraints_snapshot();
|
||||
let Some((first, remaining)) = cases.split_first() else {
|
||||
return;
|
||||
};
|
||||
@@ -921,18 +939,18 @@ where
|
||||
let mut post_case_snapshots = vec![];
|
||||
for case in remaining {
|
||||
post_case_snapshots.push(self.flow_snapshot());
|
||||
self.flow_restore(after_subject.clone());
|
||||
self.flow_restore(after_subject.clone(), after_subject_cs.clone());
|
||||
self.add_pattern_constraint(subject, &case.pattern);
|
||||
self.visit_match_case(case);
|
||||
}
|
||||
for post_clause_state in post_case_snapshots {
|
||||
self.flow_merge(post_clause_state);
|
||||
self.flow_merge(post_clause_state, after_subject_cs.clone());
|
||||
}
|
||||
if !cases
|
||||
.last()
|
||||
.is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard())
|
||||
{
|
||||
self.flow_merge(after_subject);
|
||||
self.flow_merge(after_subject, after_subject_cs.clone());
|
||||
}
|
||||
}
|
||||
ast::Stmt::Try(ast::StmtTry {
|
||||
@@ -950,6 +968,7 @@ where
|
||||
// We will merge this state with all of the intermediate
|
||||
// states during the `try` block before visiting those suites.
|
||||
let pre_try_block_state = self.flow_snapshot();
|
||||
let pre_try_block_constraints = self.constraints_snapshot();
|
||||
|
||||
self.try_node_context_stack_manager.push_context();
|
||||
|
||||
@@ -970,14 +989,17 @@ where
|
||||
// as there necessarily must have been 0 `except` blocks executed
|
||||
// if we hit the `else` block.
|
||||
let post_try_block_state = self.flow_snapshot();
|
||||
let post_try_block_constraints = self.constraints_snapshot();
|
||||
|
||||
// Prepare for visiting the `except` block(s)
|
||||
self.flow_restore(pre_try_block_state);
|
||||
self.flow_restore(pre_try_block_state, pre_try_block_constraints.clone());
|
||||
for state in try_block_snapshots {
|
||||
self.flow_merge(state);
|
||||
self.flow_merge(state, pre_try_block_constraints.clone());
|
||||
// TODO?
|
||||
}
|
||||
|
||||
let pre_except_state = self.flow_snapshot();
|
||||
let pre_except_constraints = self.constraints_snapshot();
|
||||
let num_handlers = handlers.len();
|
||||
|
||||
for (i, except_handler) in handlers.iter().enumerate() {
|
||||
@@ -1016,19 +1038,22 @@ where
|
||||
// as we'll immediately call `self.flow_restore()` to a different state
|
||||
// as soon as this loop over the handlers terminates.
|
||||
if i < (num_handlers - 1) {
|
||||
self.flow_restore(pre_except_state.clone());
|
||||
self.flow_restore(
|
||||
pre_except_state.clone(),
|
||||
pre_except_constraints.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If we get to the `else` block, we know that 0 of the `except` blocks can have been executed,
|
||||
// and the entire `try` block must have been executed:
|
||||
self.flow_restore(post_try_block_state);
|
||||
self.flow_restore(post_try_block_state, post_try_block_constraints);
|
||||
}
|
||||
|
||||
self.visit_body(orelse);
|
||||
|
||||
for post_except_state in post_except_states {
|
||||
self.flow_merge(post_except_state);
|
||||
self.flow_merge(post_except_state, pre_try_block_constraints.clone());
|
||||
}
|
||||
|
||||
// TODO: there's lots of complexity here that isn't yet handled by our model.
|
||||
@@ -1185,19 +1210,17 @@ where
|
||||
ast::Expr::If(ast::ExprIf {
|
||||
body, test, orelse, ..
|
||||
}) => {
|
||||
// TODO detect statically known truthy or falsy test (via type inference, not naive
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
self.visit_expr(test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
let pre_if_constraints = self.constraints_snapshot();
|
||||
let constraint = self.record_expression_constraint(test);
|
||||
self.visit_expr(body);
|
||||
let post_body = self.flow_snapshot();
|
||||
self.flow_restore(pre_if);
|
||||
self.flow_restore(pre_if, pre_if_constraints.clone());
|
||||
|
||||
self.record_negated_constraint(constraint);
|
||||
self.visit_expr(orelse);
|
||||
self.flow_merge(post_body);
|
||||
self.flow_merge(post_body, pre_if_constraints);
|
||||
}
|
||||
ast::Expr::ListComp(
|
||||
list_comprehension @ ast::ExprListComp {
|
||||
@@ -1258,7 +1281,7 @@ where
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
let mut snapshots = vec![];
|
||||
|
||||
let pre_op_constraints = self.constraints_snapshot();
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
self.visit_expr(value);
|
||||
// In the last value we don't need to take a snapshot nor add a constraint
|
||||
@@ -1273,7 +1296,7 @@ where
|
||||
}
|
||||
}
|
||||
for snapshot in snapshots {
|
||||
self.flow_merge(snapshot);
|
||||
self.flow_merge(snapshot, pre_op_constraints.clone());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -221,6 +221,8 @@
|
||||
//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it
|
||||
//! visits a `StmtIf` node.
|
||||
use std::collections::HashSet;
|
||||
|
||||
use self::symbol_state::{
|
||||
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
|
||||
ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
@@ -268,6 +270,109 @@ pub(crate) struct UseDefMap<'db> {
|
||||
}
|
||||
|
||||
impl<'db> UseDefMap<'db> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn print(&self, db: &dyn crate::db::Db) {
|
||||
use crate::semantic_index::constraint::ConstraintNode;
|
||||
|
||||
println!("all_definitions:");
|
||||
println!("================");
|
||||
|
||||
for (id, d) in self.all_definitions.iter_enumerated() {
|
||||
println!(
|
||||
"{:?}: {:?} {:?} {:?}",
|
||||
id,
|
||||
d.category(db),
|
||||
d.scope(db),
|
||||
d.symbol(db),
|
||||
);
|
||||
println!(" {:?}", d.kind(db));
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("all_constraints:");
|
||||
println!("================");
|
||||
|
||||
for (id, c) in self.all_constraints.iter_enumerated() {
|
||||
println!("{:?}: {:?}", id, c.node);
|
||||
if let ConstraintNode::Expression(e) = c.node {
|
||||
println!(" {:?}", e.node_ref(db));
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
println!("bindings_by_use:");
|
||||
println!("================");
|
||||
|
||||
for (id, bindings) in self.bindings_by_use.iter_enumerated() {
|
||||
println!("{:?}:", id);
|
||||
for binding in bindings.iter() {
|
||||
let definition = self.all_definitions[binding.definition];
|
||||
let mut constraint_ids = binding.constraint_ids.peekable();
|
||||
let mut active_constraint_ids =
|
||||
binding.constraints_active_at_binding_ids.peekable();
|
||||
|
||||
println!(" * {:?}", definition);
|
||||
|
||||
if constraint_ids.peek().is_some() {
|
||||
println!(" Constraints:");
|
||||
for constraint_id in constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No constraints");
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
if active_constraint_ids.peek().is_some() {
|
||||
println!(" Active constraints at binding:");
|
||||
for constraint_id in active_constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No active constraints at binding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
println!("public_symbols:");
|
||||
println!("================");
|
||||
|
||||
for (id, symbol) in self.public_symbols.iter_enumerated() {
|
||||
println!("{:?}:", id);
|
||||
println!(" * Bindings:");
|
||||
for binding in symbol.bindings().iter() {
|
||||
let definition = self.all_definitions[binding.definition];
|
||||
let mut constraint_ids = binding.constraint_ids.peekable();
|
||||
|
||||
println!(" {:?}", definition);
|
||||
|
||||
if constraint_ids.peek().is_some() {
|
||||
println!(" Constraints:");
|
||||
for constraint_id in constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No constraints");
|
||||
}
|
||||
}
|
||||
|
||||
println!(" * Declarations:");
|
||||
for (declaration, _) in symbol.declarations().iter() {
|
||||
let definition = self.all_definitions[declaration];
|
||||
println!(" {:?}", definition);
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
println!();
|
||||
println!();
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_at_use(
|
||||
&self,
|
||||
use_id: ScopedUseId,
|
||||
@@ -352,6 +457,7 @@ impl<'db> UseDefMap<'db> {
|
||||
) -> DeclarationsIterator<'a, 'db> {
|
||||
DeclarationsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: declarations.iter(),
|
||||
may_be_undeclared: declarations.may_be_undeclared(),
|
||||
}
|
||||
@@ -365,7 +471,7 @@ enum SymbolDefinitions {
|
||||
Declarations(SymbolDeclarations),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
@@ -384,6 +490,10 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraint_ids,
|
||||
},
|
||||
constraints_active_at_binding: ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraints_active_at_binding_ids,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -393,8 +503,10 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
|
||||
pub(crate) struct BindingWithConstraints<'map, 'db> {
|
||||
pub(crate) binding: Definition<'db>,
|
||||
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
|
||||
pub(crate) constraints_active_at_binding: ConstraintsIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ConstraintsIterator<'map, 'db> {
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
constraint_ids: ConstraintIdIterator<'map>,
|
||||
@@ -414,6 +526,7 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DeclarationsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
inner: DeclarationIdIterator<'map>,
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
@@ -425,10 +538,18 @@ impl DeclarationsIterator<'_, '_> {
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> {
|
||||
type Item = Definition<'db>;
|
||||
type Item = (Definition<'db>, ConstraintsIterator<'map, 'db>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|def_id| self.all_definitions[def_id])
|
||||
self.inner.next().map(|(def_id, constraints)| {
|
||||
(
|
||||
self.all_definitions[def_id],
|
||||
ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: constraints,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +561,9 @@ pub(super) struct FlowSnapshot {
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct ActiveConstraintsSnapshot(HashSet<ScopedConstraintId>);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Definition`].
|
||||
@@ -448,6 +572,8 @@ pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Constraint`].
|
||||
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
|
||||
active_constraints: HashSet<ScopedConstraintId>,
|
||||
|
||||
/// Live bindings at each so-far-recorded use.
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
|
||||
@@ -471,7 +597,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
binding,
|
||||
SymbolDefinitions::Declarations(symbol_state.declarations().clone()),
|
||||
);
|
||||
symbol_state.record_binding(def_id);
|
||||
symbol_state.record_binding(def_id, &self.active_constraints);
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
@@ -479,6 +605,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_constraint(constraint_id);
|
||||
}
|
||||
self.active_constraints.insert(constraint_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration(
|
||||
@@ -492,7 +619,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
declaration,
|
||||
SymbolDefinitions::Bindings(symbol_state.bindings().clone()),
|
||||
);
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_declaration(def_id, &self.active_constraints);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration_and_binding(
|
||||
@@ -503,8 +630,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
// We don't need to store anything in self.definitions_by_definition.
|
||||
let def_id = self.all_definitions.push(definition);
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_binding(def_id);
|
||||
symbol_state.record_declaration(def_id, &self.active_constraints);
|
||||
symbol_state.record_binding(def_id, &self.active_constraints);
|
||||
}
|
||||
|
||||
pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
|
||||
@@ -523,6 +650,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
|
||||
ActiveConstraintsSnapshot(self.active_constraints.clone())
|
||||
}
|
||||
|
||||
/// Restore the current builder symbols state to the given snapshot.
|
||||
pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
@@ -541,6 +672,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
.resize(num_symbols, SymbolState::undefined());
|
||||
}
|
||||
|
||||
pub(super) fn restore_constraints(&mut self, snapshot: ActiveConstraintsSnapshot) {
|
||||
self.active_constraints = snapshot.0;
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
/// path to get here. The new state for each symbol should include definitions from both the
|
||||
/// prior state and the snapshot.
|
||||
|
||||
@@ -122,7 +122,7 @@ impl<const B: usize> BitSet<B> {
|
||||
}
|
||||
|
||||
/// Iterator over values in a [`BitSet`].
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct BitSetIterator<'a, const B: usize> {
|
||||
/// The blocks we are iterating over.
|
||||
blocks: &'a [u64],
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
//!
|
||||
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
|
||||
//! similar to tracking live bindings.
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::bitset::{BitSet, BitSetIterator};
|
||||
use ruff_index::newtype_index;
|
||||
use smallvec::SmallVec;
|
||||
@@ -87,6 +89,8 @@ pub(super) struct SymbolDeclarations {
|
||||
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
live_declarations: Declarations,
|
||||
|
||||
constraints_active_at_declaration: Constraints, // TODO: rename to constraints_active_at_declaration
|
||||
|
||||
/// Could the symbol be un-declared at this point?
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
@@ -95,14 +99,27 @@ impl SymbolDeclarations {
|
||||
fn undeclared() -> Self {
|
||||
Self {
|
||||
live_declarations: Declarations::default(),
|
||||
constraints_active_at_declaration: Constraints::default(),
|
||||
may_be_undeclared: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration for this symbol.
|
||||
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
fn record_declaration(
|
||||
&mut self,
|
||||
declaration_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.live_declarations = Declarations::with(declaration_id.into());
|
||||
self.may_be_undeclared = false;
|
||||
|
||||
// TODO: unify code with below
|
||||
self.constraints_active_at_declaration = Constraints::with_capacity(1);
|
||||
self.constraints_active_at_declaration
|
||||
.push(BitSet::default());
|
||||
for active_constraint_id in active_constraints {
|
||||
self.constraints_active_at_declaration[0].insert(active_constraint_id.as_u32());
|
||||
}
|
||||
}
|
||||
|
||||
/// Add undeclared as a possibility for this symbol.
|
||||
@@ -114,6 +131,7 @@ impl SymbolDeclarations {
|
||||
pub(super) fn iter(&self) -> DeclarationIdIterator {
|
||||
DeclarationIdIterator {
|
||||
inner: self.live_declarations.iter(),
|
||||
constraints_active_at_binding: self.constraints_active_at_declaration.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +156,8 @@ pub(super) struct SymbolBindings {
|
||||
/// binding in `live_bindings`.
|
||||
constraints: Constraints,
|
||||
|
||||
constraints_active_at_binding: Constraints,
|
||||
|
||||
/// Could the symbol be unbound at this point?
|
||||
may_be_unbound: bool,
|
||||
}
|
||||
@@ -147,6 +167,7 @@ impl SymbolBindings {
|
||||
Self {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
constraints_active_at_binding: Constraints::default(),
|
||||
may_be_unbound: true,
|
||||
}
|
||||
}
|
||||
@@ -157,12 +178,21 @@ impl SymbolBindings {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
// The new binding replaces all previous live bindings in this path, and has no
|
||||
// constraints.
|
||||
self.live_bindings = Bindings::with(binding_id.into());
|
||||
self.constraints = Constraints::with_capacity(1);
|
||||
self.constraints.push(BitSet::default());
|
||||
self.constraints_active_at_binding = Constraints::with_capacity(1);
|
||||
self.constraints_active_at_binding.push(BitSet::default());
|
||||
for active_constraint_id in active_constraints {
|
||||
self.constraints_active_at_binding[0].insert(active_constraint_id.as_u32());
|
||||
}
|
||||
self.may_be_unbound = false;
|
||||
}
|
||||
|
||||
@@ -178,6 +208,7 @@ impl SymbolBindings {
|
||||
BindingIdWithConstraintsIterator {
|
||||
definitions: self.live_bindings.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
constraints_active_at_binding: self.constraints_active_at_binding.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,8 +238,12 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
self.bindings.record_binding(binding_id);
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.bindings.record_binding(binding_id, active_constraints);
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
@@ -222,8 +257,13 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration of this symbol.
|
||||
pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.declarations.record_declaration(declaration_id);
|
||||
pub(super) fn record_declaration(
|
||||
&mut self,
|
||||
declaration_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.declarations
|
||||
.record_declaration(declaration_id, active_constraints);
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
@@ -232,24 +272,93 @@ impl SymbolState {
|
||||
bindings: SymbolBindings {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
constraints_active_at_binding: Constraints::default(), // TODO
|
||||
may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound,
|
||||
},
|
||||
declarations: SymbolDeclarations {
|
||||
live_declarations: self.declarations.live_declarations.clone(),
|
||||
constraints_active_at_declaration: Constraints::default(), // TODO
|
||||
may_be_undeclared: self.declarations.may_be_undeclared
|
||||
|| b.declarations.may_be_undeclared,
|
||||
},
|
||||
};
|
||||
|
||||
// let mut constraints_active_at_binding = BitSet::default();
|
||||
// for active_constraint_id in active_constraints.0 {
|
||||
// constraints_active_at_binding.insert(active_constraint_id.as_u32());
|
||||
// }
|
||||
|
||||
std::mem::swap(&mut a, self);
|
||||
self.declarations
|
||||
.live_declarations
|
||||
.union(&b.declarations.live_declarations);
|
||||
// self.declarations
|
||||
// .live_declarations
|
||||
// .union(&b.declarations.live_declarations);
|
||||
|
||||
let mut a_decls_iter = a.declarations.live_declarations.iter();
|
||||
let mut b_decls_iter = b.declarations.live_declarations.iter();
|
||||
let mut a_constraints_active_at_declaration_iter =
|
||||
a.declarations.constraints_active_at_declaration.into_iter();
|
||||
let mut b_constraints_active_at_declaration_iter =
|
||||
b.declarations.constraints_active_at_declaration.into_iter();
|
||||
|
||||
let mut opt_a_decl: Option<u32> = a_decls_iter.next();
|
||||
let mut opt_b_decl: Option<u32> = b_decls_iter.next();
|
||||
|
||||
let push = |decl,
|
||||
constraints_active_at_declaration_iter: &mut ConstraintsIntoIterator,
|
||||
merged: &mut Self| {
|
||||
merged.declarations.live_declarations.insert(decl);
|
||||
let constraints_active_at_binding = constraints_active_at_declaration_iter
|
||||
.next()
|
||||
.expect("declarations and constraints_active_at_binding length mismatch");
|
||||
merged
|
||||
.declarations
|
||||
.constraints_active_at_declaration
|
||||
.push(constraints_active_at_binding);
|
||||
};
|
||||
|
||||
loop {
|
||||
match (opt_a_decl, opt_b_decl) {
|
||||
(Some(a_decl), Some(b_decl)) => match a_decl.cmp(&b_decl) {
|
||||
std::cmp::Ordering::Less => {
|
||||
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
push(a_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
self.declarations
|
||||
.constraints_active_at_declaration
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints_active_at_declaration_iter.next().unwrap());
|
||||
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_decl), None) => {
|
||||
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
(None, Some(b_decl)) => {
|
||||
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
|
||||
let mut a_defs_iter = a.bindings.live_bindings.iter();
|
||||
let mut b_defs_iter = b.bindings.live_bindings.iter();
|
||||
let mut a_constraints_iter = a.bindings.constraints.into_iter();
|
||||
let mut b_constraints_iter = b.bindings.constraints.into_iter();
|
||||
let mut a_constraints_active_at_binding_iter =
|
||||
a.bindings.constraints_active_at_binding.into_iter();
|
||||
let mut b_constraints_active_at_binding_iter =
|
||||
b.bindings.constraints_active_at_binding.into_iter();
|
||||
|
||||
let mut opt_a_def: Option<u32> = a_defs_iter.next();
|
||||
let mut opt_b_def: Option<u32> = b_defs_iter.next();
|
||||
@@ -261,7 +370,10 @@ impl SymbolState {
|
||||
// path is irrelevant.
|
||||
|
||||
// Helper to push `def`, with constraints in `constraints_iter`, onto `self`.
|
||||
let push = |def, constraints_iter: &mut ConstraintsIntoIterator, merged: &mut Self| {
|
||||
let push = |def,
|
||||
constraints_iter: &mut ConstraintsIntoIterator,
|
||||
constraints_active_at_binding_iter: &mut ConstraintsIntoIterator,
|
||||
merged: &mut Self| {
|
||||
merged.bindings.live_bindings.insert(def);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and no constraint
|
||||
// bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and
|
||||
@@ -271,7 +383,14 @@ impl SymbolState {
|
||||
let constraints = constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
let constraints_active_at_binding = constraints_active_at_binding_iter
|
||||
.next()
|
||||
.expect("definitions and constraints_active_at_binding length mismatch");
|
||||
merged.bindings.constraints.push(constraints);
|
||||
merged
|
||||
.bindings
|
||||
.constraints_active_at_binding
|
||||
.push(constraints_active_at_binding);
|
||||
};
|
||||
|
||||
loop {
|
||||
@@ -279,17 +398,32 @@ impl SymbolState {
|
||||
(Some(a_def), Some(b_def)) => match a_def.cmp(&b_def) {
|
||||
std::cmp::Ordering::Less => {
|
||||
// Next definition ID is only in `a`, push it to `self` and advance `a`.
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
push(
|
||||
a_def,
|
||||
&mut a_constraints_iter,
|
||||
&mut a_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
// Next definition ID is only in `b`, push it to `self` and advance `b`.
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
push(
|
||||
b_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
// Next definition is in both; push to `self` and intersect constraints.
|
||||
push(a_def, &mut b_constraints_iter, self);
|
||||
push(
|
||||
a_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and
|
||||
// no constraint bitsets (`::unbound`) or one definition and one constraint
|
||||
// bitset (`::with`), and `::merge` always pushes one definition and one
|
||||
@@ -298,6 +432,11 @@ impl SymbolState {
|
||||
let a_constraints = a_constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
// let _a_constraints_active_at_binding =
|
||||
// a_constraints_active_at_binding_iter.next().expect(
|
||||
// "definitions and constraints_active_at_binding length mismatch",
|
||||
// ); // TODO: perform check that we see the same constraints in both paths
|
||||
|
||||
// If the same definition is visible through both paths, any constraint
|
||||
// that applies on only one path is irrelevant to the resulting type from
|
||||
// unioning the two paths, so we intersect the constraints.
|
||||
@@ -306,18 +445,29 @@ impl SymbolState {
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints);
|
||||
|
||||
opt_a_def = a_defs_iter.next();
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_def), None) => {
|
||||
// We've exhausted `b`, just push the def from `a` and move on to the next.
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
push(
|
||||
a_def,
|
||||
&mut a_constraints_iter,
|
||||
&mut a_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
(None, Some(b_def)) => {
|
||||
// We've exhausted `a`, just push the def from `b` and move on to the next.
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
push(
|
||||
b_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
@@ -353,26 +503,37 @@ impl Default for SymbolState {
|
||||
pub(super) struct BindingIdWithConstraints<'a> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'a>,
|
||||
pub(super) constraints_active_at_binding_ids: ConstraintIdIterator<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'a> {
|
||||
definitions: BindingsIterator<'a>,
|
||||
constraints: ConstraintsIterator<'a>,
|
||||
constraints_active_at_binding: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
type Item = BindingIdWithConstraints<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (self.definitions.next(), self.constraints.next()) {
|
||||
(None, None) => None,
|
||||
(Some(def), Some(constraints)) => Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
}),
|
||||
match (
|
||||
self.definitions.next(),
|
||||
self.constraints.next(),
|
||||
self.constraints_active_at_binding.next(),
|
||||
) {
|
||||
(None, None, None) => None,
|
||||
(Some(def), Some(constraints), Some(constraints_active_at_binding)) => {
|
||||
Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
constraints_active_at_binding_ids: ConstraintIdIterator {
|
||||
wrapped: constraints_active_at_binding.iter(),
|
||||
},
|
||||
})
|
||||
}
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("definitions and constraints length mismatch"),
|
||||
}
|
||||
@@ -381,7 +542,7 @@ impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ConstraintIdIterator<'a> {
|
||||
wrapped: BitSetIterator<'a, INLINE_CONSTRAINT_BLOCKS>,
|
||||
}
|
||||
@@ -399,13 +560,25 @@ impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DeclarationIdIterator<'a> {
|
||||
inner: DeclarationsIterator<'a>,
|
||||
constraints_active_at_binding: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DeclarationIdIterator<'a> {
|
||||
type Item = ScopedDefinitionId;
|
||||
type Item = (ScopedDefinitionId, ConstraintIdIterator<'a>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
// self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
match (self.inner.next(), self.constraints_active_at_binding.next()) {
|
||||
(None, None) => None,
|
||||
(Some(declaration), Some(constraints_active_at_binding)) => Some((
|
||||
ScopedDefinitionId::from_u32(declaration),
|
||||
ConstraintIdIterator {
|
||||
wrapped: constraints_active_at_binding.iter(),
|
||||
},
|
||||
)),
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("declarations and constraints_active_at_binding length mismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,7 +586,7 @@ impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
|
||||
use super::{ScopedConstraintId, SymbolState};
|
||||
|
||||
fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
|
||||
assert_eq!(symbol.may_be_unbound(), may_be_unbound);
|
||||
@@ -445,7 +618,7 @@ mod tests {
|
||||
let actual = symbol
|
||||
.declarations()
|
||||
.iter()
|
||||
.map(ScopedDefinitionId::as_u32)
|
||||
.map(|(d, _)| d.as_u32()) // TODO: constraints
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -457,76 +630,76 @@ mod tests {
|
||||
assert_bindings(&sym, true, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// #[test]
|
||||
// fn with() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
|
||||
assert_bindings(&sym, false, &["0<>"]);
|
||||
}
|
||||
// assert_bindings(&sym, false, &["0<>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn set_may_be_unbound() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_unbound();
|
||||
// #[test]
|
||||
// fn set_may_be_unbound() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym.set_may_be_unbound();
|
||||
|
||||
assert_bindings(&sym, true, &["0<>"]);
|
||||
}
|
||||
// assert_bindings(&sym, true, &["0<>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_constraint() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// #[test]
|
||||
// fn record_constraint() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
assert_bindings(&sym, false, &["0<0>"]);
|
||||
}
|
||||
// assert_bindings(&sym, false, &["0<0>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
// merging the same definition with the same constraint keeps the constraint
|
||||
let mut sym0a = SymbolState::undefined();
|
||||
sym0a.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// #[test]
|
||||
// fn merge() {
|
||||
// // merging the same definition with the same constraint keeps the constraint
|
||||
// let mut sym0a = SymbolState::undefined();
|
||||
// sym0a.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym0a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
let mut sym0b = SymbolState::undefined();
|
||||
sym0b.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// let mut sym0b = SymbolState::undefined();
|
||||
// sym0b.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym0b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
sym0a.merge(sym0b);
|
||||
let mut sym0 = sym0a;
|
||||
assert_bindings(&sym0, false, &["0<0>"]);
|
||||
// sym0a.merge(sym0b);
|
||||
// let mut sym0 = sym0a;
|
||||
// assert_bindings(&sym0, false, &["0<0>"]);
|
||||
|
||||
// merging the same definition with differing constraints drops all constraints
|
||||
let mut sym1a = SymbolState::undefined();
|
||||
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
// // merging the same definition with differing constraints drops all constraints
|
||||
// let mut sym1a = SymbolState::undefined();
|
||||
// sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
|
||||
let mut sym1b = SymbolState::undefined();
|
||||
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
// let mut sym1b = SymbolState::undefined();
|
||||
// sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
|
||||
sym1a.merge(sym1b);
|
||||
let sym1 = sym1a;
|
||||
assert_bindings(&sym1, false, &["1<>"]);
|
||||
// sym1a.merge(sym1b);
|
||||
// let sym1 = sym1a;
|
||||
// assert_bindings(&sym1, false, &["1<>"]);
|
||||
|
||||
// merging a constrained definition with unbound keeps both
|
||||
let mut sym2a = SymbolState::undefined();
|
||||
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
sym2a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
// // merging a constrained definition with unbound keeps both
|
||||
// let mut sym2a = SymbolState::undefined();
|
||||
// sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
// sym2a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
|
||||
let sym2b = SymbolState::undefined();
|
||||
// let sym2b = SymbolState::undefined();
|
||||
|
||||
sym2a.merge(sym2b);
|
||||
let sym2 = sym2a;
|
||||
assert_bindings(&sym2, true, &["2<3>"]);
|
||||
// sym2a.merge(sym2b);
|
||||
// let sym2 = sym2a;
|
||||
// assert_bindings(&sym2, true, &["2<3>"]);
|
||||
|
||||
// merging different definitions keeps them each with their existing constraints
|
||||
sym0.merge(sym2);
|
||||
let sym = sym0;
|
||||
assert_bindings(&sym, true, &["0<0>", "2<3>"]);
|
||||
}
|
||||
// // merging different definitions keeps them each with their existing constraints
|
||||
// sym0.merge(sym2);
|
||||
// let sym = sym0;
|
||||
// assert_bindings(&sym, true, &["0<0>", "2<3>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn no_declaration() {
|
||||
@@ -535,54 +708,54 @@ mod tests {
|
||||
assert_declarations(&sym, true, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_declaration() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
assert_declarations(&sym, false, &[1]);
|
||||
}
|
||||
// assert_declarations(&sym, false, &[1]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_override() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
// #[test]
|
||||
// fn record_declaration_override() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
assert_declarations(&sym, false, &[2]);
|
||||
}
|
||||
// assert_declarations(&sym, false, &[2]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration_merge() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let mut sym2 = SymbolState::undefined();
|
||||
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
// let mut sym2 = SymbolState::undefined();
|
||||
// sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
sym.merge(sym2);
|
||||
// sym.merge(sym2);
|
||||
|
||||
assert_declarations(&sym, false, &[1, 2]);
|
||||
}
|
||||
// assert_declarations(&sym, false, &[1, 2]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge_partial_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration_merge_partial_undeclared() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let sym2 = SymbolState::undefined();
|
||||
// let sym2 = SymbolState::undefined();
|
||||
|
||||
sym.merge(sym2);
|
||||
// sym.merge(sym2);
|
||||
|
||||
assert_declarations(&sym, true, &[1]);
|
||||
}
|
||||
// assert_declarations(&sym, true, &[1]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn set_may_be_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_undeclared();
|
||||
// #[test]
|
||||
// fn set_may_be_undeclared() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(0));
|
||||
// sym.set_may_be_undeclared();
|
||||
|
||||
assert_declarations(&sym, true, &[0]);
|
||||
}
|
||||
// assert_declarations(&sym, true, &[0]);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub(crate) use self::infer::{
|
||||
pub(crate) use self::signatures::Signature;
|
||||
use crate::module_resolver::file_to_module;
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::constraint::ConstraintNode;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::{self as symbol, ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::{
|
||||
@@ -222,6 +223,12 @@ fn definition_expression_ty<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum UnconditionallyVisible {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// Infer the combined type of an iterator of bindings.
|
||||
///
|
||||
/// Will return a union if there is more than one binding.
|
||||
@@ -229,29 +236,88 @@ fn bindings_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
let mut def_types = bindings_with_constraints.map(
|
||||
let def_types = bindings_with_constraints.map(
|
||||
|BindingWithConstraints {
|
||||
binding,
|
||||
constraints,
|
||||
constraints_active_at_binding,
|
||||
}| {
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
let test_expr_tys = || {
|
||||
constraints_active_at_binding.clone().map(|c| {
|
||||
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
inference
|
||||
.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
|
||||
} else {
|
||||
// TODO: handle other constraint nodes
|
||||
todo_type!()
|
||||
};
|
||||
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
if constraint_tys.peek().is_some() {
|
||||
constraint_tys
|
||||
.fold(
|
||||
IntersectionBuilder::new(db).add_positive(binding_ty),
|
||||
IntersectionBuilder::add_positive,
|
||||
)
|
||||
.build()
|
||||
(c, ty)
|
||||
})
|
||||
};
|
||||
|
||||
if test_expr_tys().any(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
}
|
||||
}) {
|
||||
// TODO: do we need to call binding_ty(…) even if we don't need the result?
|
||||
(Type::Never, UnconditionallyVisible::No)
|
||||
} else {
|
||||
binding_ty
|
||||
let mut test_expr_tys_iter = test_expr_tys().peekable();
|
||||
|
||||
let unconditionally_visible = if test_expr_tys_iter.peek().is_some()
|
||||
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
}
|
||||
}) {
|
||||
UnconditionallyVisible::Yes
|
||||
} else {
|
||||
UnconditionallyVisible::No
|
||||
};
|
||||
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
if constraint_tys.peek().is_some() {
|
||||
let intersection_ty = constraint_tys
|
||||
.fold(
|
||||
IntersectionBuilder::new(db).add_positive(binding_ty),
|
||||
IntersectionBuilder::add_positive,
|
||||
)
|
||||
.build();
|
||||
(intersection_ty, unconditionally_visible)
|
||||
} else {
|
||||
(binding_ty, unconditionally_visible)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: get rid of all the collects and clean up, obviously
|
||||
let def_types: Vec<_> = def_types.collect();
|
||||
|
||||
// shrink the vector to only include everything from the last unconditionally visible binding
|
||||
let def_types: Vec<_> = def_types
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while_inclusive(|(_, unconditionally_visible)| {
|
||||
*unconditionally_visible != UnconditionallyVisible::Yes
|
||||
})
|
||||
.map(|(ty, _)| *ty)
|
||||
.collect();
|
||||
|
||||
let mut def_types = def_types.into_iter().rev();
|
||||
|
||||
if let Some(first) = def_types.next() {
|
||||
if let Some(second) = def_types.next() {
|
||||
Some(UnionType::from_elements(
|
||||
@@ -287,7 +353,63 @@ fn declarations_ty<'db>(
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
undeclared_ty: Option<Type<'db>>,
|
||||
) -> DeclaredTypeResult<'db> {
|
||||
let decl_types = declarations.map(|declaration| declaration_ty(db, declaration));
|
||||
let decl_types = declarations.map(|(declaration, constraints_active_at_declaration)| {
|
||||
let test_expr_tys = || {
|
||||
constraints_active_at_declaration.clone().map(|c| {
|
||||
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
|
||||
} else {
|
||||
// TODO: handle other constraint nodes
|
||||
todo_type!()
|
||||
};
|
||||
|
||||
(c, ty)
|
||||
})
|
||||
};
|
||||
|
||||
if test_expr_tys().any(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
}
|
||||
}) {
|
||||
(Type::Never, UnconditionallyVisible::No)
|
||||
} else {
|
||||
let mut test_expr_tys_iter = test_expr_tys().peekable();
|
||||
|
||||
if test_expr_tys_iter.peek().is_some()
|
||||
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
}
|
||||
})
|
||||
{
|
||||
(declaration_ty(db, declaration), UnconditionallyVisible::Yes)
|
||||
} else {
|
||||
(declaration_ty(db, declaration), UnconditionallyVisible::No)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: get rid of all the collects and clean up, obviously
|
||||
let decl_types: Vec<_> = decl_types.collect();
|
||||
|
||||
// shrink the vector to only include everything from the last unconditionally visible binding
|
||||
let decl_types: Vec<_> = decl_types
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while_inclusive(|(_, unconditionally_visible)| {
|
||||
*unconditionally_visible != UnconditionallyVisible::Yes
|
||||
})
|
||||
.map(|(ty, _)| *ty)
|
||||
.collect();
|
||||
|
||||
let decl_types = decl_types.into_iter().rev();
|
||||
|
||||
let mut all_types = undeclared_ty.into_iter().chain(decl_types);
|
||||
|
||||
@@ -768,23 +890,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
// TODO: Once we have support for final classes, we can establish that
|
||||
// `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`.
|
||||
|
||||
// TODO: The following is a workaround that is required to unify the two different versions
|
||||
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
|
||||
// we understand `sys.version_info` branches.
|
||||
self == other
|
||||
|| matches!((self, other), (Type::Todo(_), Type::Todo(_)))
|
||||
|| matches!((self, other),
|
||||
(
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::Instance(InstanceType { class: target_class })
|
||||
)
|
||||
if {
|
||||
let self_known = self_class.known(db);
|
||||
matches!(self_known, Some(KnownClass::NoneType | KnownClass::NoDefaultType))
|
||||
&& self_known == target_class.known(db)
|
||||
}
|
||||
)
|
||||
self == other || matches!((self, other), (Type::Todo(_), Type::Todo(_)))
|
||||
}
|
||||
|
||||
/// Return true if this type and `other` have no common elements.
|
||||
@@ -1763,13 +1869,13 @@ impl<'db> KnownClass {
|
||||
}
|
||||
|
||||
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||
core_module_symbol(db, self.canonical_module(), self.as_str())
|
||||
core_module_symbol(db, self.canonical_module(db), self.as_str())
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::Unknown)
|
||||
}
|
||||
|
||||
/// Return the module in which we should look up the definition for this class
|
||||
pub(crate) const fn canonical_module(self) -> CoreStdlibModule {
|
||||
pub(crate) fn canonical_module(self, db: &'db dyn Db) -> CoreStdlibModule {
|
||||
match self {
|
||||
Self::Bool
|
||||
| Self::Object
|
||||
@@ -1787,10 +1893,18 @@ impl<'db> KnownClass {
|
||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types,
|
||||
Self::NoneType => CoreStdlibModule::Typeshed,
|
||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType => CoreStdlibModule::Typing,
|
||||
// TODO when we understand sys.version_info, we will need an explicit fallback here,
|
||||
// because typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
||||
// singleton, but not for `typing._NoDefaultType`
|
||||
Self::NoDefaultType => CoreStdlibModule::TypingExtensions,
|
||||
Self::NoDefaultType => {
|
||||
let python_version = Program::get(db).target_version(db);
|
||||
|
||||
// typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
||||
// singleton, but not for `typing._NoDefaultType`. So we need to switch
|
||||
// to `typing.NoDefault` for newer versions:
|
||||
if python_version.major >= 3 && python_version.minor >= 13 {
|
||||
CoreStdlibModule::Typing
|
||||
} else {
|
||||
CoreStdlibModule::TypingExtensions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1850,11 +1964,11 @@ impl<'db> KnownClass {
|
||||
};
|
||||
|
||||
let module = file_to_module(db, file)?;
|
||||
candidate.check_module(&module).then_some(candidate)
|
||||
candidate.check_module(db, &module).then_some(candidate)
|
||||
}
|
||||
|
||||
/// Return `true` if the module of `self` matches `module_name`
|
||||
fn check_module(self, module: &Module) -> bool {
|
||||
fn check_module(self, db: &dyn Db, module: &Module) -> bool {
|
||||
if !module.search_path().is_standard_library() {
|
||||
return false;
|
||||
}
|
||||
@@ -1874,7 +1988,7 @@ impl<'db> KnownClass {
|
||||
| Self::GenericAlias
|
||||
| Self::ModuleType
|
||||
| Self::VersionInfo
|
||||
| Self::FunctionType => module.name() == self.canonical_module().as_str(),
|
||||
| Self::FunctionType => module.name() == self.canonical_module(db).as_str(),
|
||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
|
||||
matches!(module.name().as_str(), "typing" | "typing_extensions")
|
||||
@@ -2396,6 +2510,14 @@ impl Truthiness {
|
||||
matches!(self, Truthiness::Ambiguous)
|
||||
}
|
||||
|
||||
const fn is_always_false(self) -> bool {
|
||||
matches!(self, Truthiness::AlwaysFalse)
|
||||
}
|
||||
|
||||
const fn is_always_true(self) -> bool {
|
||||
matches!(self, Truthiness::AlwaysTrue)
|
||||
}
|
||||
|
||||
const fn negate(self) -> Self {
|
||||
match self {
|
||||
Self::AlwaysTrue => Self::AlwaysFalse,
|
||||
@@ -3036,7 +3158,7 @@ pub(crate) mod tests {
|
||||
use ruff_python_ast as ast;
|
||||
use test_case::test_case;
|
||||
|
||||
pub(crate) fn setup_db() -> TestDb {
|
||||
pub(crate) fn setup_db_with_python_version(python_version: PythonVersion) -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
@@ -3047,7 +3169,7 @@ pub(crate) mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
target_version: python_version,
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
@@ -3056,6 +3178,10 @@ pub(crate) mod tests {
|
||||
db
|
||||
}
|
||||
|
||||
pub(crate) fn setup_db() -> TestDb {
|
||||
setup_db_with_python_version(PythonVersion::default())
|
||||
}
|
||||
|
||||
/// A test representation of a type that can be transformed unambiguously into a real Type,
|
||||
/// given a db.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -3503,13 +3629,23 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::BooleanLiteral(false))]
|
||||
#[test_case(Ty::KnownClassInstance(KnownClass::NoDefaultType))]
|
||||
fn is_singleton(from: Ty) {
|
||||
let db = setup_db();
|
||||
|
||||
assert!(from.into_type(&db).is_singleton(&db));
|
||||
}
|
||||
|
||||
/// TODO: test documentation
|
||||
#[test_case(PythonVersion::PY312)]
|
||||
#[test_case(PythonVersion::PY313)]
|
||||
fn no_default_type_is_singleton(python_version: PythonVersion) {
|
||||
let db = setup_db_with_python_version(python_version);
|
||||
|
||||
let no_default = Ty::KnownClassInstance(KnownClass::NoDefaultType).into_type(&db);
|
||||
|
||||
assert!(no_default.is_singleton(&db));
|
||||
}
|
||||
|
||||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::IntLiteral(1))]
|
||||
|
||||
@@ -31,7 +31,6 @@ use std::num::NonZeroU32;
|
||||
use itertools::Itertools;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::visitor::{self, Visitor};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, Expr, ExprContext, UnaryOp};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use salsa;
|
||||
@@ -4215,6 +4214,25 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// Annotation expressions also get special handling for `*args` and `**kwargs`.
|
||||
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
|
||||
|
||||
ast::Expr::BytesLiteral(bytes) => {
|
||||
self.diagnostics.add(
|
||||
bytes.into(),
|
||||
"annotation-byte-string",
|
||||
format_args!("Type expressions cannot use bytes literal"),
|
||||
);
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
ast::Expr::FString(fstring) => {
|
||||
self.diagnostics.add(
|
||||
fstring.into(),
|
||||
"annotation-f-string",
|
||||
format_args!("Type expressions cannot use f-strings"),
|
||||
);
|
||||
self.infer_fstring_expression(fstring);
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
// All other annotation expressions are (possibly) valid type expressions, so handle
|
||||
// them there instead.
|
||||
type_expr => self.infer_type_expression_no_store(type_expr),
|
||||
@@ -4225,72 +4243,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
annotation_ty
|
||||
}
|
||||
|
||||
/// Walk child expressions of the given AST node and store the given type for each of them.
|
||||
/// Does not store a type for the root expression. Resets to normal type inference for all
|
||||
/// expressions with a nested scope (lambda, named expression, comprehensions, generators).
|
||||
fn store_type_for_sub_expressions_of(&mut self, expression: &ast::Expr, ty: Type<'db>) {
|
||||
struct StoreTypeVisitor<'a, 'db> {
|
||||
builder: &'a mut TypeInferenceBuilder<'db>,
|
||||
ty: Type<'db>,
|
||||
is_root_node: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'db> StoreTypeVisitor<'a, 'db> {
|
||||
fn new(builder: &'a mut TypeInferenceBuilder<'db>, ty: Type<'db>) -> Self {
|
||||
Self {
|
||||
builder,
|
||||
ty,
|
||||
is_root_node: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn store(&mut self, expr: &ast::Expr) {
|
||||
if self.is_root_node {
|
||||
self.is_root_node = false;
|
||||
} else {
|
||||
self.builder.store_expression_type(expr, self.ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'db, 'ast> Visitor<'ast> for StoreTypeVisitor<'a, 'db> {
|
||||
fn visit_expr(&mut self, expr: &'ast Expr) {
|
||||
match expr {
|
||||
ast::Expr::Lambda(lambda) => {
|
||||
self.builder.infer_lambda_expression(lambda);
|
||||
self.store(expr);
|
||||
}
|
||||
ast::Expr::Named(named) => {
|
||||
self.builder.infer_named_expression(named);
|
||||
self.store(expr);
|
||||
}
|
||||
ast::Expr::ListComp(list_comp) => {
|
||||
self.builder.infer_list_comprehension_expression(list_comp);
|
||||
self.store(expr);
|
||||
}
|
||||
ast::Expr::SetComp(set_comp) => {
|
||||
self.builder.infer_set_comprehension_expression(set_comp);
|
||||
self.store(expr);
|
||||
}
|
||||
ast::Expr::DictComp(dict_comp) => {
|
||||
self.builder.infer_dict_comprehension_expression(dict_comp);
|
||||
self.store(expr);
|
||||
}
|
||||
ast::Expr::Generator(generator) => {
|
||||
self.builder.infer_generator_expression(generator);
|
||||
self.store(expr);
|
||||
}
|
||||
_ => {
|
||||
self.store(expr);
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StoreTypeVisitor::new(self, ty).visit_expr(expression);
|
||||
}
|
||||
|
||||
/// Infer the type of a string annotation expression.
|
||||
fn infer_string_annotation_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> {
|
||||
match parse_string_annotation(self.db, self.file, string) {
|
||||
@@ -4372,6 +4324,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// expressions, but is meaningful in the context of a number of special forms.
|
||||
ast::Expr::EllipsisLiteral(_literal) => todo_type!(),
|
||||
|
||||
// Other literals do not have meaningful values in the annotation expression context.
|
||||
// However, we will we want to handle these differently when working with special forms,
|
||||
// since (e.g.) `123` is not valid in an annotation expression but `Literal[123]` is.
|
||||
ast::Expr::BytesLiteral(_literal) => todo_type!(),
|
||||
ast::Expr::NumberLiteral(_literal) => todo_type!(),
|
||||
ast::Expr::BooleanLiteral(_literal) => todo_type!(),
|
||||
|
||||
ast::Expr::Subscript(subscript) => {
|
||||
let ast::ExprSubscript {
|
||||
value,
|
||||
@@ -4420,62 +4379,90 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// string annotation, as they are not present in the semantic index.
|
||||
_ if self.deferred_state.in_string_annotation() => Type::Unknown,
|
||||
|
||||
// Forms which are invalid in the context of annotation expressions: we store a type of
|
||||
// `Type::Unknown` for these expressions (and their sub-expressions) to avoid problems
|
||||
// with invalid-syntax examples like `x: f"{x}"` or `x: lambda y: x`, for which we can
|
||||
// not infer a meaningful type for the inner `x` expression. The top-level expression
|
||||
// is also `Type::Unknown` in these cases.
|
||||
ast::Expr::BoolOp(_)
|
||||
| ast::Expr::Named(_)
|
||||
| ast::Expr::UnaryOp(_)
|
||||
| ast::Expr::Lambda(_)
|
||||
| ast::Expr::If(_)
|
||||
| ast::Expr::Dict(_)
|
||||
| ast::Expr::Set(_)
|
||||
| ast::Expr::ListComp(_)
|
||||
| ast::Expr::SetComp(_)
|
||||
| ast::Expr::DictComp(_)
|
||||
| ast::Expr::Generator(_)
|
||||
| ast::Expr::Await(_)
|
||||
| ast::Expr::Yield(_)
|
||||
| ast::Expr::YieldFrom(_)
|
||||
| ast::Expr::Compare(_)
|
||||
| ast::Expr::Call(_)
|
||||
| ast::Expr::FString(_)
|
||||
| ast::Expr::List(_)
|
||||
| ast::Expr::Tuple(_)
|
||||
| ast::Expr::Slice(_)
|
||||
| ast::Expr::IpyEscapeCommand(_)
|
||||
| ast::Expr::BytesLiteral(_)
|
||||
| ast::Expr::NumberLiteral(_)
|
||||
| ast::Expr::BooleanLiteral(_) => {
|
||||
match expression {
|
||||
ast::Expr::BytesLiteral(bytes) => {
|
||||
self.diagnostics.add(
|
||||
bytes.into(),
|
||||
"annotation-byte-string",
|
||||
format_args!("Type expressions cannot use bytes literal"),
|
||||
);
|
||||
}
|
||||
ast::Expr::FString(fstring) => {
|
||||
self.diagnostics.add(
|
||||
fstring.into(),
|
||||
"annotation-f-string",
|
||||
format_args!("Type expressions cannot use f-strings"),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
self.diagnostics.add(
|
||||
expression.into(),
|
||||
"annotation-with-invalid-expression",
|
||||
format_args!("Invalid expression in type expression"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.store_type_for_sub_expressions_of(expression, Type::Unknown);
|
||||
// Forms which are invalid in the context of annotation expressions: we infer their
|
||||
// nested expressions as normal expressions, but the type of the top-level expression is
|
||||
// always `Type::Unknown` in these cases.
|
||||
ast::Expr::BoolOp(bool_op) => {
|
||||
self.infer_boolean_expression(bool_op);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Named(named) => {
|
||||
self.infer_named_expression(named);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::UnaryOp(unary) => {
|
||||
self.infer_unary_expression(unary);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Lambda(lambda_expression) => {
|
||||
self.infer_lambda_expression(lambda_expression);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::If(if_expression) => {
|
||||
self.infer_if_expression(if_expression);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Dict(dict) => {
|
||||
self.infer_dict_expression(dict);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Set(set) => {
|
||||
self.infer_set_expression(set);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::ListComp(listcomp) => {
|
||||
self.infer_list_comprehension_expression(listcomp);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::SetComp(setcomp) => {
|
||||
self.infer_set_comprehension_expression(setcomp);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::DictComp(dictcomp) => {
|
||||
self.infer_dict_comprehension_expression(dictcomp);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Generator(generator) => {
|
||||
self.infer_generator_expression(generator);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Await(await_expression) => {
|
||||
self.infer_await_expression(await_expression);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Yield(yield_expression) => {
|
||||
self.infer_yield_expression(yield_expression);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::YieldFrom(yield_from) => {
|
||||
self.infer_yield_from_expression(yield_from);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Compare(compare) => {
|
||||
self.infer_compare_expression(compare);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Call(call_expr) => {
|
||||
self.infer_call_expression(call_expr);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::FString(fstring) => {
|
||||
self.infer_fstring_expression(fstring);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::List(list) => {
|
||||
self.infer_list_expression(list);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Tuple(tuple) => {
|
||||
self.infer_tuple_expression(tuple);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Slice(slice) => {
|
||||
self.infer_slice_expression(slice);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::IpyEscapeCommand(_) => todo!("Implement Ipy escape command support"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5044,7 +5031,7 @@ mod tests {
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::python_version::{self, PythonVersion};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::FileScopeId;
|
||||
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
||||
@@ -5054,10 +5041,11 @@ mod tests {
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use test_case::test_case;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
fn setup_db_with_python_version(python_version: PythonVersion) -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
@@ -5068,7 +5056,7 @@ mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
target_version: python_version,
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
@@ -5077,6 +5065,10 @@ mod tests {
|
||||
db
|
||||
}
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
setup_db_with_python_version(PythonVersion::default())
|
||||
}
|
||||
|
||||
fn setup_db_with_custom_typeshed<'a>(
|
||||
typeshed: &str,
|
||||
files: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||
@@ -5348,9 +5340,10 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ellipsis_type() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
#[test_case(PythonVersion::PY39, "ellipsis")]
|
||||
#[test_case(PythonVersion::PY310, "EllipsisType")]
|
||||
fn ellipsis_type(version: PythonVersion, expected_type: &str) -> anyhow::Result<()> {
|
||||
let mut db = setup_db_with_python_version(version);
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
@@ -5359,8 +5352,7 @@ mod tests {
|
||||
",
|
||||
)?;
|
||||
|
||||
// TODO: sys.version_info
|
||||
assert_public_ty(&db, "src/a.py", "x", "EllipsisType | ellipsis");
|
||||
assert_public_ty(&db, "src/a.py", "x", expected_type);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -185,11 +185,11 @@ impl Settings {
|
||||
pub enum TargetVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
#[default]
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
#[default]
|
||||
Py313,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: "&workspace"
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -37,7 +36,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -50,7 +49,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -272,4 +272,7 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[
|
||||
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false),
|
||||
// related to circular references in type aliases (salsa cycle panic):
|
||||
("crates/ruff_python_parser/resources/inline/err/type_alias_invalid_value_expr.py", true, true),
|
||||
// related to circular references in f-string annotations (invalid syntax)
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true),
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -27,17 +27,13 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
// We don't support `*` imports yet:
|
||||
"error[unresolved-import] /src/tomllib/_parser.py:7:29 Module `collections.abc` has no member `Iterable`",
|
||||
// We don't support terminal statements in control flow yet:
|
||||
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:57:71 Invalid expression in type expression",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:66:18 Name `s` used when possibly not defined",
|
||||
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:69:66 Invalid expression in type expression",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
|
||||
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:145:27 Invalid expression in type expression",
|
||||
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:196:25 Invalid expression in type expression",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
|
||||
|
||||
@@ -2,7 +2,7 @@ pub use diagnostic::{Diagnostic, DiagnosticKind};
|
||||
pub use edit::Edit;
|
||||
pub use fix::{Applicability, Fix, IsolationLevel};
|
||||
pub use source_map::{SourceMap, SourceMarker};
|
||||
pub use violation::{AlwaysFixableViolation, FixAvailability, Violation};
|
||||
pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata};
|
||||
|
||||
mod diagnostic;
|
||||
mod edit;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::DiagnosticKind;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -17,7 +18,16 @@ impl Display for FixAvailability {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Violation: Debug + PartialEq + Eq {
|
||||
pub trait ViolationMetadata {
|
||||
/// Returns the rule name of this violation
|
||||
fn rule_name() -> &'static str;
|
||||
|
||||
/// Returns an explanation of what this violation catches,
|
||||
/// why it's bad, and what users should do instead.
|
||||
fn explain() -> Option<&'static str>;
|
||||
}
|
||||
|
||||
pub trait Violation: ViolationMetadata {
|
||||
/// `None` in the case a fix is never available or otherwise Some
|
||||
/// [`FixAvailability`] describing the available fix.
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
|
||||
@@ -41,7 +51,7 @@ pub trait Violation: Debug + PartialEq + Eq {
|
||||
|
||||
/// This trait exists just to make implementing the [`Violation`] trait more
|
||||
/// convenient for violations that can always be fixed.
|
||||
pub trait AlwaysFixableViolation: Debug + PartialEq + Eq {
|
||||
pub trait AlwaysFixableViolation: ViolationMetadata {
|
||||
/// The message used to describe the violation.
|
||||
fn message(&self) -> String;
|
||||
|
||||
@@ -69,3 +79,16 @@ impl<V: AlwaysFixableViolation> Violation for V {
|
||||
<Self as AlwaysFixableViolation>::message_formats()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for DiagnosticKind
|
||||
where
|
||||
T: Violation,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
body: Violation::message(&value),
|
||||
suggestion: Violation::fix_title(&value),
|
||||
name: T::rule_name().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -60,3 +60,23 @@ def f():
|
||||
| None,
|
||||
3.0
|
||||
)
|
||||
|
||||
|
||||
def f():
|
||||
# Regression test for #14554
|
||||
import typing
|
||||
typing.cast(M-())
|
||||
|
||||
|
||||
def f():
|
||||
# Simple case with Literal that should lead to nested quotes
|
||||
from typing import cast, Literal
|
||||
|
||||
cast(Literal["A"], 'A')
|
||||
|
||||
|
||||
def f():
|
||||
# Really complex case with nested forward references
|
||||
from typing import cast, Annotated, Literal
|
||||
|
||||
cast(list[Annotated["list['Literal[\"A\"]']", "Foo"]], ['A'])
|
||||
|
||||
31
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC007.py
vendored
Normal file
31
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC007.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, TypeAlias, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Dict
|
||||
|
||||
from foo import Foo
|
||||
|
||||
OptStr: TypeAlias = str | None
|
||||
Bar: TypeAlias = Foo[int]
|
||||
|
||||
a: TypeAlias = int # OK
|
||||
b: TypeAlias = Dict # OK
|
||||
c: TypeAlias = Foo # TC007
|
||||
d: TypeAlias = Foo | None # TC007
|
||||
e: TypeAlias = OptStr # TC007
|
||||
f: TypeAlias = Bar # TC007
|
||||
g: TypeAlias = Foo | Bar # TC007 x2
|
||||
h: TypeAlias = Foo[str] # TC007
|
||||
i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||
Bar)
|
||||
|
||||
type C = Foo # OK
|
||||
type D = Foo | None # OK
|
||||
type E = OptStr # OK
|
||||
type F = Bar # OK
|
||||
type G = Foo | Bar # OK
|
||||
type H = Foo[str] # OK
|
||||
type I = (Foo | # OK
|
||||
Bar)
|
||||
52
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py
vendored
Normal file
52
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypeAlias, TYPE_CHECKING
|
||||
|
||||
from foo import Foo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Dict
|
||||
|
||||
OptStr: TypeAlias = str | None
|
||||
Bar: TypeAlias = Foo[int]
|
||||
else:
|
||||
Bar = Foo
|
||||
|
||||
a: TypeAlias = 'int' # TC008
|
||||
b: TypeAlias = 'Dict' # OK
|
||||
c: TypeAlias = 'Foo' # TC008
|
||||
d: TypeAlias = 'Foo[str]' # OK
|
||||
e: TypeAlias = 'Foo.bar' # OK
|
||||
f: TypeAlias = 'Foo | None' # TC008
|
||||
g: TypeAlias = 'OptStr' # OK
|
||||
h: TypeAlias = 'Bar' # TC008
|
||||
i: TypeAlias = Foo['str'] # TC008
|
||||
j: TypeAlias = 'Baz' # OK
|
||||
k: TypeAlias = 'k | None' # OK
|
||||
l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||
m: TypeAlias = ('int' # TC008
|
||||
| None)
|
||||
n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||
' | None')
|
||||
|
||||
type B = 'Dict' # TC008
|
||||
type D = 'Foo[str]' # TC008
|
||||
type E = 'Foo.bar' # TC008
|
||||
type G = 'OptStr' # TC008
|
||||
type I = Foo['str'] # TC008
|
||||
type J = 'Baz' # TC008
|
||||
type K = 'K | None' # TC008
|
||||
type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||
type M = ('int' # TC008
|
||||
| None)
|
||||
type N = ('int' # TC008 (fix removes comment currently)
|
||||
' | None')
|
||||
|
||||
|
||||
class Baz:
|
||||
a: TypeAlias = 'Baz' # OK
|
||||
type A = 'Baz' # TC008
|
||||
|
||||
class Nested:
|
||||
a: TypeAlias = 'Baz' # OK
|
||||
type A = 'Baz' # TC008
|
||||
@@ -101,3 +101,12 @@ def f():
|
||||
|
||||
def test_annotated_non_typing_reference(user: Annotated[str, Depends(get_foo)]):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
from typing import TypeAlias, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import DataFrame
|
||||
|
||||
x: TypeAlias = DataFrame | None
|
||||
|
||||
@@ -58,7 +58,7 @@ def f():
|
||||
from typing import Literal
|
||||
from third_party import Type
|
||||
|
||||
def test_string_contains_opposite_quote_do_not_fix(self, type1: Type[Literal["'"]], type2: Type[Literal["\'"]]):
|
||||
def test_string_contains_opposite_quote(self, type1: Type[Literal["'"]], type2: Type[Literal["\'"]]):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
23
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH208.py
vendored
Normal file
23
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH208.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
os.listdir('.')
|
||||
os.listdir(b'.')
|
||||
|
||||
string_path = '.'
|
||||
os.listdir(string_path)
|
||||
|
||||
bytes_path = b'.'
|
||||
os.listdir(bytes_path)
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
path_path = Path('.')
|
||||
os.listdir(path_path)
|
||||
|
||||
|
||||
if os.listdir("dir"):
|
||||
...
|
||||
|
||||
if "file" in os.listdir("dir"):
|
||||
...
|
||||
@@ -1,3 +1,9 @@
|
||||
import mod.CONST as const
|
||||
from mod import CONSTANT as constant
|
||||
from mod import ANOTHER_CONSTANT as another_constant
|
||||
import mod.CON as c
|
||||
from mod import C as c
|
||||
|
||||
# These are all OK:
|
||||
import django.db.models.Q as Query1
|
||||
from django.db.models import Q as Query2
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import mod.Camel as CAMEL
|
||||
from mod import CamelCase as CAMELCASE
|
||||
from mod import AnotherCamelCase as ANOTHER_CAMELCASE
|
||||
|
||||
# These are all OK:
|
||||
import mod.AppleFruit as A
|
||||
from mod import BananaFruit as B
|
||||
|
||||
@@ -93,3 +93,53 @@ op_itemgetter = lambda x: x[1, :]
|
||||
|
||||
# Without a slice, trivia is retained
|
||||
op_itemgetter = lambda x: x[1, 2]
|
||||
|
||||
|
||||
# All methods in classes are ignored, even those defined using lambdas:
|
||||
class Foo:
|
||||
def x(self, other):
|
||||
return self == other
|
||||
|
||||
class Bar:
|
||||
y = lambda self, other: self == other
|
||||
|
||||
from typing import Callable
|
||||
class Baz:
|
||||
z: Callable = lambda self, other: self == other
|
||||
|
||||
|
||||
# Lambdas wrapped in function calls could also still be method definitions!
|
||||
# To avoid false positives, we shouldn't flag any of these either:
|
||||
from typing import final, override, no_type_check
|
||||
|
||||
|
||||
class Foo:
|
||||
a = final(lambda self, other: self == other)
|
||||
b = override(lambda self, other: self == other)
|
||||
c = no_type_check(lambda self, other: self == other)
|
||||
d = final(override(no_type_check(lambda self, other: self == other)))
|
||||
|
||||
|
||||
# lambdas used in decorators do not constitute method definitions,
|
||||
# so these *should* be flagged:
|
||||
class TheLambdasHereAreNotMethods:
|
||||
@pytest.mark.parametrize(
|
||||
"slicer, expected",
|
||||
[
|
||||
(lambda x: x[-2:], "foo"),
|
||||
(lambda x: x[-5:-3], "bar"),
|
||||
],
|
||||
)
|
||||
def test_inlet_asset_alias_extra_slice(self, slicer, expected):
|
||||
assert slice("whatever") == expected
|
||||
|
||||
|
||||
class NotAMethodButHardToDetect:
|
||||
# In an ideal world, perhaps we'd emit a diagnostic here,
|
||||
# since this `lambda` is clearly not a method definition,
|
||||
# and *could* be safely replaced with an `operator` function.
|
||||
# Practically speaking, however, it's hard to see how we'd accurately determine
|
||||
# that the `lambda` is *not* a method definition
|
||||
# without risking false positives elsewhere or introducing complex heuristics
|
||||
# that users would find surprising and confusing
|
||||
FOO = sorted([x for x in BAR], key=lambda x: x.baz)
|
||||
|
||||
31
crates/ruff_linter/resources/test/fixtures/ruff/RUF041.py
vendored
Normal file
31
crates/ruff_linter/resources/test/fixtures/ruff/RUF041.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import Literal
|
||||
import typing as t
|
||||
import typing_extensions
|
||||
|
||||
|
||||
y: Literal[1, print("hello"), 3, Literal[4, 1]]
|
||||
Literal[1, Literal[1]]
|
||||
Literal[1, 2, Literal[1, 2]]
|
||||
Literal[1, Literal[1], Literal[1]]
|
||||
Literal[1, Literal[2], Literal[2]]
|
||||
t.Literal[1, t.Literal[2, t.Literal[1]]]
|
||||
Literal[
|
||||
1, # comment 1
|
||||
Literal[ # another comment
|
||||
1 # yet annother comment
|
||||
]
|
||||
] # once
|
||||
|
||||
# Ensure issue is only raised once, even on nested literals
|
||||
MyType = Literal["foo", Literal[True, False, True], "bar"]
|
||||
|
||||
# nested literals, all equivalent to `Literal[1]`
|
||||
Literal[Literal[1]]
|
||||
Literal[Literal[Literal[1], Literal[1]]]
|
||||
Literal[Literal[1], Literal[Literal[Literal[1]]]]
|
||||
|
||||
# OK
|
||||
x: Literal[True, False, True, False]
|
||||
z: Literal[{1, 3, 5}, "foobar", {1,3,5}]
|
||||
typing_extensions.Literal[1, 1, 1]
|
||||
n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
31
crates/ruff_linter/resources/test/fixtures/ruff/RUF041.pyi
vendored
Normal file
31
crates/ruff_linter/resources/test/fixtures/ruff/RUF041.pyi
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import Literal
|
||||
import typing as t
|
||||
import typing_extensions
|
||||
|
||||
|
||||
y: Literal[1, print("hello"), 3, Literal[4, 1]]
|
||||
Literal[1, Literal[1]]
|
||||
Literal[1, 2, Literal[1, 2]]
|
||||
Literal[1, Literal[1], Literal[1]]
|
||||
Literal[1, Literal[2], Literal[2]]
|
||||
t.Literal[1, t.Literal[2, t.Literal[1]]]
|
||||
Literal[
|
||||
1, # comment 1
|
||||
Literal[ # another comment
|
||||
1 # yet annother comment
|
||||
]
|
||||
] # once
|
||||
|
||||
# Ensure issue is only raised once, even on nested literals
|
||||
MyType = Literal["foo", Literal[True, False, True], "bar"]
|
||||
|
||||
# nested literals, all equivalent to `Literal[1]`
|
||||
Literal[Literal[1]]
|
||||
Literal[Literal[Literal[1], Literal[1]]]
|
||||
Literal[Literal[1], Literal[Literal[Literal[1]]]]
|
||||
|
||||
# OK
|
||||
x: Literal[True, False, True, False]
|
||||
z: Literal[{1, 3, 5}, "foobar", {1,3,5}]
|
||||
typing_extensions.Literal[1, 1, 1]
|
||||
n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
13
crates/ruff_linter/resources/test/fixtures/ruff/RUF101_1.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/ruff/RUF101_1.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Regression test for #14531.
|
||||
|
||||
RUF101 should trigger here because the TCH rules have been recoded to TC.
|
||||
"""
|
||||
# ruff: noqa: TCH002
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import local_module
|
||||
|
||||
|
||||
def func(sized: local_module.Container) -> int:
|
||||
return len(sized)
|
||||
@@ -3,7 +3,9 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_import_conventions, flake8_pyi, pyflakes, pylint, ruff};
|
||||
use crate::rules::{
|
||||
flake8_import_conventions, flake8_pyi, flake8_type_checking, pyflakes, pylint, ruff,
|
||||
};
|
||||
|
||||
/// Run lint rules over the [`Binding`]s.
|
||||
pub(crate) fn bindings(checker: &mut Checker) {
|
||||
@@ -15,6 +17,7 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
Rule::UnconventionalImportAlias,
|
||||
Rule::UnsortedDunderSlots,
|
||||
Rule::UnusedVariable,
|
||||
Rule::UnquotedTypeAlias,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
@@ -72,6 +75,13 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnquotedTypeAlias) {
|
||||
if let Some(diagnostics) =
|
||||
flake8_type_checking::rules::unquoted_type_alias(checker, binding)
|
||||
{
|
||||
checker.diagnostics.extend(diagnostics);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnsortedDunderSlots) {
|
||||
if let Some(diagnostic) = ruff::rules::sort_dunder_slots(checker, binding) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_python_ast::Expr;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_builtins, flake8_pie, pylint, refurb};
|
||||
use crate::rules::{flake8_builtins, flake8_pie, pylint};
|
||||
|
||||
/// Run lint rules over all deferred lambdas in the [`SemanticModel`].
|
||||
pub(crate) fn deferred_lambdas(checker: &mut Checker) {
|
||||
@@ -21,9 +21,6 @@ pub(crate) fn deferred_lambdas(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ReimplementedContainerBuiltin) {
|
||||
flake8_pie::rules::reimplemented_container_builtin(checker, lambda);
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedOperator) {
|
||||
refurb::rules::reimplemented_operator(checker, &lambda.into());
|
||||
}
|
||||
if checker.enabled(Rule::BuiltinLambdaArgumentShadowing) {
|
||||
flake8_builtins::rules::builtin_lambda_argument_shadowing(checker, lambda);
|
||||
}
|
||||
|
||||
@@ -52,14 +52,13 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// Identify any valid runtime imports. If a module is imported at runtime, and
|
||||
// used at runtime, then by default, we avoid flagging any other
|
||||
// imports from that model as typing-only.
|
||||
let enforce_typing_imports = !checker.source_type.is_stub()
|
||||
let enforce_typing_only_imports = !checker.source_type.is_stub()
|
||||
&& checker.any_enabled(&[
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
]);
|
||||
let runtime_imports: Vec<Vec<&Binding>> = if enforce_typing_imports {
|
||||
let runtime_imports: Vec<Vec<&Binding>> = if enforce_typing_only_imports {
|
||||
checker
|
||||
.semantic
|
||||
.scopes
|
||||
@@ -375,7 +374,16 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
|
||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
||||
if enforce_typing_imports {
|
||||
if !checker.source_type.is_stub()
|
||||
&& checker.enabled(Rule::RuntimeImportInTypeCheckingBlock)
|
||||
{
|
||||
flake8_type_checking::rules::runtime_import_in_type_checking_block(
|
||||
checker,
|
||||
scope,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
if enforce_typing_only_imports {
|
||||
let runtime_imports: Vec<&Binding> = checker
|
||||
.semantic
|
||||
.scopes
|
||||
@@ -384,26 +392,12 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
if checker.enabled(Rule::RuntimeImportInTypeCheckingBlock) {
|
||||
flake8_type_checking::rules::runtime_import_in_type_checking_block(
|
||||
checker,
|
||||
scope,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
|
||||
if checker.any_enabled(&[
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
]) {
|
||||
flake8_type_checking::rules::typing_only_runtime_import(
|
||||
checker,
|
||||
scope,
|
||||
&runtime_imports,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
flake8_type_checking::rules::typing_only_runtime_import(
|
||||
checker,
|
||||
scope,
|
||||
&runtime_imports,
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::UnusedImport) {
|
||||
|
||||
@@ -108,6 +108,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::DuplicateLiteralMember,
|
||||
Rule::RedundantBoolLiteral,
|
||||
Rule::RedundantNoneLiteral,
|
||||
Rule::UnnecessaryNestedLiteral,
|
||||
]) {
|
||||
if !checker.semantic.in_nested_literal() {
|
||||
if checker.enabled(Rule::DuplicateLiteralMember) {
|
||||
@@ -119,6 +120,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::RedundantNoneLiteral) {
|
||||
flake8_pyi::rules::redundant_none_literal(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryNestedLiteral) {
|
||||
ruff::rules::unnecessary_nested_literal(checker, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -965,6 +969,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::OsPathGetmtime,
|
||||
Rule::OsPathGetctime,
|
||||
Rule::Glob,
|
||||
Rule::OsListdir,
|
||||
]) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
@@ -1645,6 +1650,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
ruff::rules::assignment_in_assert(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::Lambda(lambda_expr) => {
|
||||
if checker.enabled(Rule::ReimplementedOperator) {
|
||||
refurb::rules::reimplemented_operator(checker, &lambda_expr.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -888,9 +888,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
if let Some(type_params) = type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
self.visit
|
||||
.type_param_definitions
|
||||
.push((value, self.semantic.snapshot()));
|
||||
self.visit_deferred_type_alias_value(value);
|
||||
self.semantic.pop_scope();
|
||||
self.visit_expr(name);
|
||||
}
|
||||
@@ -961,7 +959,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
|
||||
if let Some(expr) = value {
|
||||
if self.semantic.match_typing_expr(annotation, "TypeAlias") {
|
||||
self.visit_type_definition(expr);
|
||||
self.visit_annotated_type_alias_value(expr);
|
||||
} else {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
@@ -1855,6 +1853,45 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.flags = snapshot;
|
||||
}
|
||||
|
||||
/// Visit an [`Expr`], and treat it as the value expression
|
||||
/// of a [PEP 613] type alias.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// OptStr: TypeAlias = str | None # We're visiting the RHS
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||
fn visit_annotated_type_alias_value(&mut self, expr: &'a Expr) {
|
||||
let snapshot = self.semantic.flags;
|
||||
self.semantic.flags |= SemanticModelFlags::ANNOTATED_TYPE_ALIAS;
|
||||
self.visit_type_definition(expr);
|
||||
self.semantic.flags = snapshot;
|
||||
}
|
||||
|
||||
/// Visit an [`Expr`], and treat it as the value expression
|
||||
/// of a [PEP 695] type alias.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// type OptStr = str | None # We're visiting the RHS
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 695]: https://peps.python.org/pep-0695/#generic-type-alias
|
||||
fn visit_deferred_type_alias_value(&mut self, expr: &'a Expr) {
|
||||
let snapshot = self.semantic.flags;
|
||||
// even though we don't visit these nodes immediately we need to
|
||||
// modify the semantic flags before we push the expression and its
|
||||
// corresponding semantic snapshot
|
||||
self.semantic.flags |= SemanticModelFlags::DEFERRED_TYPE_ALIAS;
|
||||
self.visit
|
||||
.type_param_definitions
|
||||
.push((expr, self.semantic.snapshot()));
|
||||
self.semantic.flags = snapshot;
|
||||
}
|
||||
|
||||
/// Visit an [`Expr`], and treat it as a type definition.
|
||||
fn visit_type_definition(&mut self, expr: &'a Expr) {
|
||||
if self.semantic.in_no_type_check() {
|
||||
@@ -2017,6 +2054,21 @@ impl<'a> Checker<'a> {
|
||||
flags.insert(BindingFlags::UNPACKED_ASSIGNMENT);
|
||||
}
|
||||
|
||||
match parent {
|
||||
Stmt::TypeAlias(_) => flags.insert(BindingFlags::DEFERRED_TYPE_ALIAS),
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { annotation, .. }) => {
|
||||
// TODO: It is a bit unfortunate that we do this check twice
|
||||
// maybe we should change how we visit this statement
|
||||
// so the semantic flag for the type alias sticks around
|
||||
// until after we've handled this store, so we can check
|
||||
// the flag instead of duplicating this check
|
||||
if self.semantic.match_typing_expr(annotation, "TypeAlias") {
|
||||
flags.insert(BindingFlags::ANNOTATED_TYPE_ALIAS);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let scope = self.semantic.current_scope();
|
||||
|
||||
if scope.kind.is_module()
|
||||
@@ -2272,7 +2324,17 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.flags |=
|
||||
SemanticModelFlags::TYPE_DEFINITION | type_definition_flag;
|
||||
self.visit_expr(parsed_annotation.expression());
|
||||
let parsed_expr = parsed_annotation.expression();
|
||||
self.visit_expr(parsed_expr);
|
||||
if self.semantic.in_type_alias_value() {
|
||||
if self.enabled(Rule::QuotedTypeAlias) {
|
||||
flake8_type_checking::rules::quoted_type_alias(
|
||||
self,
|
||||
parsed_expr,
|
||||
string_expr,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.parsed_type_annotation = None;
|
||||
} else {
|
||||
if self.enabled(Rule::ForwardAnnotationSyntaxError) {
|
||||
|
||||
@@ -211,6 +211,7 @@ pub(crate) fn check_noqa(
|
||||
&& !exemption.includes(Rule::RedirectedNOQA)
|
||||
{
|
||||
ruff::rules::redirected_noqa(diagnostics, &noqa_directives);
|
||||
ruff::rules::redirected_file_noqa(diagnostics, &file_noqa_directives);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::BlanketNOQA)
|
||||
|
||||
@@ -858,6 +858,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8TypeChecking, "004") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
|
||||
(Flake8TypeChecking, "005") => (RuleGroup::Stable, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
|
||||
(Flake8TypeChecking, "006") => (RuleGroup::Preview, rules::flake8_type_checking::rules::RuntimeCastValue),
|
||||
(Flake8TypeChecking, "007") => (RuleGroup::Preview, rules::flake8_type_checking::rules::UnquotedTypeAlias),
|
||||
(Flake8TypeChecking, "008") => (RuleGroup::Preview, rules::flake8_type_checking::rules::QuotedTypeAlias),
|
||||
(Flake8TypeChecking, "010") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeStringUnion),
|
||||
|
||||
// tryceratops
|
||||
@@ -906,6 +908,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8UsePathlib, "205") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathGetctime),
|
||||
(Flake8UsePathlib, "206") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsSepSplit),
|
||||
(Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob),
|
||||
(Flake8UsePathlib, "208") => (RuleGroup::Preview, rules::flake8_use_pathlib::violations::OsListdir),
|
||||
|
||||
// flake8-logging-format
|
||||
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),
|
||||
@@ -977,9 +980,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "035") => (RuleGroup::Preview, rules::ruff::rules::UnsafeMarkupUse),
|
||||
(Ruff, "036") => (RuleGroup::Preview, rules::ruff::rules::NoneNotAtEndOfUnion),
|
||||
(Ruff, "038") => (RuleGroup::Preview, rules::ruff::rules::RedundantBoolLiteral),
|
||||
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
|
||||
(Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern),
|
||||
(Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
|
||||
(Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral),
|
||||
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption<'a> {
|
||||
if directives
|
||||
.lines()
|
||||
.iter()
|
||||
.any(|line| ParsedFileExemption::All == line.parsed_file_exemption)
|
||||
.any(|line| matches!(line.parsed_file_exemption, ParsedFileExemption::All))
|
||||
{
|
||||
FileExemption::All(codes)
|
||||
} else {
|
||||
@@ -362,7 +362,7 @@ impl<'a> FileNoqaDirectives<'a> {
|
||||
let mut lines = vec![];
|
||||
|
||||
for range in comment_ranges {
|
||||
match ParsedFileExemption::try_extract(&locator.contents()[range]) {
|
||||
match ParsedFileExemption::try_extract(range, locator.contents()) {
|
||||
Err(err) => {
|
||||
#[allow(deprecated)]
|
||||
let line = locator.compute_line_index(range.start());
|
||||
@@ -384,6 +384,7 @@ impl<'a> FileNoqaDirectives<'a> {
|
||||
}
|
||||
ParsedFileExemption::Codes(codes) => {
|
||||
codes.iter().filter_map(|code| {
|
||||
let code = code.as_str();
|
||||
// Ignore externally-defined rules.
|
||||
if external.iter().any(|external| code.starts_with(external)) {
|
||||
return None;
|
||||
@@ -424,21 +425,26 @@ impl<'a> FileNoqaDirectives<'a> {
|
||||
/// An individual file-level exemption (e.g., `# ruff: noqa` or `# ruff: noqa: F401, F841`). Like
|
||||
/// [`FileNoqaDirectives`], but only for a single line, as opposed to an aggregated set of exemptions
|
||||
/// across a source file.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ParsedFileExemption<'a> {
|
||||
/// The file-level exemption ignores all rules (e.g., `# ruff: noqa`).
|
||||
All,
|
||||
/// The file-level exemption ignores specific rules (e.g., `# ruff: noqa: F401, F841`).
|
||||
Codes(Vec<&'a str>),
|
||||
Codes(Codes<'a>),
|
||||
}
|
||||
|
||||
impl<'a> ParsedFileExemption<'a> {
|
||||
/// Return a [`ParsedFileExemption`] for a given comment line.
|
||||
fn try_extract(line: &'a str) -> Result<Option<Self>, ParseError> {
|
||||
/// Return a [`ParsedFileExemption`] for a given `comment_range` in `source`.
|
||||
fn try_extract(comment_range: TextRange, source: &'a str) -> Result<Option<Self>, ParseError> {
|
||||
let line = &source[comment_range];
|
||||
let offset = comment_range.start();
|
||||
let init_line_len = line.text_len();
|
||||
|
||||
let line = Self::lex_whitespace(line);
|
||||
let Some(line) = Self::lex_char(line, '#') else {
|
||||
return Ok(None);
|
||||
};
|
||||
let comment_start = init_line_len - line.text_len() - '#'.text_len();
|
||||
let line = Self::lex_whitespace(line);
|
||||
|
||||
let Some(line) = Self::lex_flake8(line).or_else(|| Self::lex_ruff(line)) else {
|
||||
@@ -469,7 +475,11 @@ impl<'a> ParsedFileExemption<'a> {
|
||||
let mut codes = vec![];
|
||||
let mut line = line;
|
||||
while let Some(code) = Self::lex_code(line) {
|
||||
codes.push(code);
|
||||
let codes_end = init_line_len - line.text_len();
|
||||
codes.push(Code {
|
||||
code,
|
||||
range: TextRange::at(codes_end, code.text_len()).add(offset),
|
||||
});
|
||||
line = &line[code.len()..];
|
||||
|
||||
// Codes can be comma- or whitespace-delimited.
|
||||
@@ -485,7 +495,12 @@ impl<'a> ParsedFileExemption<'a> {
|
||||
return Err(ParseError::MissingCodes);
|
||||
}
|
||||
|
||||
Self::Codes(codes)
|
||||
let codes_end = init_line_len - line.text_len();
|
||||
let range = TextRange::new(comment_start, codes_end);
|
||||
Self::Codes(Codes {
|
||||
range: range.add(offset),
|
||||
codes,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1059,7 +1074,7 @@ mod tests {
|
||||
use ruff_diagnostics::{Diagnostic, Edit};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::LineEnding;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption};
|
||||
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
||||
@@ -1226,50 +1241,74 @@ mod tests {
|
||||
#[test]
|
||||
fn flake8_exemption_all() {
|
||||
let source = "# flake8: noqa";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruff_exemption_all() {
|
||||
let source = "# ruff: noqa";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flake8_exemption_all_no_space() {
|
||||
let source = "#flake8:noqa";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruff_exemption_all_no_space() {
|
||||
let source = "#ruff:noqa";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flake8_exemption_codes() {
|
||||
// Note: Flake8 doesn't support this; it's treated as a blanket exemption.
|
||||
let source = "# flake8: noqa: F401, F841";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruff_exemption_codes() {
|
||||
let source = "# ruff: noqa: F401, F841";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flake8_exemption_all_case_insensitive() {
|
||||
let source = "# flake8: NoQa";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruff_exemption_all_case_insensitive() {
|
||||
let source = "# ruff: NoQa";
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(source));
|
||||
assert_debug_snapshot!(ParsedFileExemption::try_extract(
|
||||
TextRange::up_to(source.text_len()),
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_python_semantic::Modules;
|
||||
@@ -39,8 +39,8 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// dag = DAG(dag_id="my_dag", schedule=timedelta(days=1))
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AirflowDagNoScheduleArgument;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct AirflowDagNoScheduleArgument;
|
||||
|
||||
impl Violation for AirflowDagNoScheduleArgument {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -30,8 +31,8 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// my_task = PythonOperator(task_id="my_task")
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AirflowVariableNameTaskIdMismatch {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct AirflowVariableNameTaskIdMismatch {
|
||||
task_id: String,
|
||||
}
|
||||
|
||||
@@ -49,6 +50,10 @@ pub(crate) fn variable_name_task_id(
|
||||
targets: &[Expr],
|
||||
value: &Expr,
|
||||
) -> Option<Diagnostic> {
|
||||
if !checker.semantic().seen_module(Modules::AIRFLOW) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If we have more than one target, we can't do anything.
|
||||
let [target] = targets else {
|
||||
return None;
|
||||
@@ -69,7 +74,7 @@ pub(crate) fn variable_name_task_id(
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments()[0], "airflow"))
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["airflow", ..]))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{LineRanges, UniversalNewlineIterator};
|
||||
use ruff_text_size::TextRange;
|
||||
@@ -29,8 +29,8 @@ use super::super::detection::comment_contains_code;
|
||||
/// - `lint.task-tags`
|
||||
///
|
||||
/// [#4845]: https://github.com/astral-sh/ruff/issues/4845
|
||||
#[violation]
|
||||
pub struct CommentedOutCode;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct CommentedOutCode;
|
||||
|
||||
impl Violation for CommentedOutCode {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::helpers::map_callable;
|
||||
use ruff_python_semantic::Modules;
|
||||
@@ -63,8 +63,8 @@ use crate::settings::types::PythonVersion;
|
||||
/// [fastAPI documentation]: https://fastapi.tiangolo.com/tutorial/query-params-str-validations/?h=annotated#advantages-of-annotated
|
||||
/// [typing.Annotated]: https://docs.python.org/3/library/typing.html#typing.Annotated
|
||||
/// [typing_extensions]: https://typing-extensions.readthedocs.io/en/stable/
|
||||
#[violation]
|
||||
pub struct FastApiNonAnnotatedDependency {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct FastApiNonAnnotatedDependency {
|
||||
py_version: PythonVersion,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{Decorator, Expr, ExprCall, Keyword, StmtFunctionDef};
|
||||
use ruff_python_semantic::{Modules, SemanticModel};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -59,8 +59,8 @@ use crate::rules::fastapi::rules::is_fastapi_route_decorator;
|
||||
/// return item
|
||||
/// ```
|
||||
|
||||
#[violation]
|
||||
pub struct FastApiRedundantResponseModel;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct FastApiRedundantResponseModel;
|
||||
|
||||
impl AlwaysFixableViolation for FastApiRedundantResponseModel {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::str::CharIndices;
|
||||
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_diagnostics::{Diagnostic, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Expr, Parameter, ParameterWithDefault};
|
||||
use ruff_python_semantic::{Modules, SemanticModel};
|
||||
@@ -62,8 +62,8 @@ use crate::rules::fastapi::rules::is_fastapi_route_decorator;
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as modifying a function signature can
|
||||
/// change the behavior of the code.
|
||||
#[violation]
|
||||
pub struct FastApiUnusedPathParameter {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct FastApiUnusedPathParameter {
|
||||
arg_name: String,
|
||||
function_name: String,
|
||||
is_positional: bool,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -40,8 +40,8 @@ use super::super::helpers::is_sys;
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionCmpStr3;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersionCmpStr3;
|
||||
|
||||
impl Violation for SysVersionCmpStr3 {
|
||||
#[derive_message_formats]
|
||||
@@ -87,8 +87,8 @@ impl Violation for SysVersionCmpStr3 {
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionInfo0Eq3;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersionInfo0Eq3;
|
||||
|
||||
impl Violation for SysVersionInfo0Eq3 {
|
||||
#[derive_message_formats]
|
||||
@@ -127,8 +127,8 @@ impl Violation for SysVersionInfo0Eq3 {
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionInfo1CmpInt;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersionInfo1CmpInt;
|
||||
|
||||
impl Violation for SysVersionInfo1CmpInt {
|
||||
#[derive_message_formats]
|
||||
@@ -169,8 +169,8 @@ impl Violation for SysVersionInfo1CmpInt {
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionInfoMinorCmpInt;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersionInfoMinorCmpInt;
|
||||
|
||||
impl Violation for SysVersionInfoMinorCmpInt {
|
||||
#[derive_message_formats]
|
||||
@@ -212,8 +212,8 @@ impl Violation for SysVersionInfoMinorCmpInt {
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionCmpStr10;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersionCmpStr10;
|
||||
|
||||
impl Violation for SysVersionCmpStr10 {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -35,8 +35,8 @@ use crate::checkers::ast::Checker;
|
||||
/// - [PyPI: `six`](https://pypi.org/project/six/)
|
||||
/// - [Six documentation: `six.PY2`](https://six.readthedocs.io/#six.PY2)
|
||||
/// - [Six documentation: `six.PY3`](https://six.readthedocs.io/#six.PY3)
|
||||
#[violation]
|
||||
pub struct SixPY3;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SixPY3;
|
||||
|
||||
impl Violation for SixPY3 {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -37,8 +37,8 @@ use crate::rules::flake8_2020::helpers::is_sys;
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionSlice3;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersionSlice3;
|
||||
|
||||
impl Violation for SysVersionSlice3 {
|
||||
#[derive_message_formats]
|
||||
@@ -77,8 +77,8 @@ impl Violation for SysVersionSlice3 {
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersion2;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersion2;
|
||||
|
||||
impl Violation for SysVersion2 {
|
||||
#[derive_message_formats]
|
||||
@@ -117,8 +117,8 @@ impl Violation for SysVersion2 {
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersion0;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersion0;
|
||||
|
||||
impl Violation for SysVersion0 {
|
||||
#[derive_message_formats]
|
||||
@@ -157,8 +157,8 @@ impl Violation for SysVersion0 {
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionSlice1;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SysVersionSlice1;
|
||||
|
||||
impl Violation for SysVersionSlice1 {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
@@ -33,8 +33,8 @@ use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
||||
/// ```python
|
||||
/// def foo(x: int): ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeFunctionArgument {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingTypeFunctionArgument {
|
||||
name: String,
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ impl Violation for MissingTypeFunctionArgument {
|
||||
/// ```python
|
||||
/// def foo(*args: int): ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeArgs {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingTypeArgs {
|
||||
name: String,
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ impl Violation for MissingTypeArgs {
|
||||
/// ```python
|
||||
/// def foo(**kwargs: int): ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeKwargs {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingTypeKwargs {
|
||||
name: String,
|
||||
}
|
||||
|
||||
@@ -137,9 +137,9 @@ impl Violation for MissingTypeKwargs {
|
||||
/// class Foo:
|
||||
/// def bar(self: "Foo"): ...
|
||||
/// ```
|
||||
#[violation]
|
||||
#[derive(ViolationMetadata)]
|
||||
#[deprecated(note = "ANN101 has been removed")]
|
||||
pub struct MissingTypeSelf;
|
||||
pub(crate) struct MissingTypeSelf;
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl Violation for MissingTypeSelf {
|
||||
@@ -181,9 +181,9 @@ impl Violation for MissingTypeSelf {
|
||||
/// @classmethod
|
||||
/// def bar(cls: Type["Foo"]): ...
|
||||
/// ```
|
||||
#[violation]
|
||||
#[derive(ViolationMetadata)]
|
||||
#[deprecated(note = "ANN102 has been removed")]
|
||||
pub struct MissingTypeCls;
|
||||
pub(crate) struct MissingTypeCls;
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl Violation for MissingTypeCls {
|
||||
@@ -215,8 +215,8 @@ impl Violation for MissingTypeCls {
|
||||
/// def add(a: int, b: int) -> int:
|
||||
/// return a + b
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeUndocumentedPublicFunction {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
@@ -258,8 +258,8 @@ impl Violation for MissingReturnTypeUndocumentedPublicFunction {
|
||||
/// def _add(a: int, b: int) -> int:
|
||||
/// return a + b
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypePrivateFunction {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingReturnTypePrivateFunction {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
@@ -314,8 +314,8 @@ impl Violation for MissingReturnTypePrivateFunction {
|
||||
/// def __init__(self, x: int) -> None:
|
||||
/// self.x = x
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeSpecialMethod {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingReturnTypeSpecialMethod {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
@@ -361,8 +361,8 @@ impl Violation for MissingReturnTypeSpecialMethod {
|
||||
/// def bar() -> int:
|
||||
/// return 1
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeStaticMethod {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingReturnTypeStaticMethod {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
@@ -408,8 +408,8 @@ impl Violation for MissingReturnTypeStaticMethod {
|
||||
/// def bar(cls) -> int:
|
||||
/// return 1
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeClassMethod {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingReturnTypeClassMethod {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
@@ -474,8 +474,8 @@ impl Violation for MissingReturnTypeClassMethod {
|
||||
/// - [Typing spec: `Any`](https://typing.readthedocs.io/en/latest/spec/special-types.html#any)
|
||||
/// - [Python documentation: `typing.Any`](https://docs.python.org/3/library/typing.html#typing.Any)
|
||||
/// - [Mypy documentation: The Any type](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-any-type)
|
||||
#[violation]
|
||||
pub struct AnyType {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct AnyType {
|
||||
name: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -37,8 +37,8 @@ use crate::rules::flake8_async::helpers::AsyncModule;
|
||||
/// - [`asyncio` events](https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event)
|
||||
/// - [`anyio` events](https://trio.readthedocs.io/en/latest/reference-core.html#trio.Event)
|
||||
/// - [`trio` events](https://anyio.readthedocs.io/en/latest/api.html#anyio.Event)
|
||||
#[violation]
|
||||
pub struct AsyncBusyWait {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct AsyncBusyWait {
|
||||
module: AsyncModule,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -8,6 +8,7 @@ use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_async::helpers::AsyncModule;
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
#[allow(clippy::doc_link_with_quotes)]
|
||||
/// ## What it does
|
||||
/// Checks for `async` function definitions with `timeout` parameters.
|
||||
///
|
||||
@@ -63,8 +64,8 @@ use crate::settings::types::PythonVersion;
|
||||
/// - [`trio` timeouts](https://trio.readthedocs.io/en/stable/reference-core.html#cancellation-and-timeouts)
|
||||
///
|
||||
/// ["structured concurrency"]: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#timeouts-and-cancellation
|
||||
#[violation]
|
||||
pub struct AsyncFunctionWithTimeout {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct AsyncFunctionWithTimeout {
|
||||
module: AsyncModule,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number};
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -32,8 +32,8 @@ use crate::rules::flake8_async::helpers::AsyncModule;
|
||||
/// async def func():
|
||||
/// await trio.lowlevel.checkpoint()
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AsyncZeroSleep {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct AsyncZeroSleep {
|
||||
module: AsyncModule,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -31,8 +31,8 @@ use crate::checkers::ast::Checker;
|
||||
/// async with session.get("https://example.com/foo/bar") as resp:
|
||||
/// ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct BlockingHttpCallInAsyncFunction;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BlockingHttpCallInAsyncFunction;
|
||||
|
||||
impl Violation for BlockingHttpCallInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::{analyze, SemanticModel};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -33,8 +33,8 @@ use crate::checkers::ast::Checker;
|
||||
/// async with await anyio.open_file("bar.txt") as f:
|
||||
/// contents = await f.read()
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct BlockingOpenCallInAsyncFunction;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BlockingOpenCallInAsyncFunction;
|
||||
|
||||
impl Violation for BlockingOpenCallInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::analyze::typing::find_assigned_value;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
@@ -30,8 +30,8 @@ use crate::registry::AsRule;
|
||||
/// async def foo():
|
||||
/// asyncio.create_subprocess_shell(cmd)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct CreateSubprocessInAsyncFunction;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct CreateSubprocessInAsyncFunction;
|
||||
|
||||
impl Violation for CreateSubprocessInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
@@ -62,8 +62,8 @@ impl Violation for CreateSubprocessInAsyncFunction {
|
||||
/// async def foo():
|
||||
/// asyncio.create_subprocess_shell(cmd)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct RunProcessInAsyncFunction;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct RunProcessInAsyncFunction;
|
||||
|
||||
impl Violation for RunProcessInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
@@ -98,8 +98,8 @@ impl Violation for RunProcessInAsyncFunction {
|
||||
/// async def foo():
|
||||
/// await asyncio.loop.run_in_executor(None, wait_for_process)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct WaitForProcessInAsyncFunction;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct WaitForProcessInAsyncFunction;
|
||||
|
||||
impl Violation for WaitForProcessInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -28,8 +28,8 @@ use crate::checkers::ast::Checker;
|
||||
/// async def fetch():
|
||||
/// await asyncio.sleep(1)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct BlockingSleepInAsyncFunction;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BlockingSleepInAsyncFunction;
|
||||
|
||||
impl Violation for BlockingSleepInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::helpers::{any_over_body, AwaitVisitor};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{Expr, StmtWith, WithItem};
|
||||
@@ -38,8 +38,8 @@ use crate::rules::flake8_async::helpers::MethodName;
|
||||
/// - [`asyncio` timeouts](https://docs.python.org/3/library/asyncio-task.html#timeouts)
|
||||
/// - [`anyio` timeouts](https://anyio.readthedocs.io/en/stable/cancellation.html)
|
||||
/// - [`trio` timeouts](https://trio.readthedocs.io/en/stable/reference-core.html#cancellation-and-timeouts)
|
||||
#[violation]
|
||||
pub struct CancelScopeNoCheckpoint {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct CancelScopeNoCheckpoint {
|
||||
method_name: MethodName,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{Expr, ExprCall, ExprNumberLiteral, Number};
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -34,8 +34,8 @@ use crate::rules::flake8_async::helpers::AsyncModule;
|
||||
/// async def func():
|
||||
/// await trio.sleep_forever()
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct LongSleepNotForever {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct LongSleepNotForever {
|
||||
module: AsyncModule,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -31,8 +31,8 @@ use crate::rules::flake8_async::helpers::MethodName;
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as adding an `await` to a function
|
||||
/// call changes its semantics and runtime behavior.
|
||||
#[violation]
|
||||
pub struct TrioSyncCall {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct TrioSyncCall {
|
||||
method_name: MethodName,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_python_ast::Stmt;
|
||||
use ruff_text_size::{TextLen, TextRange};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
@@ -30,8 +30,8 @@ use ruff_text_size::Ranged;
|
||||
/// if x <= 0:
|
||||
/// raise ValueError("Expected positive value.")
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct Assert;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct Assert;
|
||||
|
||||
impl Violation for Assert {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_python_semantic::{Modules, SemanticModel};
|
||||
@@ -34,8 +34,8 @@ use crate::checkers::ast::Checker;
|
||||
/// - [Python documentation: `os.chmod`](https://docs.python.org/3/library/os.html#os.chmod)
|
||||
/// - [Python documentation: `stat`](https://docs.python.org/3/library/stat.html)
|
||||
/// - [Common Weakness Enumeration: CWE-732](https://cwe.mitre.org/data/definitions/732.html)
|
||||
#[violation]
|
||||
pub struct BadFilePermissions {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BadFilePermissions {
|
||||
reason: Reason,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprAttribute};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -33,8 +33,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ## References
|
||||
/// - [Django documentation: SQL injection protection](https://docs.djangoproject.com/en/dev/topics/security/#sql-injection-protection)
|
||||
/// - [Common Weakness Enumeration: CWE-89](https://cwe.mitre.org/data/definitions/89.html)
|
||||
#[violation]
|
||||
pub struct DjangoExtra;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct DjangoExtra;
|
||||
|
||||
impl Violation for DjangoExtra {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -24,8 +24,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ## References
|
||||
/// - [Django documentation: SQL injection protection](https://docs.djangoproject.com/en/dev/topics/security/#sql-injection-protection)
|
||||
/// - [Common Weakness Enumeration: CWE-89](https://cwe.mitre.org/data/definitions/89.html)
|
||||
#[violation]
|
||||
pub struct DjangoRawSql;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct DjangoRawSql;
|
||||
|
||||
impl Violation for DjangoRawSql {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_python_ast::Expr;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -21,8 +21,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ## References
|
||||
/// - [Python documentation: `exec`](https://docs.python.org/3/library/functions.html#exec)
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
#[violation]
|
||||
pub struct ExecBuiltin;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct ExecBuiltin;
|
||||
|
||||
impl Violation for ExecBuiltin {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::{Expr, ExprAttribute, ExprCall};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
@@ -36,8 +36,8 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## References
|
||||
/// - [Flask documentation: Debug Mode](https://flask.palletsprojects.com/en/latest/quickstart/#debug-mode)
|
||||
#[violation]
|
||||
pub struct FlaskDebugTrue;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct FlaskDebugTrue;
|
||||
|
||||
impl Violation for FlaskDebugTrue {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, StringLike};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -26,8 +26,8 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-200](https://cwe.mitre.org/data/definitions/200.html)
|
||||
#[violation]
|
||||
pub struct HardcodedBindAllInterfaces;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct HardcodedBindAllInterfaces;
|
||||
|
||||
impl Violation for HardcodedBindAllInterfaces {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_python_ast::{Expr, Parameter, ParameterWithDefault, Parameters};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -38,8 +38,8 @@ use super::super::helpers::{matches_password_name, string_literal};
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-259](https://cwe.mitre.org/data/definitions/259.html)
|
||||
#[violation]
|
||||
pub struct HardcodedPasswordDefault {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct HardcodedPasswordDefault {
|
||||
name: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_python_ast::Keyword;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -34,8 +34,8 @@ use super::super::helpers::{matches_password_name, string_literal};
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-259](https://cwe.mitre.org/data/definitions/259.html)
|
||||
#[violation]
|
||||
pub struct HardcodedPasswordFuncArg {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct HardcodedPasswordFuncArg {
|
||||
name: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -33,8 +33,8 @@ use super::super::helpers::{matches_password_name, string_literal};
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-259](https://cwe.mitre.org/data/definitions/259.html)
|
||||
#[violation]
|
||||
pub struct HardcodedPasswordString {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct HardcodedPasswordString {
|
||||
name: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
|
||||
use regex::Regex;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::str::raw_contents;
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -35,8 +35,8 @@ static SQL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
/// ## References
|
||||
/// - [B608: Test for SQL injection](https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html)
|
||||
/// - [psycopg3: Server-side binding](https://www.psycopg.org/psycopg3/docs/basic/from_pg2.html#server-side-binding)
|
||||
#[violation]
|
||||
pub struct HardcodedSQLExpression;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct HardcodedSQLExpression;
|
||||
|
||||
impl Violation for HardcodedSQLExpression {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, Expr, StringLike};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -39,8 +39,8 @@ use crate::checkers::ast::Checker;
|
||||
/// - [Common Weakness Enumeration: CWE-377](https://cwe.mitre.org/data/definitions/377.html)
|
||||
/// - [Common Weakness Enumeration: CWE-379](https://cwe.mitre.org/data/definitions/379.html)
|
||||
/// - [Python documentation: `tempfile`](https://docs.python.org/3/library/tempfile.html)
|
||||
#[violation]
|
||||
pub struct HardcodedTempFile {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct HardcodedTempFile {
|
||||
string: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::helpers::is_const_false;
|
||||
use ruff_python_ast::{self as ast, Arguments};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -48,8 +48,8 @@ use super::super::helpers::string_literal;
|
||||
/// - [Common Weakness Enumeration: CWE-327](https://cwe.mitre.org/data/definitions/327.html)
|
||||
/// - [Common Weakness Enumeration: CWE-328](https://cwe.mitre.org/data/definitions/328.html)
|
||||
/// - [Common Weakness Enumeration: CWE-916](https://cwe.mitre.org/data/definitions/916.html)
|
||||
#[violation]
|
||||
pub struct HashlibInsecureHashFunction {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct HashlibInsecureHashFunction {
|
||||
library: String,
|
||||
string: String,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -34,8 +34,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ## References
|
||||
/// - [Jinja documentation: API](https://jinja.palletsprojects.com/en/latest/api/#autoescaping)
|
||||
/// - [Common Weakness Enumeration: CWE-94](https://cwe.mitre.org/data/definitions/94.html)
|
||||
#[violation]
|
||||
pub struct Jinja2AutoescapeFalse {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct Jinja2AutoescapeFalse {
|
||||
value: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -24,8 +24,8 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `logging.config.listen()`](https://docs.python.org/3/library/logging.config.html#logging.config.listen)
|
||||
#[violation]
|
||||
pub struct LoggingConfigInsecureListen;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct LoggingConfigInsecureListen;
|
||||
|
||||
impl Violation for LoggingConfigInsecureListen {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -31,8 +31,8 @@ use ruff_text_size::Ranged;
|
||||
/// - [Mako documentation](https://www.makotemplates.org/)
|
||||
/// - [OpenStack security: Cross site scripting XSS](https://security.openstack.org/guidelines/dg_cross-site-scripting-xss.html)
|
||||
/// - [Common Weakness Enumeration: CWE-80](https://cwe.mitre.org/data/definitions/80.html)
|
||||
#[violation]
|
||||
pub struct MakoTemplates;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MakoTemplates;
|
||||
|
||||
impl Violation for MakoTemplates {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_python_ast::Expr;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -25,8 +25,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
/// - [Paramiko documentation: `SSHClient.exec_command()`](https://docs.paramiko.org/en/stable/api/client.html#paramiko.client.SSHClient.exec_command)
|
||||
#[violation]
|
||||
pub struct ParamikoCall;
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct ParamikoCall;
|
||||
|
||||
impl Violation for ParamikoCall {
|
||||
#[derive_message_formats]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::helpers::is_const_false;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -30,8 +30,8 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-295](https://cwe.mitre.org/data/definitions/295.html)
|
||||
#[violation]
|
||||
pub struct RequestWithNoCertValidation {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct RequestWithNoCertValidation {
|
||||
string: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -32,8 +32,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ## References
|
||||
/// - [Requests documentation: Timeouts](https://requests.readthedocs.io/en/latest/user/advanced/#timeouts)
|
||||
/// - [httpx documentation: Timeouts](https://www.python-httpx.org/advanced/timeouts/)
|
||||
#[violation]
|
||||
pub struct RequestWithoutTimeout {
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct RequestWithoutTimeout {
|
||||
implicit: bool,
|
||||
module: String,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user