Compare commits
1 Commits
david/allo
...
david/type
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9220598fc8 |
1
.github/mypy-primer-ty.toml
vendored
1
.github/mypy-primer-ty.toml
vendored
@@ -5,4 +5,3 @@
|
||||
[rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
unused-ignore-comment = "warn"
|
||||
division-by-zero = "warn"
|
||||
|
||||
1
.github/workflows/mypy_primer.yaml
vendored
1
.github/workflows/mypy_primer.yaml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
- "crates/ruff_python_parser"
|
||||
- ".github/workflows/mypy_primer.yaml"
|
||||
- ".github/workflows/mypy_primer_comment.yaml"
|
||||
- "Cargo.lock"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
|
||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,30 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.11.11
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`airflow`\] Add autofixes for `AIR302` and `AIR312` ([#17942](https://github.com/astral-sh/ruff/pull/17942))
|
||||
- \[`airflow`\] Move rules from `AIR312` to `AIR302` ([#17940](https://github.com/astral-sh/ruff/pull/17940))
|
||||
- \[`airflow`\] Update `AIR301` and `AIR311` with the latest Airflow implementations ([#17985](https://github.com/astral-sh/ruff/pull/17985))
|
||||
- \[`flake8-simplify`\] Enable fix in preview mode (`SIM117`) ([#18208](https://github.com/astral-sh/ruff/pull/18208))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix inconsistent formatting of match-case on `[]` and `_` ([#18147](https://github.com/astral-sh/ruff/pull/18147))
|
||||
- \[`pylint`\] Fix `PLW1514` not recognizing the `encoding` positional argument of `codecs.open` ([#18109](https://github.com/astral-sh/ruff/pull/18109))
|
||||
|
||||
### CLI
|
||||
|
||||
- Add full option name in formatter warning ([#18217](https://github.com/astral-sh/ruff/pull/18217))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix rendering of admonition in docs ([#18163](https://github.com/astral-sh/ruff/pull/18163))
|
||||
- \[`flake8-print`\] Improve print/pprint docs for `T201` and `T203` ([#18130](https://github.com/astral-sh/ruff/pull/18130))
|
||||
- \[`flake8-simplify`\] Add fix safety section (`SIM110`,`SIM210`) ([#18114](https://github.com/astral-sh/ruff/pull/18114),[#18100](https://github.com/astral-sh/ruff/pull/18100))
|
||||
- \[`pylint`\] Fix docs example that produced different output (`PLW0603`) ([#18216](https://github.com/astral-sh/ruff/pull/18216))
|
||||
|
||||
## 0.11.10
|
||||
|
||||
### Preview features
|
||||
|
||||
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
@@ -2485,7 +2485,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.11"
|
||||
version = "0.11.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2725,7 +2725,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.11.11"
|
||||
version = "0.11.10"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3061,7 +3061,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.11.11"
|
||||
version = "0.11.10"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3180,7 +3180,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.21.1"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=4818b15f3b7516555d39f5a41cb75970448bee4c#4818b15f3b7516555d39f5a41cb75970448bee4c"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3203,12 +3203,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.21.1"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=4818b15f3b7516555d39f5a41cb75970448bee4c#4818b15f3b7516555d39f5a41cb75970448bee4c"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.21.1"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=4818b15f3b7516555d39f5a41cb75970448bee4c#4818b15f3b7516555d39f5a41cb75970448bee4c"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
||||
@@ -129,7 +129,7 @@ regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "4818b15f3b7516555d39f5a41cb75970448bee4c" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "7edce6e248f35c8114b4b021cdb474a3fb2813b3" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
||||
@@ -149,8 +149,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.11.11/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.11/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.11.10/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.10/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -183,7 +183,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.11.11
|
||||
rev: v0.11.10
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
doc-valid-idents = [
|
||||
"..",
|
||||
"CodeQL",
|
||||
"CPython",
|
||||
"FastAPI",
|
||||
"IPython",
|
||||
"LangChain",
|
||||
@@ -15,7 +14,7 @@ doc-valid-idents = [
|
||||
"SNMPv1",
|
||||
"SNMPv2",
|
||||
"SNMPv3",
|
||||
"PyFlakes",
|
||||
"PyFlakes"
|
||||
]
|
||||
|
||||
ignore-interior-mutability = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.11.11"
|
||||
version = "0.11.10"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -822,11 +822,11 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||
rule_names.sort();
|
||||
if let [rule] = rule_names.as_slice() {
|
||||
warn_user_once!(
|
||||
"The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration."
|
||||
"The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration."
|
||||
);
|
||||
} else {
|
||||
warn_user_once!(
|
||||
"The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `lint.select` or `lint.extend-select` configuration, or adding them to the `lint.ignore` configuration.",
|
||||
"The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.",
|
||||
rule_names.join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -862,7 +862,7 @@ if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
||||
"#);
|
||||
Ok(())
|
||||
}
|
||||
@@ -999,7 +999,7 @@ def say_hy(name: str):
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
warning: The `lint.flake8-implicit-str-concat.allow-multiline = false` option is incompatible with the formatter unless `ISC001` is enabled. We recommend enabling `ISC001` or setting `allow-multiline=true`.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
@@ -1059,7 +1059,7 @@ def say_hy(name: str):
|
||||
print(f"Hy {name}")
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||
@@ -1199,7 +1199,7 @@ def say_hy(name: str):
|
||||
----- stderr -----
|
||||
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
|
||||
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `lint.select` or `lint.extend-select` configuration, or adding it to the `lint.ignore` configuration.
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
||||
");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -275,12 +275,7 @@ impl fmt::Debug for Files {
|
||||
impl std::panic::RefUnwindSafe for Files {}
|
||||
|
||||
/// A file that's either stored on the host system's file system or in the vendored file system.
|
||||
///
|
||||
/// # Ordering
|
||||
/// Ordering is based on the file's salsa-assigned id and not on its values.
|
||||
/// The id may change between runs.
|
||||
#[salsa::input]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct File {
|
||||
/// The path of the file (immutable).
|
||||
#[returns(ref)]
|
||||
|
||||
@@ -80,7 +80,6 @@ fn generate() -> String {
|
||||
|
||||
let mut parents = Vec::new();
|
||||
|
||||
output.push_str("<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the doc comments in 'crates/ty/src/args.rs' if you want to change anything here. -->\n\n");
|
||||
output.push_str("# CLI Reference\n\n");
|
||||
generate_command(&mut output, &ty, &mut parents);
|
||||
|
||||
|
||||
@@ -25,10 +25,6 @@ pub(crate) fn main(args: &Args) -> anyhow::Result<()> {
|
||||
let file_name = "crates/ty/docs/configuration.md";
|
||||
let markdown_path = PathBuf::from(ROOT_DIR).join(file_name);
|
||||
|
||||
output.push_str(
|
||||
"<!-- WARNING: This file is auto-generated (cargo dev generate-all). Update the doc comments on the 'Options' struct in 'crates/ty_project/src/metadata/options.rs' if you want to change anything here. -->\n\n",
|
||||
);
|
||||
|
||||
generate_set(
|
||||
&mut output,
|
||||
Set::Toplevel(Options::metadata()),
|
||||
|
||||
@@ -56,10 +56,6 @@ fn generate_markdown() -> String {
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
"<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the lint-declarations in 'crates/ty_python_semantic/src/types/diagnostic.rs' if you want to change anything here. -->\n"
|
||||
);
|
||||
let _ = writeln!(&mut output, "# Rules\n");
|
||||
|
||||
let mut lints: Vec<_> = registry.lints().iter().collect();
|
||||
|
||||
@@ -9,8 +9,8 @@ use ruff_db::{Db as SourceDb, Upcast};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
Db, Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource,
|
||||
PythonVersionWithSource, SearchPathSettings, default_lint_registry,
|
||||
Db, Program, ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings,
|
||||
default_lint_registry,
|
||||
};
|
||||
|
||||
static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::LazyLock::new(|| {
|
||||
@@ -44,10 +44,7 @@ impl ModuleDb {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource {
|
||||
version: python_version,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_version,
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths,
|
||||
},
|
||||
|
||||
@@ -19,20 +19,19 @@ impl<'a> Resolver<'a> {
|
||||
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
|
||||
match import {
|
||||
CollectedImport::Import(import) => {
|
||||
let module = resolve_module(self.db, &import)?;
|
||||
Some(module.file()?.path(self.db))
|
||||
resolve_module(self.db, &import).map(|module| module.file().path(self.db))
|
||||
}
|
||||
CollectedImport::ImportFrom(import) => {
|
||||
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
|
||||
let parent = import.parent();
|
||||
|
||||
let module = resolve_module(self.db, &import).or_else(|| {
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
resolve_module(self.db, &import)
|
||||
.map(|module| module.file().path(self.db))
|
||||
.or_else(|| {
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
|
||||
resolve_module(self.db, &parent?)
|
||||
})?;
|
||||
|
||||
Some(module.file()?.path(self.db))
|
||||
resolve_module(self.db, &parent?).map(|module| module.file().path(self.db))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.11.11"
|
||||
version = "0.11.10"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -9,11 +9,3 @@ class Foo:
|
||||
yield 3
|
||||
yield from 3
|
||||
await f()
|
||||
|
||||
def _():
|
||||
# Invalid yield scopes; but not outside a function
|
||||
type X[T: (yield 1)] = int
|
||||
type Y = (yield 2)
|
||||
|
||||
# Valid yield scope
|
||||
yield 3
|
||||
|
||||
@@ -83,7 +83,11 @@ pub(crate) fn bindings(checker: &Checker) {
|
||||
}
|
||||
}
|
||||
if !checker.source_type.is_stub() && checker.enabled(Rule::UnquotedTypeAlias) {
|
||||
flake8_type_checking::rules::unquoted_type_alias(checker, binding);
|
||||
if let Some(diagnostics) =
|
||||
flake8_type_checking::rules::unquoted_type_alias(checker, binding)
|
||||
{
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnsortedDunderSlots) {
|
||||
if let Some(diagnostic) = ruff::rules::sort_dunder_slots(checker, binding) {
|
||||
|
||||
@@ -137,7 +137,11 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
&checker.semantic,
|
||||
)
|
||||
}) {
|
||||
flake8_annotations::rules::definition(checker, definition, *visibility);
|
||||
checker.report_diagnostics(flake8_annotations::rules::definition(
|
||||
checker,
|
||||
definition,
|
||||
*visibility,
|
||||
));
|
||||
}
|
||||
overloaded_name =
|
||||
flake8_annotations::helpers::overloaded_name(definition, &checker.semantic);
|
||||
|
||||
@@ -385,6 +385,15 @@ impl<'a> Checker<'a> {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
/// Extend the collection of [`Diagnostic`] objects in the [`Checker`]
|
||||
pub(crate) fn report_diagnostics<I>(&self, diagnostics: I)
|
||||
where
|
||||
I: IntoIterator<Item = Diagnostic>,
|
||||
{
|
||||
let mut checker_diagnostics = self.diagnostics.borrow_mut();
|
||||
checker_diagnostics.extend(diagnostics);
|
||||
}
|
||||
|
||||
/// Adds a [`TextRange`] to the set of ranges of variable names
|
||||
/// flagged in `flake8-bugbear` violations so far.
|
||||
///
|
||||
@@ -681,17 +690,6 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
false
|
||||
}
|
||||
|
||||
fn in_yield_allowed_context(&self) -> bool {
|
||||
for scope in self.semantic.current_scopes() {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) | ScopeKind::Generator { .. } => return false,
|
||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||
ScopeKind::Module | ScopeKind::Type => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn in_sync_comprehension(&self) -> bool {
|
||||
for scope in self.semantic.current_scopes() {
|
||||
if let ScopeKind::Generator {
|
||||
|
||||
@@ -127,10 +127,6 @@ pub(crate) const fn is_check_file_level_directives_enabled(settings: &LinterSett
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/17644
|
||||
pub(crate) const fn is_readlines_in_for_fix_safe_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
pub(crate) const fn multiple_with_statements_fix_safe_enabled(settings: &LinterSettings) -> bool {
|
||||
pub(crate) const fn is_readlines_in_for_fix_safe(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -190,12 +190,24 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum
|
||||
match qualified_name.segments() {
|
||||
["airflow", .., "DAG" | "dag"] => {
|
||||
// with replacement
|
||||
diagnostic_for_argument(checker, arguments, "fail_stop", Some("fail_fast"));
|
||||
diagnostic_for_argument(checker, arguments, "schedule_interval", Some("schedule"));
|
||||
diagnostic_for_argument(checker, arguments, "timetable", Some("schedule"));
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"fail_stop",
|
||||
Some("fail_fast"),
|
||||
));
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"schedule_interval",
|
||||
Some("schedule"),
|
||||
));
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"timetable",
|
||||
Some("schedule"),
|
||||
));
|
||||
// without replacement
|
||||
diagnostic_for_argument(checker, arguments, "default_view", None);
|
||||
diagnostic_for_argument(checker, arguments, "orientation", None);
|
||||
checker.report_diagnostics(diagnostic_for_argument(arguments, "default_view", None));
|
||||
checker.report_diagnostics(diagnostic_for_argument(arguments, "orientation", None));
|
||||
}
|
||||
segments => {
|
||||
if is_airflow_auth_manager(segments) {
|
||||
@@ -211,14 +223,17 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum
|
||||
));
|
||||
}
|
||||
} else if is_airflow_task_handler(segments) {
|
||||
diagnostic_for_argument(checker, arguments, "filename_template", None);
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"filename_template",
|
||||
None,
|
||||
));
|
||||
} else if is_airflow_builtin_or_provider(segments, "operators", "Operator") {
|
||||
diagnostic_for_argument(
|
||||
checker,
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"task_concurrency",
|
||||
Some("max_active_tis_per_dag"),
|
||||
);
|
||||
));
|
||||
match segments {
|
||||
[
|
||||
"airflow",
|
||||
@@ -227,12 +242,11 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum
|
||||
"trigger_dagrun",
|
||||
"TriggerDagRunOperator",
|
||||
] => {
|
||||
diagnostic_for_argument(
|
||||
checker,
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"execution_date",
|
||||
Some("logical_date"),
|
||||
);
|
||||
));
|
||||
}
|
||||
[
|
||||
"airflow",
|
||||
@@ -249,12 +263,11 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum
|
||||
"BranchDayOfWeekOperator",
|
||||
]
|
||||
| ["airflow", .., "sensors", "weekday", "DayOfWeekSensor"] => {
|
||||
diagnostic_for_argument(
|
||||
checker,
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"use_task_execution_day",
|
||||
Some("use_task_logical_date"),
|
||||
);
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -1044,14 +1057,11 @@ fn check_airflow_plugin_extension(
|
||||
/// Check if the `deprecated` keyword argument is being used and create a diagnostic if so along
|
||||
/// with a possible `replacement`.
|
||||
fn diagnostic_for_argument(
|
||||
checker: &Checker,
|
||||
arguments: &Arguments,
|
||||
deprecated: &str,
|
||||
replacement: Option<&'static str>,
|
||||
) {
|
||||
let Some(keyword) = arguments.find_keyword(deprecated) else {
|
||||
return;
|
||||
};
|
||||
) -> Option<Diagnostic> {
|
||||
let keyword = arguments.find_keyword(deprecated)?;
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
Airflow3Removal {
|
||||
deprecated: deprecated.to_string(),
|
||||
@@ -1073,7 +1083,7 @@ fn diagnostic_for_argument(
|
||||
)));
|
||||
}
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
Some(diagnostic)
|
||||
}
|
||||
|
||||
/// Check whether the symbol is coming from the `secrets` builtin or provider module which ends
|
||||
|
||||
@@ -112,14 +112,11 @@ pub(crate) fn airflow_3_0_suggested_update_expr(checker: &Checker, expr: &Expr)
|
||||
/// Check if the `deprecated` keyword argument is being used and create a diagnostic if so along
|
||||
/// with a possible `replacement`.
|
||||
fn diagnostic_for_argument(
|
||||
checker: &Checker,
|
||||
arguments: &Arguments,
|
||||
deprecated: &str,
|
||||
replacement: Option<&'static str>,
|
||||
) {
|
||||
let Some(keyword) = arguments.find_keyword(deprecated) else {
|
||||
return;
|
||||
};
|
||||
) -> Option<Diagnostic> {
|
||||
let keyword = arguments.find_keyword(deprecated)?;
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
Airflow3SuggestedUpdate {
|
||||
deprecated: deprecated.to_string(),
|
||||
@@ -141,7 +138,7 @@ fn diagnostic_for_argument(
|
||||
)));
|
||||
}
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
Some(diagnostic)
|
||||
}
|
||||
/// Check whether a removed Airflow argument is passed.
|
||||
///
|
||||
@@ -155,11 +152,15 @@ fn diagnostic_for_argument(
|
||||
fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, arguments: &Arguments) {
|
||||
match qualified_name.segments() {
|
||||
["airflow", .., "DAG" | "dag"] => {
|
||||
diagnostic_for_argument(checker, arguments, "sla_miss_callback", None);
|
||||
checker.report_diagnostics(diagnostic_for_argument(
|
||||
arguments,
|
||||
"sla_miss_callback",
|
||||
None,
|
||||
));
|
||||
}
|
||||
segments => {
|
||||
if is_airflow_builtin_or_provider(segments, "operators", "Operator") {
|
||||
diagnostic_for_argument(checker, arguments, "sla", None);
|
||||
checker.report_diagnostics(diagnostic_for_argument(arguments, "sla", None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,9 +604,9 @@ pub(crate) fn definition(
|
||||
checker: &Checker,
|
||||
definition: &Definition,
|
||||
visibility: visibility::Visibility,
|
||||
) {
|
||||
) -> Vec<Diagnostic> {
|
||||
let Some(function) = definition.as_function_def() else {
|
||||
return;
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let ast::StmtFunctionDef {
|
||||
@@ -899,10 +899,13 @@ pub(crate) fn definition(
|
||||
}
|
||||
}
|
||||
|
||||
if !checker.settings.flake8_annotations.ignore_fully_untyped {
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
// If settings say so, don't report any of the
|
||||
// diagnostics gathered here if there were no type annotations at all.
|
||||
if !checker.settings.flake8_annotations.ignore_fully_untyped
|
||||
|| has_any_typed_arg
|
||||
if has_any_typed_arg
|
||||
|| has_typed_return
|
||||
|| (is_method
|
||||
&& !visibility::is_staticmethod(decorator_list, checker.semantic())
|
||||
@@ -912,8 +915,8 @@ pub(crate) fn definition(
|
||||
.or_else(|| parameters.args.first())
|
||||
.is_some_and(|first_param| first_param.annotation().is_some()))
|
||||
{
|
||||
for diagnostic in diagnostics {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
diagnostics
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,21 +52,17 @@ impl Violation for HardcodedPasswordFuncArg {
|
||||
|
||||
/// S106
|
||||
pub(crate) fn hardcoded_password_func_arg(checker: &Checker, keywords: &[Keyword]) {
|
||||
for keyword in keywords {
|
||||
if string_literal(&keyword.value).is_none_or(str::is_empty) {
|
||||
continue;
|
||||
}
|
||||
let Some(arg) = &keyword.arg else {
|
||||
continue;
|
||||
};
|
||||
checker.report_diagnostics(keywords.iter().filter_map(|keyword| {
|
||||
string_literal(&keyword.value).filter(|string| !string.is_empty())?;
|
||||
let arg = keyword.arg.as_ref()?;
|
||||
if !matches_password_name(arg) {
|
||||
continue;
|
||||
return None;
|
||||
}
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
Some(Diagnostic::new(
|
||||
HardcodedPasswordFuncArg {
|
||||
name: arg.to_string(),
|
||||
},
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
))
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -76,20 +76,16 @@ pub(crate) fn compare_to_hardcoded_password_string(
|
||||
left: &Expr,
|
||||
comparators: &[Expr],
|
||||
) {
|
||||
for comp in comparators {
|
||||
if string_literal(comp).is_none_or(str::is_empty) {
|
||||
continue;
|
||||
}
|
||||
let Some(name) = password_target(left) else {
|
||||
continue;
|
||||
};
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
checker.report_diagnostics(comparators.iter().filter_map(|comp| {
|
||||
string_literal(comp).filter(|string| !string.is_empty())?;
|
||||
let name = password_target(left)?;
|
||||
Some(Diagnostic::new(
|
||||
HardcodedPasswordString {
|
||||
name: name.to_string(),
|
||||
},
|
||||
comp.range(),
|
||||
));
|
||||
}
|
||||
))
|
||||
}));
|
||||
}
|
||||
|
||||
/// S105
|
||||
|
||||
@@ -7,6 +7,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::typing::{
|
||||
is_immutable_annotation, is_immutable_func, is_immutable_newtype_call, is_mutable_func,
|
||||
};
|
||||
@@ -82,15 +83,20 @@ impl Violation for FunctionCallInDefaultArgument {
|
||||
}
|
||||
|
||||
struct ArgumentDefaultVisitor<'a, 'b> {
|
||||
checker: &'a Checker<'b>,
|
||||
semantic: &'a SemanticModel<'b>,
|
||||
extend_immutable_calls: &'a [QualifiedName<'b>],
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ArgumentDefaultVisitor<'a, 'b> {
|
||||
fn new(checker: &'a Checker<'b>, extend_immutable_calls: &'a [QualifiedName<'b>]) -> Self {
|
||||
fn new(
|
||||
semantic: &'a SemanticModel<'b>,
|
||||
extend_immutable_calls: &'a [QualifiedName<'b>],
|
||||
) -> Self {
|
||||
Self {
|
||||
checker,
|
||||
semantic,
|
||||
extend_immutable_calls,
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,21 +105,13 @@ impl Visitor<'_> for ArgumentDefaultVisitor<'_, '_> {
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
match expr {
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
if !is_mutable_func(func, self.checker.semantic())
|
||||
&& !is_immutable_func(
|
||||
func,
|
||||
self.checker.semantic(),
|
||||
self.extend_immutable_calls,
|
||||
)
|
||||
if !is_mutable_func(func, self.semantic)
|
||||
&& !is_immutable_func(func, self.semantic, self.extend_immutable_calls)
|
||||
&& !func.as_name_expr().is_some_and(|name| {
|
||||
is_immutable_newtype_call(
|
||||
name,
|
||||
self.checker.semantic(),
|
||||
self.extend_immutable_calls,
|
||||
)
|
||||
is_immutable_newtype_call(name, self.semantic, self.extend_immutable_calls)
|
||||
})
|
||||
{
|
||||
self.checker.report_diagnostic(Diagnostic::new(
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
FunctionCallInDefaultArgument {
|
||||
name: UnqualifiedName::from_expr(func).map(|name| name.to_string()),
|
||||
},
|
||||
@@ -141,7 +139,7 @@ pub(crate) fn function_call_in_argument_default(checker: &Checker, parameters: &
|
||||
.map(|target| QualifiedName::from_dotted_name(target))
|
||||
.collect();
|
||||
|
||||
let mut visitor = ArgumentDefaultVisitor::new(checker, &extend_immutable_calls);
|
||||
let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), &extend_immutable_calls);
|
||||
for parameter in parameters.iter_non_variadic_params() {
|
||||
if let Some(default) = parameter.default() {
|
||||
if !parameter.annotation().is_some_and(|expr| {
|
||||
@@ -151,4 +149,6 @@ pub(crate) fn function_call_in_argument_default(checker: &Checker, parameters: &
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker.report_diagnostics(visitor.diagnostics);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,5 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) {
|
||||
}
|
||||
}
|
||||
|
||||
for diagnostic in diagnostics {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
@@ -139,9 +139,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
|
||||
}
|
||||
|
||||
// Add all diagnostics to the checker
|
||||
for diagnostic in diagnostics {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
@@ -210,23 +210,23 @@ impl Violation for PytestUnittestAssertion {
|
||||
|
||||
/// Visitor that tracks assert statements and checks if they reference
|
||||
/// the exception name.
|
||||
struct ExceptionHandlerVisitor<'a, 'b> {
|
||||
struct ExceptionHandlerVisitor<'a> {
|
||||
exception_name: &'a str,
|
||||
current_assert: Option<&'a Stmt>,
|
||||
checker: &'a Checker<'b>,
|
||||
errors: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ExceptionHandlerVisitor<'a, 'b> {
|
||||
const fn new(checker: &'a Checker<'b>, exception_name: &'a str) -> Self {
|
||||
impl<'a> ExceptionHandlerVisitor<'a> {
|
||||
const fn new(exception_name: &'a str) -> Self {
|
||||
Self {
|
||||
exception_name,
|
||||
current_assert: None,
|
||||
checker,
|
||||
errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for ExceptionHandlerVisitor<'a, '_> {
|
||||
impl<'a> Visitor<'a> for ExceptionHandlerVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match stmt {
|
||||
Stmt::Assert(_) => {
|
||||
@@ -243,7 +243,7 @@ impl<'a> Visitor<'a> for ExceptionHandlerVisitor<'a, '_> {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
if let Some(current_assert) = self.current_assert {
|
||||
if id.as_str() == self.exception_name {
|
||||
self.checker.report_diagnostic(Diagnostic::new(
|
||||
self.errors.push(Diagnostic::new(
|
||||
PytestAssertInExcept {
|
||||
name: id.to_string(),
|
||||
},
|
||||
@@ -257,12 +257,13 @@ impl<'a> Visitor<'a> for ExceptionHandlerVisitor<'a, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_assert_in_except(checker: &Checker, name: &str, body: &[Stmt]) {
|
||||
fn check_assert_in_except(name: &str, body: &[Stmt]) -> Vec<Diagnostic> {
|
||||
// Walk body to find assert statements that reference the exception name
|
||||
let mut visitor = ExceptionHandlerVisitor::new(checker, name);
|
||||
let mut visitor = ExceptionHandlerVisitor::new(name);
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
visitor.errors
|
||||
}
|
||||
|
||||
/// PT009
|
||||
@@ -595,13 +596,15 @@ pub(crate) fn assert_falsy(checker: &Checker, stmt: &Stmt, test: &Expr) {
|
||||
|
||||
/// PT017
|
||||
pub(crate) fn assert_in_exception_handler(checker: &Checker, handlers: &[ExceptHandler]) {
|
||||
for handler in handlers {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { name, body, .. }) =
|
||||
handler;
|
||||
if let Some(name) = name {
|
||||
check_assert_in_except(checker, name, body);
|
||||
checker.report_diagnostics(handlers.iter().flat_map(|handler| match handler {
|
||||
ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { name, body, .. }) => {
|
||||
if let Some(name) = name {
|
||||
check_assert_in_except(name, body)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
||||
@@ -9,6 +9,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_quotes;
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for strings that include escaped quotes, and suggests changing
|
||||
@@ -60,7 +61,11 @@ pub(crate) fn avoidable_escaped_quote(checker: &Checker, string_like: StringLike
|
||||
return;
|
||||
}
|
||||
|
||||
let mut rule_checker = AvoidableEscapedQuoteChecker::new(checker, checker.target_version());
|
||||
let mut rule_checker = AvoidableEscapedQuoteChecker::new(
|
||||
checker.locator(),
|
||||
checker.settings,
|
||||
checker.target_version(),
|
||||
);
|
||||
|
||||
for part in string_like.parts() {
|
||||
match part {
|
||||
@@ -73,45 +78,59 @@ pub(crate) fn avoidable_escaped_quote(checker: &Checker, string_like: StringLike
|
||||
ast::StringLikePart::FString(f_string) => rule_checker.visit_f_string(f_string),
|
||||
}
|
||||
}
|
||||
|
||||
checker.report_diagnostics(rule_checker.into_diagnostics());
|
||||
}
|
||||
|
||||
/// Checks for `Q003` violations using the [`Visitor`] implementation.
|
||||
struct AvoidableEscapedQuoteChecker<'a, 'b> {
|
||||
checker: &'a Checker<'b>,
|
||||
#[derive(Debug)]
|
||||
struct AvoidableEscapedQuoteChecker<'a> {
|
||||
locator: &'a Locator<'a>,
|
||||
quotes_settings: &'a flake8_quotes::settings::Settings,
|
||||
supports_pep701: bool,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> AvoidableEscapedQuoteChecker<'a, 'b> {
|
||||
fn new(checker: &'a Checker<'b>, target_version: PythonVersion) -> Self {
|
||||
impl<'a> AvoidableEscapedQuoteChecker<'a> {
|
||||
fn new(
|
||||
locator: &'a Locator<'a>,
|
||||
settings: &'a LinterSettings,
|
||||
target_version: PythonVersion,
|
||||
) -> Self {
|
||||
Self {
|
||||
checker,
|
||||
quotes_settings: &checker.settings.flake8_quotes,
|
||||
locator,
|
||||
quotes_settings: &settings.flake8_quotes,
|
||||
supports_pep701: target_version.supports_pep_701(),
|
||||
diagnostics: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the checker and returns a vector of [`Diagnostic`] found during the visit.
|
||||
fn into_diagnostics(self) -> Vec<Diagnostic> {
|
||||
self.diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor<'_> for AvoidableEscapedQuoteChecker<'_, '_> {
|
||||
fn visit_string_literal(&mut self, string_literal: &ast::StringLiteral) {
|
||||
impl Visitor<'_> for AvoidableEscapedQuoteChecker<'_> {
|
||||
fn visit_string_literal(&mut self, string_literal: &'_ ast::StringLiteral) {
|
||||
if let Some(diagnostic) = check_string_or_bytes(
|
||||
self.checker.locator(),
|
||||
self.locator,
|
||||
self.quotes_settings,
|
||||
string_literal.range(),
|
||||
AnyStringFlags::from(string_literal.flags),
|
||||
) {
|
||||
self.checker.report_diagnostic(diagnostic);
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_bytes_literal(&mut self, bytes_literal: &ast::BytesLiteral) {
|
||||
fn visit_bytes_literal(&mut self, bytes_literal: &'_ ast::BytesLiteral) {
|
||||
if let Some(diagnostic) = check_string_or_bytes(
|
||||
self.checker.locator(),
|
||||
self.locator,
|
||||
self.quotes_settings,
|
||||
bytes_literal.range(),
|
||||
AnyStringFlags::from(bytes_literal.flags),
|
||||
) {
|
||||
self.checker.report_diagnostic(diagnostic);
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,10 +203,8 @@ impl Visitor<'_> for AvoidableEscapedQuoteChecker<'_, '_> {
|
||||
.literals()
|
||||
.any(|literal| contains_quote(literal, opposite_quote_char))
|
||||
{
|
||||
if let Some(diagnostic) =
|
||||
check_f_string(self.checker.locator(), self.quotes_settings, f_string)
|
||||
{
|
||||
self.checker.report_diagnostic(diagnostic);
|
||||
if let Some(diagnostic) = check_f_string(self.locator, self.quotes_settings, f_string) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test_case(Rule::IfElseBlockInsteadOfIfExp, Path::new("SIM108.py"))]
|
||||
#[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -8,10 +8,10 @@ use ruff_python_ast::{self as ast, Stmt, WithItem};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use super::fix_with;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::fits;
|
||||
use crate::preview::multiple_with_statements_fix_safe_enabled;
|
||||
|
||||
use super::fix_with;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the unnecessary nesting of multiple consecutive context
|
||||
@@ -45,15 +45,8 @@ use crate::preview::multiple_with_statements_fix_safe_enabled;
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// # Fix safety
|
||||
///
|
||||
/// This fix is marked as always unsafe unless [preview] mode is enabled, in which case it is always
|
||||
/// marked as safe. Note that the fix is unavailable if it would remove comments (in either case).
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The `with` statement](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement)
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MultipleWithStatements;
|
||||
|
||||
@@ -195,11 +188,7 @@ pub(crate) fn multiple_with_statements(
|
||||
checker.settings.tab_size,
|
||||
)
|
||||
}) {
|
||||
if multiple_with_statements_fix_safe_enabled(checker.settings) {
|
||||
Ok(Some(Fix::safe_edit(edit)))
|
||||
} else {
|
||||
Ok(Some(Fix::unsafe_edit(edit)))
|
||||
}
|
||||
Ok(Some(Fix::unsafe_edit(edit)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM117.py:2:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
1 | # SIM117
|
||||
2 | / with A() as a:
|
||||
3 | | with B() as b:
|
||||
| |__________________^ SIM117
|
||||
4 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | # SIM117
|
||||
2 |-with A() as a:
|
||||
3 |- with B() as b:
|
||||
4 |- print("hello")
|
||||
2 |+with A() as a, B() as b:
|
||||
3 |+ print("hello")
|
||||
5 4 |
|
||||
6 5 | # SIM117
|
||||
7 6 | with A():
|
||||
|
||||
SIM117.py:7:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
6 | # SIM117
|
||||
7 | / with A():
|
||||
8 | | with B():
|
||||
| |_____________^ SIM117
|
||||
9 | with C():
|
||||
10 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | print("hello")
|
||||
5 5 |
|
||||
6 6 | # SIM117
|
||||
7 |-with A():
|
||||
8 |- with B():
|
||||
9 |- with C():
|
||||
10 |- print("hello")
|
||||
7 |+with A(), B():
|
||||
8 |+ with C():
|
||||
9 |+ print("hello")
|
||||
11 10 |
|
||||
12 11 | # SIM117
|
||||
13 12 | with A() as a:
|
||||
|
||||
SIM117.py:13:1: SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
12 | # SIM117
|
||||
13 | / with A() as a:
|
||||
14 | | # Unfixable due to placement of this comment.
|
||||
15 | | with B() as b:
|
||||
| |__________________^ SIM117
|
||||
16 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
SIM117.py:19:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
18 | # SIM117
|
||||
19 | / with A() as a:
|
||||
20 | | with B() as b:
|
||||
| |__________________^ SIM117
|
||||
21 | # Fixable due to placement of this comment.
|
||||
22 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
16 16 | print("hello")
|
||||
17 17 |
|
||||
18 18 | # SIM117
|
||||
19 |-with A() as a:
|
||||
20 |- with B() as b:
|
||||
21 |- # Fixable due to placement of this comment.
|
||||
22 |- print("hello")
|
||||
19 |+with A() as a, B() as b:
|
||||
20 |+ # Fixable due to placement of this comment.
|
||||
21 |+ print("hello")
|
||||
23 22 |
|
||||
24 23 | # OK
|
||||
25 24 | with A() as a:
|
||||
|
||||
SIM117.py:47:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
46 | # SIM117
|
||||
47 | / async with A() as a:
|
||||
48 | | async with B() as b:
|
||||
| |________________________^ SIM117
|
||||
49 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
44 44 | print("hello")
|
||||
45 45 |
|
||||
46 46 | # SIM117
|
||||
47 |-async with A() as a:
|
||||
48 |- async with B() as b:
|
||||
49 |- print("hello")
|
||||
47 |+async with A() as a, B() as b:
|
||||
48 |+ print("hello")
|
||||
50 49 |
|
||||
51 50 | while True:
|
||||
52 51 | # SIM117
|
||||
|
||||
SIM117.py:53:5: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
51 | while True:
|
||||
52 | # SIM117
|
||||
53 | / with A() as a:
|
||||
54 | | with B() as b:
|
||||
| |______________________^ SIM117
|
||||
55 | """this
|
||||
56 | is valid"""
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
50 50 |
|
||||
51 51 | while True:
|
||||
52 52 | # SIM117
|
||||
53 |- with A() as a:
|
||||
54 |- with B() as b:
|
||||
55 |- """this
|
||||
53 |+ with A() as a, B() as b:
|
||||
54 |+ """this
|
||||
56 55 | is valid"""
|
||||
57 56 |
|
||||
58 |- """the indentation on
|
||||
57 |+ """the indentation on
|
||||
59 58 | this line is significant"""
|
||||
60 59 |
|
||||
61 |- "this is" \
|
||||
60 |+ "this is" \
|
||||
62 61 | "allowed too"
|
||||
63 62 |
|
||||
64 |- ("so is"
|
||||
63 |+ ("so is"
|
||||
65 64 | "this for some reason")
|
||||
66 65 |
|
||||
67 66 | # SIM117
|
||||
|
||||
SIM117.py:68:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
67 | # SIM117
|
||||
68 | / with (
|
||||
69 | | A() as a,
|
||||
70 | | B() as b,
|
||||
71 | | ):
|
||||
72 | | with C() as c:
|
||||
| |__________________^ SIM117
|
||||
73 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
67 67 | # SIM117
|
||||
68 68 | with (
|
||||
69 69 | A() as a,
|
||||
70 |- B() as b,
|
||||
70 |+ B() as b,C() as c
|
||||
71 71 | ):
|
||||
72 |- with C() as c:
|
||||
73 |- print("hello")
|
||||
72 |+ print("hello")
|
||||
74 73 |
|
||||
75 74 | # SIM117
|
||||
76 75 | with A() as a:
|
||||
|
||||
SIM117.py:76:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
75 | # SIM117
|
||||
76 | / with A() as a:
|
||||
77 | | with (
|
||||
78 | | B() as b,
|
||||
79 | | C() as c,
|
||||
80 | | ):
|
||||
| |______^ SIM117
|
||||
81 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
73 73 | print("hello")
|
||||
74 74 |
|
||||
75 75 | # SIM117
|
||||
76 |-with A() as a:
|
||||
77 |- with (
|
||||
78 |- B() as b,
|
||||
79 |- C() as c,
|
||||
80 |- ):
|
||||
81 |- print("hello")
|
||||
76 |+with (
|
||||
77 |+ A() as a, B() as b,
|
||||
78 |+ C() as c,
|
||||
79 |+):
|
||||
80 |+ print("hello")
|
||||
82 81 |
|
||||
83 82 | # SIM117
|
||||
84 83 | with (
|
||||
|
||||
SIM117.py:84:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
83 | # SIM117
|
||||
84 | / with (
|
||||
85 | | A() as a,
|
||||
86 | | B() as b,
|
||||
87 | | ):
|
||||
88 | | with (
|
||||
89 | | C() as c,
|
||||
90 | | D() as d,
|
||||
91 | | ):
|
||||
| |______^ SIM117
|
||||
92 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
83 83 | # SIM117
|
||||
84 84 | with (
|
||||
85 85 | A() as a,
|
||||
86 |- B() as b,
|
||||
86 |+ B() as b,C() as c,
|
||||
87 |+ D() as d,
|
||||
87 88 | ):
|
||||
88 |- with (
|
||||
89 |- C() as c,
|
||||
90 |- D() as d,
|
||||
91 |- ):
|
||||
92 |- print("hello")
|
||||
89 |+ print("hello")
|
||||
93 90 |
|
||||
94 91 | # SIM117 (auto-fixable)
|
||||
95 92 | with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
|
||||
SIM117.py:95:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
94 | # SIM117 (auto-fixable)
|
||||
95 | / with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
96 | | with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
| |__________________________________________________^ SIM117
|
||||
97 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
92 92 | print("hello")
|
||||
93 93 |
|
||||
94 94 | # SIM117 (auto-fixable)
|
||||
95 |-with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
96 |- with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
97 |- print("hello")
|
||||
95 |+with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a, B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
96 |+ print("hello")
|
||||
98 97 |
|
||||
99 98 | # SIM117 (not auto-fixable too long)
|
||||
100 99 | with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ890") as a:
|
||||
|
||||
SIM117.py:100:1: SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
99 | # SIM117 (not auto-fixable too long)
|
||||
100 | / with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ890") as a:
|
||||
101 | | with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
| |__________________________________________________^ SIM117
|
||||
102 | print("hello")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
SIM117.py:106:5: SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
104 | # From issue #3025.
|
||||
105 | async def main():
|
||||
106 | / async with A() as a: # SIM117.
|
||||
107 | | async with B() as b:
|
||||
| |____________________________^ SIM117
|
||||
108 | print("async-inside!")
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
SIM117.py:126:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
125 | # SIM117
|
||||
126 | / with A() as a:
|
||||
127 | | with B() as b:
|
||||
| |__________________^ SIM117
|
||||
128 | type ListOrSet[T] = list[T] | set[T]
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
123 123 | f(b2, c2, d2)
|
||||
124 124 |
|
||||
125 125 | # SIM117
|
||||
126 |-with A() as a:
|
||||
127 |- with B() as b:
|
||||
128 |- type ListOrSet[T] = list[T] | set[T]
|
||||
126 |+with A() as a, B() as b:
|
||||
127 |+ type ListOrSet[T] = list[T] | set[T]
|
||||
129 128 |
|
||||
130 |- class ClassA[T: str]:
|
||||
131 |- def method1(self) -> T:
|
||||
132 |- ...
|
||||
129 |+ class ClassA[T: str]:
|
||||
130 |+ def method1(self) -> T:
|
||||
131 |+ ...
|
||||
133 132 |
|
||||
134 |- f" something { my_dict["key"] } something else "
|
||||
133 |+ f" something { my_dict["key"] } something else "
|
||||
135 134 |
|
||||
136 |- f"foo {f"bar {x}"} baz"
|
||||
135 |+ f"foo {f"bar {x}"} baz"
|
||||
137 136 |
|
||||
138 137 | # Allow cascading for some statements.
|
||||
139 138 | import anyio
|
||||
|
||||
SIM117.py:163:1: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
|
|
||||
162 | # Do not suppress combination, if a context manager is already combined with another.
|
||||
163 | / async with asyncio.timeout(1), A():
|
||||
164 | | async with B():
|
||||
| |___________________^ SIM117
|
||||
165 | pass
|
||||
|
|
||||
= help: Combine `with` statements
|
||||
|
||||
ℹ Safe fix
|
||||
160 160 | pass
|
||||
161 161 |
|
||||
162 162 | # Do not suppress combination, if a context manager is already combined with another.
|
||||
163 |-async with asyncio.timeout(1), A():
|
||||
164 |- async with B():
|
||||
165 |- pass
|
||||
163 |+async with asyncio.timeout(1), A(), B():
|
||||
164 |+ pass
|
||||
@@ -142,26 +142,26 @@ impl AlwaysFixableViolation for QuotedTypeAlias {
|
||||
}
|
||||
|
||||
/// TC007
|
||||
pub(crate) fn unquoted_type_alias(checker: &Checker, binding: &Binding) {
|
||||
pub(crate) fn unquoted_type_alias(checker: &Checker, binding: &Binding) -> Option<Vec<Diagnostic>> {
|
||||
if binding.context.is_typing() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
if !binding.is_annotated_type_alias() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
value: Some(expr), ..
|
||||
})) = binding.statement(checker.semantic())
|
||||
else {
|
||||
return;
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut names = Vec::new();
|
||||
collect_typing_references(checker, expr, &mut names);
|
||||
if names.is_empty() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
// We generate a diagnostic for every name that needs to be quoted
|
||||
@@ -178,13 +178,14 @@ pub(crate) fn unquoted_type_alias(checker: &Checker, binding: &Binding) {
|
||||
checker.locator(),
|
||||
checker.default_string_flags(),
|
||||
);
|
||||
let mut diagnostics = Vec::with_capacity(names.len());
|
||||
for name in names {
|
||||
checker.report_diagnostic(
|
||||
Diagnostic::new(UnquotedTypeAlias, name.range())
|
||||
.with_parent(parent)
|
||||
.with_fix(Fix::unsafe_edit(edit.clone())),
|
||||
);
|
||||
let mut diagnostic = Diagnostic::new(UnquotedTypeAlias, name.range());
|
||||
diagnostic.set_parent(parent);
|
||||
diagnostic.set_fix(Fix::unsafe_edit(edit.clone()));
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
Some(diagnostics)
|
||||
}
|
||||
|
||||
/// Traverses the type expression and collects `[Expr::Name]` nodes that are
|
||||
|
||||
@@ -304,21 +304,19 @@ fn call<'a>(
|
||||
) {
|
||||
let semantic = checker.semantic();
|
||||
let dummy_variable_rgx = &checker.settings.dummy_variable_rgx;
|
||||
for arg in parameters {
|
||||
let Some(binding) = scope
|
||||
checker.report_diagnostics(parameters.filter_map(|arg| {
|
||||
let binding = scope
|
||||
.get(arg.name())
|
||||
.map(|binding_id| semantic.binding(binding_id))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
.map(|binding_id| semantic.binding(binding_id))?;
|
||||
if binding.kind.is_argument()
|
||||
&& binding.is_unused()
|
||||
&& !dummy_variable_rgx.is_match(arg.name())
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(argumentable.check_for(arg.name.to_string(), binding.range()));
|
||||
Some(argumentable.check_for(arg.name.to_string(), binding.range()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// Returns `true` if a function appears to be a base class stub. In other
|
||||
|
||||
@@ -431,7 +431,5 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
|
||||
}
|
||||
}
|
||||
|
||||
for diagnostic in diagnostics {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
@@ -873,6 +873,8 @@ pub(crate) fn check_docstring(
|
||||
section_contexts: &SectionContexts,
|
||||
convention: Option<Convention>,
|
||||
) {
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
// Only check function docstrings.
|
||||
let Some(function_def) = definition.as_function_def() else {
|
||||
return;
|
||||
@@ -925,7 +927,7 @@ pub(crate) fn check_docstring(
|
||||
semantic,
|
||||
)
|
||||
{
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
diagnostics.push(Diagnostic::new(
|
||||
DocstringMissingReturns,
|
||||
docstring.range(),
|
||||
));
|
||||
@@ -936,10 +938,8 @@ pub(crate) fn check_docstring(
|
||||
.iter()
|
||||
.any(|entry| !entry.is_none_return()) =>
|
||||
{
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
DocstringMissingReturns,
|
||||
docstring.range(),
|
||||
));
|
||||
diagnostics
|
||||
.push(Diagnostic::new(DocstringMissingReturns, docstring.range()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -958,16 +958,12 @@ pub(crate) fn check_docstring(
|
||||
|arguments| arguments.first().is_none_or(Expr::is_none_literal_expr),
|
||||
) =>
|
||||
{
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
DocstringMissingYields,
|
||||
docstring.range(),
|
||||
));
|
||||
diagnostics
|
||||
.push(Diagnostic::new(DocstringMissingYields, docstring.range()));
|
||||
}
|
||||
None if body_entries.yields.iter().any(|entry| !entry.is_none_yield) => {
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
DocstringMissingYields,
|
||||
docstring.range(),
|
||||
));
|
||||
diagnostics
|
||||
.push(Diagnostic::new(DocstringMissingYields, docstring.range()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -1000,7 +996,7 @@ pub(crate) fn check_docstring(
|
||||
},
|
||||
docstring.range(),
|
||||
);
|
||||
checker.report_diagnostic(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1015,7 +1011,7 @@ pub(crate) fn check_docstring(
|
||||
|| body_entries.returns.iter().all(ReturnEntry::is_implicit)
|
||||
{
|
||||
let diagnostic = Diagnostic::new(DocstringExtraneousReturns, docstring.range());
|
||||
checker.report_diagnostic(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1025,7 +1021,7 @@ pub(crate) fn check_docstring(
|
||||
if docstring_sections.yields.is_some() {
|
||||
if body_entries.yields.is_empty() {
|
||||
let diagnostic = Diagnostic::new(DocstringExtraneousYields, docstring.range());
|
||||
checker.report_diagnostic(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1051,9 +1047,11 @@ pub(crate) fn check_docstring(
|
||||
},
|
||||
docstring.range(),
|
||||
);
|
||||
checker.report_diagnostic(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
F704.py:6:5: F704 `yield` statement outside of a function
|
||||
|
|
||||
@@ -30,6 +31,4 @@ F704.py:11:1: F704 `await` statement outside of a function
|
||||
10 | yield from 3
|
||||
11 | await f()
|
||||
| ^^^^^^^^^ F704
|
||||
12 |
|
||||
13 | def _():
|
||||
|
|
||||
|
||||
@@ -158,9 +158,7 @@ pub(crate) fn boolean_chained_comparison(checker: &Checker, expr_bool_op: &ExprB
|
||||
Some(diagnostic)
|
||||
});
|
||||
|
||||
for diagnostic in diagnostics {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
/// Checks whether two compare expressions are simplifiable
|
||||
|
||||
@@ -31,9 +31,8 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
///
|
||||
/// def foo():
|
||||
/// var = 10
|
||||
/// print(var)
|
||||
/// return var
|
||||
/// return 10
|
||||
///
|
||||
///
|
||||
/// var = foo()
|
||||
|
||||
@@ -379,13 +379,15 @@ pub(crate) fn redefined_loop_name(checker: &Checker, stmt: &Stmt) {
|
||||
_ => panic!("redefined_loop_name called on Statement that is not a `With` or `For`"),
|
||||
};
|
||||
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
for outer_assignment_target in &outer_assignment_targets {
|
||||
for inner_assignment_target in &inner_assignment_targets {
|
||||
// Compare the targets structurally.
|
||||
if ComparableExpr::from(outer_assignment_target.expr)
|
||||
.eq(&(ComparableExpr::from(inner_assignment_target.expr)))
|
||||
{
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
diagnostics.push(Diagnostic::new(
|
||||
RedefinedLoopName {
|
||||
name: checker.generator().expr(outer_assignment_target.expr),
|
||||
outer_kind: outer_assignment_target.binding_kind,
|
||||
@@ -396,4 +398,6 @@ pub(crate) fn redefined_loop_name(checker: &Checker, stmt: &Stmt) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use ruff_python_semantic::analyze::class::iter_super_class;
|
||||
use ruff_python_semantic::{SemanticModel, analyze::class::iter_super_class};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -66,9 +66,11 @@ pub(crate) fn redefined_slots_in_subclass(checker: &Checker, class_def: &ast::St
|
||||
return;
|
||||
}
|
||||
|
||||
for slot in class_slots {
|
||||
check_super_slots(checker, class_def, &slot);
|
||||
}
|
||||
let semantic = checker.semantic();
|
||||
let diagnostics = class_slots
|
||||
.iter()
|
||||
.filter_map(|slot| check_super_slots(class_def, semantic, slot));
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -101,18 +103,25 @@ impl Ranged for Slot<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_super_slots(checker: &Checker, class_def: &ast::StmtClassDef, slot: &Slot) {
|
||||
for super_class in iter_super_class(class_def, checker.semantic()).skip(1) {
|
||||
if slots_members(&super_class.body).contains(slot) {
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
RedefinedSlotsInSubclass {
|
||||
base: super_class.name.to_string(),
|
||||
slot_name: slot.name.to_string(),
|
||||
},
|
||||
slot.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
fn check_super_slots(
|
||||
class_def: &ast::StmtClassDef,
|
||||
semantic: &SemanticModel,
|
||||
slot: &Slot,
|
||||
) -> Option<Diagnostic> {
|
||||
iter_super_class(class_def, semantic)
|
||||
.skip(1)
|
||||
.find_map(&|super_class: &ast::StmtClassDef| {
|
||||
if slots_members(&super_class.body).contains(slot) {
|
||||
return Some(Diagnostic::new(
|
||||
RedefinedSlotsInSubclass {
|
||||
base: super_class.name.to_string(),
|
||||
slot_name: slot.name.to_string(),
|
||||
},
|
||||
slot.range(),
|
||||
));
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn slots_members(body: &[Stmt]) -> FxHashSet<Slot> {
|
||||
|
||||
@@ -43,6 +43,7 @@ pub(crate) fn self_assignment(checker: &Checker, assign: &ast::StmtAssign) {
|
||||
if checker.semantic().current_scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
for (left, right) in assign
|
||||
.targets
|
||||
@@ -50,8 +51,9 @@ pub(crate) fn self_assignment(checker: &Checker, assign: &ast::StmtAssign) {
|
||||
.chain(std::iter::once(assign.value.as_ref()))
|
||||
.tuple_combinations()
|
||||
{
|
||||
visit_assignments(checker, left, right);
|
||||
visit_assignments(left, right, &mut diagnostics);
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
/// PLW0127
|
||||
@@ -65,21 +67,23 @@ pub(crate) fn self_annotated_assignment(checker: &Checker, assign: &ast::StmtAnn
|
||||
if checker.semantic().current_scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
visit_assignments(checker, &assign.target, value);
|
||||
visit_assignments(&assign.target, value, &mut diagnostics);
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
fn visit_assignments(checker: &Checker, left: &Expr, right: &Expr) {
|
||||
fn visit_assignments(left: &Expr, right: &Expr, diagnostics: &mut Vec<Diagnostic>) {
|
||||
match (left, right) {
|
||||
(Expr::Tuple(lhs), Expr::Tuple(rhs)) if lhs.len() == rhs.len() => lhs
|
||||
.iter()
|
||||
.zip(rhs)
|
||||
.for_each(|(lhs_elem, rhs_elem)| visit_assignments(checker, lhs_elem, rhs_elem)),
|
||||
.for_each(|(lhs_elem, rhs_elem)| visit_assignments(lhs_elem, rhs_elem, diagnostics)),
|
||||
(
|
||||
Expr::Name(ast::ExprName { id: lhs_name, .. }),
|
||||
Expr::Name(ast::ExprName { id: rhs_name, .. }),
|
||||
) if lhs_name == rhs_name => {
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
diagnostics.push(Diagnostic::new(
|
||||
SelfAssigningVariable {
|
||||
name: lhs_name.to_string(),
|
||||
},
|
||||
|
||||
@@ -64,26 +64,49 @@ pub(crate) fn read_whole_file(checker: &Checker, with: &ast::StmtWith) {
|
||||
}
|
||||
|
||||
// Then we need to match each `open` operation with exactly one `read` call.
|
||||
let mut matcher = ReadMatcher::new(checker, candidates);
|
||||
visitor::walk_body(&mut matcher, &with.body);
|
||||
let matches = {
|
||||
let mut matcher = ReadMatcher::new(candidates);
|
||||
visitor::walk_body(&mut matcher, &with.body);
|
||||
matcher.into_matches()
|
||||
};
|
||||
|
||||
// All the matched operations should be reported.
|
||||
let diagnostics: Vec<Diagnostic> = matches
|
||||
.iter()
|
||||
.map(|open| {
|
||||
Diagnostic::new(
|
||||
ReadWholeFile {
|
||||
filename: SourceCodeSnippet::from_str(&checker.generator().expr(open.filename)),
|
||||
suggestion: make_suggestion(open, checker.generator()),
|
||||
},
|
||||
open.item.range(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
/// AST visitor that matches `open` operations with the corresponding `read` calls.
|
||||
struct ReadMatcher<'a, 'b> {
|
||||
checker: &'a Checker<'b>,
|
||||
#[derive(Debug)]
|
||||
struct ReadMatcher<'a> {
|
||||
candidates: Vec<FileOpen<'a>>,
|
||||
matches: Vec<FileOpen<'a>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ReadMatcher<'a, 'b> {
|
||||
fn new(checker: &'a Checker<'b>, candidates: Vec<FileOpen<'a>>) -> Self {
|
||||
impl<'a> ReadMatcher<'a> {
|
||||
fn new(candidates: Vec<FileOpen<'a>>) -> Self {
|
||||
Self {
|
||||
checker,
|
||||
candidates,
|
||||
matches: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn into_matches(self) -> Vec<FileOpen<'a>> {
|
||||
self.matches
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for ReadMatcher<'a, '_> {
|
||||
impl<'a> Visitor<'a> for ReadMatcher<'a> {
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
if let Some(read_from) = match_read_call(expr) {
|
||||
if let Some(open) = self
|
||||
@@ -91,16 +114,7 @@ impl<'a> Visitor<'a> for ReadMatcher<'a, '_> {
|
||||
.iter()
|
||||
.position(|open| open.is_ref(read_from))
|
||||
{
|
||||
let open = self.candidates.remove(open);
|
||||
self.checker.report_diagnostic(Diagnostic::new(
|
||||
ReadWholeFile {
|
||||
filename: SourceCodeSnippet::from_str(
|
||||
&self.checker.generator().expr(open.filename),
|
||||
),
|
||||
suggestion: make_suggestion(&open, self.checker.generator()),
|
||||
},
|
||||
open.item.range(),
|
||||
));
|
||||
self.matches.push(self.candidates.remove(open));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::preview::is_readlines_in_for_fix_safe_enabled;
|
||||
use crate::preview::is_readlines_in_for_fix_safe;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{Comprehension, Expr, StmtFor};
|
||||
@@ -86,7 +86,7 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) {
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(ReadlinesInFor, expr_call.range());
|
||||
diagnostic.set_fix(if is_readlines_in_for_fix_safe_enabled(checker.settings) {
|
||||
diagnostic.set_fix(if is_readlines_in_for_fix_safe(checker.settings) {
|
||||
Fix::safe_edit(Edit::range_deletion(
|
||||
expr_call.range().add_start(expr_attr.value.range().len()),
|
||||
))
|
||||
|
||||
@@ -86,42 +86,48 @@ pub(crate) fn repeated_append(checker: &Checker, stmt: &Stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
for group in group_appends(appends) {
|
||||
// Groups with just one element are fine, and shouldn't be replaced by `extend`.
|
||||
if group.appends.len() <= 1 {
|
||||
continue;
|
||||
}
|
||||
// group borrows from checker, so we can't directly push into checker.diagnostics
|
||||
let diagnostics: Vec<Diagnostic> = group_appends(appends)
|
||||
.iter()
|
||||
.filter_map(|group| {
|
||||
// Groups with just one element are fine, and shouldn't be replaced by `extend`.
|
||||
if group.appends.len() <= 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let replacement = make_suggestion(&group, checker.generator());
|
||||
let replacement = make_suggestion(group, checker.generator());
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RepeatedAppend {
|
||||
name: group.name().to_string(),
|
||||
replacement: SourceCodeSnippet::new(replacement.clone()),
|
||||
},
|
||||
group.range(),
|
||||
);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RepeatedAppend {
|
||||
name: group.name().to_string(),
|
||||
replacement: SourceCodeSnippet::new(replacement.clone()),
|
||||
},
|
||||
group.range(),
|
||||
);
|
||||
|
||||
// We only suggest a fix when all appends in a group are clumped together. If they're
|
||||
// non-consecutive, fixing them is much more difficult.
|
||||
//
|
||||
// Avoid fixing if there are comments in between the appends:
|
||||
//
|
||||
// ```python
|
||||
// a.append(1)
|
||||
// # comment
|
||||
// a.append(2)
|
||||
// ```
|
||||
if group.is_consecutive && !checker.comment_ranges().intersects(group.range()) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement(
|
||||
replacement,
|
||||
group.start(),
|
||||
group.end(),
|
||||
)));
|
||||
}
|
||||
// We only suggest a fix when all appends in a group are clumped together. If they're
|
||||
// non-consecutive, fixing them is much more difficult.
|
||||
//
|
||||
// Avoid fixing if there are comments in between the appends:
|
||||
//
|
||||
// ```python
|
||||
// a.append(1)
|
||||
// # comment
|
||||
// a.append(2)
|
||||
// ```
|
||||
if group.is_consecutive && !checker.comment_ranges().intersects(group.range()) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement(
|
||||
replacement,
|
||||
group.start(),
|
||||
group.end(),
|
||||
)));
|
||||
}
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
Some(diagnostic)
|
||||
})
|
||||
.collect();
|
||||
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -65,28 +65,54 @@ pub(crate) fn write_whole_file(checker: &Checker, with: &ast::StmtWith) {
|
||||
}
|
||||
|
||||
// Then we need to match each `open` operation with exactly one `write` call.
|
||||
let mut matcher = WriteMatcher::new(checker, candidates);
|
||||
visitor::walk_body(&mut matcher, &with.body);
|
||||
let (matches, contents) = {
|
||||
let mut matcher = WriteMatcher::new(candidates);
|
||||
visitor::walk_body(&mut matcher, &with.body);
|
||||
matcher.finish()
|
||||
};
|
||||
|
||||
// All the matched operations should be reported.
|
||||
let diagnostics: Vec<Diagnostic> = matches
|
||||
.iter()
|
||||
.zip(contents)
|
||||
.map(|(open, content)| {
|
||||
Diagnostic::new(
|
||||
WriteWholeFile {
|
||||
filename: SourceCodeSnippet::from_str(&checker.generator().expr(open.filename)),
|
||||
suggestion: make_suggestion(open, content, checker.generator()),
|
||||
},
|
||||
open.item.range(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
/// AST visitor that matches `open` operations with the corresponding `write` calls.
|
||||
struct WriteMatcher<'a, 'b> {
|
||||
checker: &'a Checker<'b>,
|
||||
#[derive(Debug)]
|
||||
struct WriteMatcher<'a> {
|
||||
candidates: Vec<FileOpen<'a>>,
|
||||
matches: Vec<FileOpen<'a>>,
|
||||
contents: Vec<&'a Expr>,
|
||||
loop_counter: u32,
|
||||
}
|
||||
|
||||
impl<'a, 'b> WriteMatcher<'a, 'b> {
|
||||
fn new(checker: &'a Checker<'b>, candidates: Vec<FileOpen<'a>>) -> Self {
|
||||
impl<'a> WriteMatcher<'a> {
|
||||
fn new(candidates: Vec<FileOpen<'a>>) -> Self {
|
||||
Self {
|
||||
checker,
|
||||
candidates,
|
||||
matches: vec![],
|
||||
contents: vec![],
|
||||
loop_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(self) -> (Vec<FileOpen<'a>>, Vec<&'a Expr>) {
|
||||
(self.matches, self.contents)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for WriteMatcher<'a, '_> {
|
||||
impl<'a> Visitor<'a> for WriteMatcher<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
if matches!(stmt, ast::Stmt::While(_) | ast::Stmt::For(_)) {
|
||||
self.loop_counter += 1;
|
||||
@@ -105,16 +131,8 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> {
|
||||
.position(|open| open.is_ref(write_to))
|
||||
{
|
||||
if self.loop_counter == 0 {
|
||||
let open = self.candidates.remove(open);
|
||||
self.checker.report_diagnostic(Diagnostic::new(
|
||||
WriteWholeFile {
|
||||
filename: SourceCodeSnippet::from_str(
|
||||
&self.checker.generator().expr(open.filename),
|
||||
),
|
||||
suggestion: make_suggestion(&open, content, self.checker.generator()),
|
||||
},
|
||||
open.item.range(),
|
||||
));
|
||||
self.matches.push(self.candidates.remove(open));
|
||||
self.contents.push(content);
|
||||
} else {
|
||||
self.candidates.remove(open);
|
||||
}
|
||||
|
||||
@@ -211,9 +211,7 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like:
|
||||
context,
|
||||
checker.settings,
|
||||
);
|
||||
for diagnostic in diagnostics {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
ast::StringLikePart::Bytes(_) => {}
|
||||
ast::StringLikePart::FString(f_string) => {
|
||||
@@ -227,9 +225,7 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like:
|
||||
context,
|
||||
checker.settings,
|
||||
);
|
||||
for diagnostic in diagnostics {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
||||
}
|
||||
|
||||
let mut stopped_fixes = false;
|
||||
let mut diagnostics = vec![];
|
||||
|
||||
for parameter in function_def.parameters.iter_non_variadic_params() {
|
||||
let Some(default) = parameter.default() else {
|
||||
@@ -131,8 +132,10 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
||||
stopped_fixes |= diagnostic.fix.is_none();
|
||||
}
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to transform a `__post_init__` default argument into a
|
||||
|
||||
@@ -40,30 +40,33 @@ impl Violation for UselessTryExcept {
|
||||
|
||||
/// TRY203 (previously TRY302)
|
||||
pub(crate) fn useless_try_except(checker: &Checker, handlers: &[ExceptHandler]) {
|
||||
if handlers.iter().all(|handler| {
|
||||
let ExceptHandler::ExceptHandler(ExceptHandlerExceptHandler { name, body, .. }) = handler;
|
||||
let Some(Stmt::Raise(ast::StmtRaise {
|
||||
exc, cause: None, ..
|
||||
})) = &body.first()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if let Some(expr) = exc {
|
||||
// E.g., `except ... as e: raise e`
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = expr.as_ref() {
|
||||
if name.as_ref().is_some_and(|name| name.as_str() == id) {
|
||||
return true;
|
||||
if let Some(diagnostics) = handlers
|
||||
.iter()
|
||||
.map(|handler| {
|
||||
let ExceptHandler::ExceptHandler(ExceptHandlerExceptHandler { name, body, .. }) =
|
||||
handler;
|
||||
let Some(Stmt::Raise(ast::StmtRaise {
|
||||
exc, cause: None, ..
|
||||
})) = &body.first()
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if let Some(expr) = exc {
|
||||
// E.g., `except ... as e: raise e`
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = expr.as_ref() {
|
||||
if name.as_ref().is_some_and(|name| name.as_str() == id) {
|
||||
return Some(Diagnostic::new(UselessTryExcept, handler.range()));
|
||||
}
|
||||
}
|
||||
None
|
||||
} else {
|
||||
// E.g., `except ...: raise`
|
||||
Some(Diagnostic::new(UselessTryExcept, handler.range()))
|
||||
}
|
||||
false
|
||||
} else {
|
||||
// E.g., `except ...: raise`
|
||||
true
|
||||
}
|
||||
}) {
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
{
|
||||
// Require that all handlers are useless, but create one diagnostic per handler.
|
||||
for handler in handlers {
|
||||
checker.report_diagnostic(Diagnostic::new(UselessTryExcept, handler.range()));
|
||||
}
|
||||
checker.report_diagnostics(diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,13 +288,5 @@ match x:
|
||||
]:
|
||||
pass
|
||||
|
||||
match a, b:
|
||||
case [], []:
|
||||
...
|
||||
case [], _:
|
||||
...
|
||||
case _, []:
|
||||
...
|
||||
case _, _:
|
||||
...
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Ruff in some cases added brackets around tuples in some cases; deviating from Black.
|
||||
# Ensure we don't revert already-applied formats with the fix.
|
||||
# See https://github.com/astral-sh/ruff/pull/18147
|
||||
match a, b:
|
||||
case [[], []]:
|
||||
...
|
||||
case [[], _]:
|
||||
...
|
||||
@@ -79,26 +79,9 @@ pub(crate) enum SequenceType {
|
||||
|
||||
impl SequenceType {
|
||||
pub(crate) fn from_pattern(pattern: &PatternMatchSequence, source: &str) -> SequenceType {
|
||||
let before_first_pattern = &source[TextRange::new(
|
||||
pattern.start(),
|
||||
pattern
|
||||
.patterns
|
||||
.first()
|
||||
.map(Ranged::start)
|
||||
.unwrap_or(pattern.end()),
|
||||
)];
|
||||
let after_last_patttern = &source[TextRange::new(
|
||||
pattern.start(),
|
||||
pattern
|
||||
.patterns
|
||||
.first()
|
||||
.map(Ranged::end)
|
||||
.unwrap_or(pattern.end()),
|
||||
)];
|
||||
|
||||
if before_first_pattern.starts_with('[') && !after_last_patttern.ends_with(',') {
|
||||
if source[pattern.range()].starts_with('[') {
|
||||
SequenceType::List
|
||||
} else if before_first_pattern.starts_with('(') {
|
||||
} else if source[pattern.range()].starts_with('(') {
|
||||
// If the pattern is empty, it must be a parenthesized tuple with no members. (This
|
||||
// branch exists to differentiate between a tuple with and without its own parentheses,
|
||||
// but a tuple without its own parentheses must have at least one member.)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/pattern_matching_trailing_comma.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## Input
|
||||
|
||||
@@ -26,7 +27,7 @@ match more := (than, one), indeed,:
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -8,7 +8,10 @@
|
||||
@@ -8,13 +8,16 @@
|
||||
pass
|
||||
|
||||
|
||||
@@ -37,7 +38,15 @@ match more := (than, one), indeed,:
|
||||
+):
|
||||
case _, (5, 6):
|
||||
pass
|
||||
case (
|
||||
- case (
|
||||
+ case [
|
||||
[[5], (6)],
|
||||
[7],
|
||||
- ):
|
||||
+ ]:
|
||||
pass
|
||||
case _:
|
||||
pass
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
@@ -59,10 +68,10 @@ match (
|
||||
):
|
||||
case _, (5, 6):
|
||||
pass
|
||||
case (
|
||||
case [
|
||||
[[5], (6)],
|
||||
[7],
|
||||
):
|
||||
]:
|
||||
pass
|
||||
case _:
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/pattern/pattern_maybe_parenthesize.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
@@ -294,15 +295,7 @@ match x:
|
||||
]:
|
||||
pass
|
||||
|
||||
match a, b:
|
||||
case [], []:
|
||||
...
|
||||
case [], _:
|
||||
...
|
||||
case _, []:
|
||||
...
|
||||
case _, _:
|
||||
...
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -599,14 +592,4 @@ match x:
|
||||
ccccccccccccccccccccccccccccccccc,
|
||||
]:
|
||||
pass
|
||||
|
||||
match a, b:
|
||||
case [], []:
|
||||
...
|
||||
case [], _:
|
||||
...
|
||||
case _, []:
|
||||
...
|
||||
case _, _:
|
||||
...
|
||||
```
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/pattern_match_regression_brackets.py
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
# Ruff in some cases added brackets around tuples in some cases; deviating from Black.
|
||||
# Ensure we don't revert already-applied formats with the fix.
|
||||
# See https://github.com/astral-sh/ruff/pull/18147
|
||||
match a, b:
|
||||
case [[], []]:
|
||||
...
|
||||
case [[], _]:
|
||||
...
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
# Ruff in some cases added brackets around tuples in some cases; deviating from Black.
|
||||
# Ensure we don't revert already-applied formats with the fix.
|
||||
# See https://github.com/astral-sh/ruff/pull/18147
|
||||
match a, b:
|
||||
case [[], []]:
|
||||
...
|
||||
case [[], _]:
|
||||
...
|
||||
```
|
||||
@@ -769,21 +769,16 @@ impl SemanticSyntaxChecker {
|
||||
// We are intentionally not inspecting the async status of the scope for now to mimic F704.
|
||||
// await-outside-async is PLE1142 instead, so we'll end up emitting both syntax errors for
|
||||
// cases that trigger F704
|
||||
|
||||
if ctx.in_function_scope() {
|
||||
return;
|
||||
}
|
||||
|
||||
if kind.is_await() {
|
||||
if ctx.in_await_allowed_context() {
|
||||
return;
|
||||
}
|
||||
// `await` is allowed at the top level of a Jupyter notebook.
|
||||
// See: https://ipython.readthedocs.io/en/stable/interactive/autoawait.html.
|
||||
if ctx.in_module_scope() && ctx.in_notebook() {
|
||||
return;
|
||||
}
|
||||
if ctx.in_await_allowed_context() {
|
||||
return;
|
||||
}
|
||||
} else if ctx.in_yield_allowed_context() {
|
||||
} else if ctx.in_function_scope() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1724,35 +1719,6 @@ pub trait SemanticSyntaxContext {
|
||||
/// See the trait-level documentation for more details.
|
||||
fn in_await_allowed_context(&self) -> bool;
|
||||
|
||||
/// Returns `true` if the visitor is currently in a context where `yield` and `yield from`
|
||||
/// expressions are allowed.
|
||||
///
|
||||
/// Yield expressions are allowed only in:
|
||||
/// 1. Function definitions
|
||||
/// 2. Lambda expressions
|
||||
///
|
||||
/// Unlike `await`, yield is not allowed in:
|
||||
/// - Comprehensions (list, set, dict)
|
||||
/// - Generator expressions
|
||||
/// - Class definitions
|
||||
///
|
||||
/// This method should traverse parent scopes to check if the closest relevant scope
|
||||
/// is a function or lambda, and that no disallowed context (class, comprehension, generator)
|
||||
/// intervenes. For example:
|
||||
///
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// yield 1 # okay, in a function
|
||||
/// lambda: (yield 1) # okay, in a lambda
|
||||
///
|
||||
/// [(yield 1) for x in range(3)] # error, in a comprehension
|
||||
/// ((yield 1) for x in range(3)) # error, in a generator expression
|
||||
/// class C:
|
||||
/// yield 1 # error, in a class within a function
|
||||
/// ```
|
||||
///
|
||||
fn in_yield_allowed_context(&self) -> bool;
|
||||
|
||||
/// Returns `true` if the visitor is currently inside of a synchronous comprehension.
|
||||
///
|
||||
/// This method is necessary because `in_async_context` only checks for the nearest, enclosing
|
||||
|
||||
@@ -556,10 +556,6 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
|
||||
true
|
||||
}
|
||||
|
||||
fn in_yield_allowed_context(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn in_generator_scope(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.11.11"
|
||||
version = "0.11.10"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
4
crates/ty/docs/cli.md
generated
4
crates/ty/docs/cli.md
generated
@@ -1,5 +1,3 @@
|
||||
<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the doc comments in 'crates/ty/src/args.rs' if you want to change anything here. -->
|
||||
|
||||
# CLI Reference
|
||||
|
||||
## ty
|
||||
@@ -53,7 +51,7 @@ ty check [OPTIONS] [PATH]...
|
||||
</dd><dt id="ty-check--output-format"><a href="#ty-check--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>The format to use for printing diagnostic messages</p>
|
||||
<p>Possible values:</p>
|
||||
<ul>
|
||||
<li><code>full</code>: Print diagnostics verbosely, with context and helpful hints [default]</li>
|
||||
<li><code>full</code>: Print diagnostics verbosely, with context and helpful hints</li>
|
||||
<li><code>concise</code>: Print diagnostics concisely, one per line</li>
|
||||
</ul></dd><dt id="ty-check--project"><a href="#ty-check--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
|
||||
<p>All <code>pyproject.toml</code> files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (<code>.venv</code>) unless the <code>venv-path</code> option is set.</p>
|
||||
|
||||
2
crates/ty/docs/configuration.md
generated
2
crates/ty/docs/configuration.md
generated
@@ -1,5 +1,3 @@
|
||||
<!-- WARNING: This file is auto-generated (cargo dev generate-all). Update the doc comments on the 'Options' struct in 'crates/ty_project/src/metadata/options.rs' if you want to change anything here. -->
|
||||
|
||||
# Configuration
|
||||
#### `respect-ignore-files`
|
||||
|
||||
|
||||
250
crates/ty/docs/rules.md
generated
250
crates/ty/docs/rules.md
generated
@@ -1,5 +1,3 @@
|
||||
<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the lint-declarations in 'crates/ty_python_semantic/src/types/diagnostic.rs' if you want to change anything here. -->
|
||||
|
||||
# Rules
|
||||
|
||||
## `byte-string-type-annotation`
|
||||
@@ -52,7 +50,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L91)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L87)
|
||||
</details>
|
||||
|
||||
## `conflicting-argument-forms`
|
||||
@@ -83,7 +81,7 @@ f(int) # error
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L118)
|
||||
</details>
|
||||
|
||||
## `conflicting-declarations`
|
||||
@@ -113,7 +111,7 @@ a = 1
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L144)
|
||||
</details>
|
||||
|
||||
## `conflicting-metaclass`
|
||||
@@ -144,7 +142,7 @@ class C(A, B): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169)
|
||||
</details>
|
||||
|
||||
## `cyclic-class-definition`
|
||||
@@ -175,7 +173,30 @@ class B(A): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L195)
|
||||
</details>
|
||||
|
||||
## `division-by-zero`
|
||||
|
||||
**Default level**: error
|
||||
|
||||
<details>
|
||||
<summary>detects division by zero</summary>
|
||||
|
||||
### What it does
|
||||
It detects division by zero.
|
||||
|
||||
### Why is this bad?
|
||||
Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
5 / 0
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L221)
|
||||
</details>
|
||||
|
||||
## `duplicate-base`
|
||||
@@ -201,7 +222,7 @@ class B(A, A): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239)
|
||||
</details>
|
||||
|
||||
## `escape-character-in-forward-annotation`
|
||||
@@ -338,7 +359,7 @@ TypeError: multiple bases have instance lay-out conflict
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260)
|
||||
</details>
|
||||
|
||||
## `inconsistent-mro`
|
||||
@@ -367,7 +388,7 @@ class C(A, B): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L346)
|
||||
</details>
|
||||
|
||||
## `index-out-of-bounds`
|
||||
@@ -392,7 +413,7 @@ t[3] # IndexError: tuple index out of range
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L370)
|
||||
</details>
|
||||
|
||||
## `invalid-argument-type`
|
||||
@@ -418,7 +439,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L407)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L390)
|
||||
</details>
|
||||
|
||||
## `invalid-assignment`
|
||||
@@ -445,7 +466,7 @@ a: int = ''
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430)
|
||||
</details>
|
||||
|
||||
## `invalid-attribute-access`
|
||||
@@ -478,7 +499,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1395)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1336)
|
||||
</details>
|
||||
|
||||
## `invalid-base`
|
||||
@@ -486,22 +507,13 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
**Default level**: error
|
||||
|
||||
<details>
|
||||
<summary>detects class bases that will cause the class definition to raise an exception at runtime</summary>
|
||||
<summary>detects invalid bases in class definitions</summary>
|
||||
|
||||
### What it does
|
||||
Checks for class definitions that have bases which are not instances of `type`.
|
||||
|
||||
### Why is this bad?
|
||||
Class definitions with bases like this will lead to `TypeError` being raised at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
class A(42): ... # error: [invalid-base]
|
||||
```
|
||||
TODO #14889
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L469)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L452)
|
||||
</details>
|
||||
|
||||
## `invalid-context-manager`
|
||||
@@ -527,7 +539,7 @@ with 1:
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L461)
|
||||
</details>
|
||||
|
||||
## `invalid-declaration`
|
||||
@@ -555,7 +567,7 @@ a: str
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L482)
|
||||
</details>
|
||||
|
||||
## `invalid-exception-caught`
|
||||
@@ -596,7 +608,7 @@ except ZeroDivisionError:
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L505)
|
||||
</details>
|
||||
|
||||
## `invalid-generic-class`
|
||||
@@ -627,7 +639,7 @@ class C[U](Generic[T]): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L600)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541)
|
||||
</details>
|
||||
|
||||
## `invalid-legacy-type-variable`
|
||||
@@ -660,7 +672,7 @@ def f(t: TypeVar("U")): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
|
||||
</details>
|
||||
|
||||
## `invalid-metaclass`
|
||||
@@ -692,7 +704,7 @@ class B(metaclass=f): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L616)
|
||||
</details>
|
||||
|
||||
## `invalid-overload`
|
||||
@@ -740,7 +752,7 @@ def foo(x: int) -> int: ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L643)
|
||||
</details>
|
||||
|
||||
## `invalid-parameter-default`
|
||||
@@ -765,7 +777,7 @@ def f(a: int = ''): ...
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L745)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L686)
|
||||
</details>
|
||||
|
||||
## `invalid-protocol`
|
||||
@@ -798,7 +810,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L318)
|
||||
</details>
|
||||
|
||||
## `invalid-raise`
|
||||
@@ -846,7 +858,7 @@ def g():
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L706)
|
||||
</details>
|
||||
|
||||
## `invalid-return-type`
|
||||
@@ -870,7 +882,7 @@ def func() -> int:
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L411)
|
||||
</details>
|
||||
|
||||
## `invalid-super-argument`
|
||||
@@ -914,7 +926,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)`
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L749)
|
||||
</details>
|
||||
|
||||
## `invalid-syntax-in-forward-annotation`
|
||||
@@ -954,7 +966,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L595)
|
||||
</details>
|
||||
|
||||
## `invalid-type-checking-constant`
|
||||
@@ -983,7 +995,7 @@ TYPE_CHECKING = ''
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L788)
|
||||
</details>
|
||||
|
||||
## `invalid-type-form`
|
||||
@@ -1012,7 +1024,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812)
|
||||
</details>
|
||||
|
||||
## `invalid-type-variable-constraints`
|
||||
@@ -1046,7 +1058,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L836)
|
||||
</details>
|
||||
|
||||
## `missing-argument`
|
||||
@@ -1070,7 +1082,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L865)
|
||||
</details>
|
||||
|
||||
## `no-matching-overload`
|
||||
@@ -1098,7 +1110,7 @@ func("string") # error: [no-matching-overload]
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L943)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L884)
|
||||
</details>
|
||||
|
||||
## `non-subscriptable`
|
||||
@@ -1121,7 +1133,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L907)
|
||||
</details>
|
||||
|
||||
## `not-iterable`
|
||||
@@ -1146,7 +1158,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925)
|
||||
</details>
|
||||
|
||||
## `parameter-already-assigned`
|
||||
@@ -1172,7 +1184,7 @@ f(1, x=2) # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L976)
|
||||
</details>
|
||||
|
||||
## `raw-string-type-annotation`
|
||||
@@ -1231,7 +1243,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312)
|
||||
</details>
|
||||
|
||||
## `subclass-of-final-class`
|
||||
@@ -1259,7 +1271,7 @@ class B(A): ... # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1067)
|
||||
</details>
|
||||
|
||||
## `too-many-positional-arguments`
|
||||
@@ -1285,7 +1297,7 @@ f("foo") # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1112)
|
||||
</details>
|
||||
|
||||
## `type-assertion-failure`
|
||||
@@ -1312,7 +1324,7 @@ def _(x: int):
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1090)
|
||||
</details>
|
||||
|
||||
## `unavailable-implicit-super-arguments`
|
||||
@@ -1356,7 +1368,7 @@ class A:
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1133)
|
||||
</details>
|
||||
|
||||
## `unknown-argument`
|
||||
@@ -1382,7 +1394,7 @@ f(x=1, y=2) # Error raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1190)
|
||||
</details>
|
||||
|
||||
## `unresolved-attribute`
|
||||
@@ -1409,7 +1421,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1270)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1211)
|
||||
</details>
|
||||
|
||||
## `unresolved-import`
|
||||
@@ -1433,7 +1445,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233)
|
||||
</details>
|
||||
|
||||
## `unresolved-reference`
|
||||
@@ -1457,7 +1469,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1252)
|
||||
</details>
|
||||
|
||||
## `unsupported-bool-conversion`
|
||||
@@ -1493,7 +1505,7 @@ b1 < b2 < b1 # exception raised here
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1004)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945)
|
||||
</details>
|
||||
|
||||
## `unsupported-operator`
|
||||
@@ -1520,7 +1532,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271)
|
||||
</details>
|
||||
|
||||
## `zero-stepsize-in-slice`
|
||||
@@ -1544,7 +1556,25 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293)
|
||||
</details>
|
||||
|
||||
## `call-possibly-unbound-method`
|
||||
|
||||
**Default level**: warn
|
||||
|
||||
<details>
|
||||
<summary>detects calls to possibly unbound methods</summary>
|
||||
|
||||
### What it does
|
||||
Checks for calls to possibly unbound methods.
|
||||
|
||||
### Why is this bad?
|
||||
Calling an unbound method will raise an `AttributeError` at runtime.
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-possibly-unbound-method)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L105)
|
||||
</details>
|
||||
|
||||
## `invalid-ignore-comment`
|
||||
@@ -1600,38 +1630,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1056)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-implicit-call`
|
||||
|
||||
**Default level**: warn
|
||||
|
||||
<details>
|
||||
<summary>detects implicit calls to possibly unbound methods</summary>
|
||||
|
||||
### What it does
|
||||
Checks for implicit calls to possibly unbound methods.
|
||||
|
||||
### Why is this bad?
|
||||
Expressions such as `x[y]` and `x * y` call methods
|
||||
under the hood (`__getitem__` and `__mul__` respectively).
|
||||
Calling an unbound method will raise an `AttributeError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
import datetime
|
||||
|
||||
class A:
|
||||
if datetime.date.today().weekday() != 6:
|
||||
def __getitem__(self, v): ...
|
||||
|
||||
A()[0] # TypeError: 'A' object is not subscriptable
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L109)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L997)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-import`
|
||||
@@ -1662,7 +1661,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1078)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1019)
|
||||
</details>
|
||||
|
||||
## `redundant-cast`
|
||||
@@ -1688,7 +1687,7 @@ cast(int, f()) # Redundant
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1364)
|
||||
</details>
|
||||
|
||||
## `undefined-reveal`
|
||||
@@ -1711,7 +1710,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172)
|
||||
</details>
|
||||
|
||||
## `unknown-rule`
|
||||
@@ -1744,67 +1743,6 @@ a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L40)
|
||||
</details>
|
||||
|
||||
## `unsupported-base`
|
||||
|
||||
**Default level**: warn
|
||||
|
||||
<details>
|
||||
<summary>detects class bases that are unsupported as ty could not feasibly calculate the class's MRO</summary>
|
||||
|
||||
### What it does
|
||||
Checks for class definitions that have bases which are unsupported by ty.
|
||||
|
||||
### Why is this bad?
|
||||
If a class has a base that is an instance of a complex type such as a union type,
|
||||
ty will not be able to resolve the [method resolution order] (MRO) for the class.
|
||||
This will lead to an inferior understanding of your codebase and unpredictable
|
||||
type-checking behavior.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
import datetime
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
if datetime.date.today().weekday() != 6:
|
||||
C = A
|
||||
else:
|
||||
C = B
|
||||
|
||||
class D(C): ... # error: [unsupported-base]
|
||||
```
|
||||
|
||||
[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L487)
|
||||
</details>
|
||||
|
||||
## `division-by-zero`
|
||||
|
||||
**Default level**: ignore
|
||||
|
||||
<details>
|
||||
<summary>detects division by zero</summary>
|
||||
|
||||
### What it does
|
||||
It detects division by zero.
|
||||
|
||||
### Why is this bad?
|
||||
Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
|
||||
### Examples
|
||||
```python
|
||||
5 / 0
|
||||
```
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238)
|
||||
</details>
|
||||
|
||||
## `possibly-unresolved-reference`
|
||||
|
||||
**Default level**: ignore
|
||||
@@ -1829,7 +1767,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1045)
|
||||
</details>
|
||||
|
||||
## `unused-ignore-comment`
|
||||
|
||||
@@ -283,7 +283,7 @@ impl clap::Args for RulesArg {
|
||||
/// The diagnostic output format.
|
||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
|
||||
pub enum OutputFormat {
|
||||
/// Print diagnostics verbosely, with context and helpful hints \[default\].
|
||||
/// Print diagnostics verbosely, with context and helpful hints.
|
||||
///
|
||||
/// Diagnostic messages may include additional context and
|
||||
/// annotations on the input to help understand the message.
|
||||
|
||||
@@ -241,73 +241,6 @@ fn config_override_python_platform() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> {
|
||||
let case = TestCase::with_files([
|
||||
(
|
||||
"pyproject.toml",
|
||||
r#"
|
||||
[tool.ty.environment]
|
||||
python-version = "3.8"
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"test.py",
|
||||
r#"
|
||||
aiter
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-reference]: Name `aiter` used when not defined
|
||||
--> test.py:2:1
|
||||
|
|
||||
2 | aiter
|
||||
| ^^^^^
|
||||
|
|
||||
info: `aiter` was added as a builtin in Python 3.10
|
||||
info: Python 3.8 was assumed when resolving types
|
||||
--> pyproject.toml:3:18
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
3 | python-version = "3.8"
|
||||
| ^^^^^ Python 3.8 assumed due to this configuration setting
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"#);
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-reference]: Name `aiter` used when not defined
|
||||
--> test.py:2:1
|
||||
|
|
||||
2 | aiter
|
||||
| ^^^^^
|
||||
|
|
||||
info: `aiter` was added as a builtin in Python 3.10
|
||||
info: Python 3.9 was assumed when resolving types because it was specified on the command line
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paths specified on the CLI are relative to the current working directory and not the project root.
|
||||
///
|
||||
/// We test this by adding an extra search path from the CLI to the libs directory when
|
||||
@@ -454,11 +387,22 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Assert that there's an `unresolved-reference` diagnostic (error).
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
// Assert that there's an `unresolved-reference` diagnostic (error)
|
||||
// and a `division-by-zero` diagnostic (error).
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
|
||||
--> test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
info: rule `division-by-zero` is enabled by default
|
||||
|
||||
error[unresolved-reference]: Name `prin` used when not defined
|
||||
--> test.py:7:1
|
||||
|
|
||||
@@ -469,17 +413,17 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"###);
|
||||
");
|
||||
|
||||
case.write_file(
|
||||
"pyproject.toml",
|
||||
r#"
|
||||
[tool.ty.rules]
|
||||
division-by-zero = "warn" # promote to warn
|
||||
division-by-zero = "warn" # demote to warn
|
||||
unresolved-reference = "ignore"
|
||||
"#,
|
||||
)?;
|
||||
@@ -524,9 +468,9 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Assert that there's an `unresolved-reference` diagnostic (error)
|
||||
// and an unresolved-import (error) diagnostic by default.
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
// Assert that there's an `unresolved-reference` diagnostic (error),
|
||||
// a `division-by-zero` (error) and a unresolved-import (error) diagnostic by default.
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -541,6 +485,18 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
|
||||
--> test.py:4:5
|
||||
|
|
||||
2 | import does_not_exit
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
| ^^^^^
|
||||
5 |
|
||||
6 | for a in range(0, int(y)):
|
||||
|
|
||||
info: rule `division-by-zero` is enabled by default
|
||||
|
||||
error[unresolved-reference]: Name `prin` used when not defined
|
||||
--> test.py:9:1
|
||||
|
|
||||
@@ -551,11 +507,11 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 2 diagnostics
|
||||
Found 3 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"###);
|
||||
");
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
case
|
||||
@@ -619,11 +575,22 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Assert that there's a `unresolved-reference` diagnostic (error) by default.
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
// Assert that there's a `unresolved-reference` diagnostic (error)
|
||||
// and a `division-by-zero` (error) by default.
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
|
||||
--> test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
info: rule `division-by-zero` is enabled by default
|
||||
|
||||
error[unresolved-reference]: Name `prin` used when not defined
|
||||
--> test.py:7:1
|
||||
|
|
||||
@@ -634,11 +601,11 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"###);
|
||||
");
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
case
|
||||
@@ -647,6 +614,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
.arg("unresolved-reference")
|
||||
.arg("--warn")
|
||||
.arg("division-by-zero")
|
||||
// Override the error severity with warning
|
||||
.arg("--ignore")
|
||||
.arg("unresolved-reference"),
|
||||
@r"
|
||||
@@ -1135,10 +1103,18 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
case.command(),
|
||||
@r###"
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
|
||||
--> project/main.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0 # error: division-by-zero
|
||||
| ^^^^^
|
||||
|
|
||||
info: rule `division-by-zero` is enabled by default
|
||||
|
||||
error[unresolved-import]: Cannot resolve imported module `main2`
|
||||
--> project/other.py:2:6
|
||||
|
|
||||
@@ -1159,11 +1135,11 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
Found 2 diagnostics
|
||||
Found 3 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"###
|
||||
"
|
||||
);
|
||||
|
||||
// Now check only the `tests` and `other.py` files.
|
||||
@@ -1454,10 +1430,10 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_config_args_overrides_ty_toml() -> anyhow::Result<()> {
|
||||
fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> {
|
||||
let case = TestCase::with_files(vec![
|
||||
(
|
||||
"ty.toml",
|
||||
"knot.toml",
|
||||
r#"
|
||||
[terminal]
|
||||
error-on-warning = true
|
||||
@@ -1465,27 +1441,6 @@ fn cli_config_args_overrides_ty_toml() -> anyhow::Result<()> {
|
||||
),
|
||||
("test.py", r"print(x) # [unresolved-reference]"),
|
||||
])?;
|
||||
|
||||
// Exit code of 1 due to the setting in `ty.toml`
|
||||
assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning[unresolved-reference]: Name `x` used when not defined
|
||||
--> test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| ^
|
||||
|
|
||||
info: rule `unresolved-reference` was selected on the command line
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
");
|
||||
|
||||
// Exit code of 0 because the `ty.toml` setting is overwritten by `--config`
|
||||
assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=false"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
||||
@@ -1444,11 +1444,13 @@ mod unix {
|
||||
)
|
||||
.expect("Expected bar.baz to exist in site-packages.");
|
||||
let baz_project = case.project_path("bar/baz.py");
|
||||
let baz_file = baz.file().unwrap();
|
||||
|
||||
assert_eq!(source_text(case.db(), baz_file).as_str(), "def baz(): ...");
|
||||
assert_eq!(
|
||||
baz_file.path(case.db()).as_system_path(),
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
"def baz(): ..."
|
||||
);
|
||||
assert_eq!(
|
||||
baz.file().path(case.db()).as_system_path(),
|
||||
Some(&*baz_project)
|
||||
);
|
||||
|
||||
@@ -1463,7 +1465,7 @@ mod unix {
|
||||
case.apply_changes(changes);
|
||||
|
||||
assert_eq!(
|
||||
source_text(case.db(), baz_file).as_str(),
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
"def baz(): print('Version 2')"
|
||||
);
|
||||
|
||||
@@ -1476,7 +1478,7 @@ mod unix {
|
||||
case.apply_changes(changes);
|
||||
|
||||
assert_eq!(
|
||||
source_text(case.db(), baz_file).as_str(),
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
"def baz(): print('Version 3')"
|
||||
);
|
||||
|
||||
@@ -1522,7 +1524,6 @@ mod unix {
|
||||
&ModuleName::new_static("bar.baz").unwrap(),
|
||||
)
|
||||
.expect("Expected bar.baz to exist in site-packages.");
|
||||
let baz_file = baz.file().unwrap();
|
||||
let bar_baz = case.project_path("bar/baz.py");
|
||||
|
||||
let patched_bar_baz = case.project_path("patched/bar/baz.py");
|
||||
@@ -1533,8 +1534,11 @@ mod unix {
|
||||
"def baz(): ..."
|
||||
);
|
||||
|
||||
assert_eq!(source_text(case.db(), baz_file).as_str(), "def baz(): ...");
|
||||
assert_eq!(baz_file.path(case.db()).as_system_path(), Some(&*bar_baz));
|
||||
assert_eq!(
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
"def baz(): ..."
|
||||
);
|
||||
assert_eq!(baz.file().path(case.db()).as_system_path(), Some(&*bar_baz));
|
||||
|
||||
case.assert_indexed_project_files([patched_bar_baz_file]);
|
||||
|
||||
@@ -1563,7 +1567,7 @@ mod unix {
|
||||
let patched_baz_text = source_text(case.db(), patched_bar_baz_file);
|
||||
let did_update_patched_baz = patched_baz_text.as_str() == "def baz(): print('Version 2')";
|
||||
|
||||
let bar_baz_text = source_text(case.db(), baz_file);
|
||||
let bar_baz_text = source_text(case.db(), baz.file());
|
||||
let did_update_bar_baz = bar_baz_text.as_str() == "def baz(): print('Version 2')";
|
||||
|
||||
assert!(
|
||||
@@ -1646,7 +1650,7 @@ mod unix {
|
||||
"def baz(): ..."
|
||||
);
|
||||
assert_eq!(
|
||||
baz.file().unwrap().path(case.db()).as_system_path(),
|
||||
baz.file().path(case.db()).as_system_path(),
|
||||
Some(&*baz_original)
|
||||
);
|
||||
|
||||
|
||||
@@ -158,9 +158,9 @@ mod tests {
|
||||
use crate::db::tests::TestDb;
|
||||
|
||||
use ruff_db::system::{DbWithWritableSystem, SystemPathBuf};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_python_semantic::{
|
||||
Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionWithSource,
|
||||
SearchPathSettings,
|
||||
Program, ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings,
|
||||
};
|
||||
|
||||
pub(super) fn inlay_hint_test(source: &str) -> InlayHintTest {
|
||||
@@ -191,7 +191,7 @@ mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_version: PythonVersion::latest_ty(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
|
||||
@@ -13,10 +13,11 @@ pub use hover::hover;
|
||||
pub use inlay_hints::inlay_hints;
|
||||
pub use markup::MarkupKind;
|
||||
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ty_python_semantic::types::{Type, TypeDefinition};
|
||||
|
||||
/// Information associated with a text range.
|
||||
@@ -187,10 +188,7 @@ impl HasNavigationTargets for Type<'_> {
|
||||
|
||||
impl HasNavigationTargets for TypeDefinition<'_> {
|
||||
fn navigation_targets(&self, db: &dyn Db) -> NavigationTargets {
|
||||
let Some(full_range) = self.full_range(db.upcast()) else {
|
||||
return NavigationTargets::empty();
|
||||
};
|
||||
|
||||
let full_range = self.full_range(db.upcast());
|
||||
NavigationTargets::single(NavigationTarget {
|
||||
file: full_range.file(),
|
||||
focus_range: self.focus_range(db.upcast()).unwrap_or(full_range).range(),
|
||||
@@ -207,10 +205,10 @@ mod tests {
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig};
|
||||
use ruff_db::files::{File, system_path_to_file};
|
||||
use ruff_db::system::{DbWithWritableSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_python_semantic::{
|
||||
Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionWithSource,
|
||||
SearchPathSettings,
|
||||
Program, ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings,
|
||||
};
|
||||
|
||||
pub(super) fn cursor_test(source: &str) -> CursorTest {
|
||||
@@ -230,7 +228,7 @@ mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_version: PythonVersion::latest_ty(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
|
||||
@@ -25,17 +25,9 @@ pub trait Db: SemanticDb + Upcast<dyn SemanticDb> {
|
||||
#[derive(Clone)]
|
||||
pub struct ProjectDatabase {
|
||||
project: Option<Project>,
|
||||
files: Files,
|
||||
|
||||
// IMPORTANT: Never return clones of `system` outside `ProjectDatabase` (only return references)
|
||||
// or the "trick" to get a mutable `Arc` in `Self::system_mut` is no longer guaranteed to work.
|
||||
system: Arc<dyn System + Send + Sync + RefUnwindSafe>,
|
||||
|
||||
// IMPORTANT: This field must be the last because we use `zalsa_mut` (drops all other storage references)
|
||||
// to drop all other references to the database, which gives us exclusive access to other `Arc`s stored on this db.
|
||||
// However, for this to work it's important that the `storage` is dropped AFTER any `Arc` that
|
||||
// we try to mutably borrow using `Arc::get_mut` (like `system`).
|
||||
storage: salsa::Storage<ProjectDatabase>,
|
||||
files: Files,
|
||||
system: Arc<dyn System + Send + Sync + RefUnwindSafe>,
|
||||
}
|
||||
|
||||
impl ProjectDatabase {
|
||||
|
||||
@@ -663,11 +663,10 @@ mod tests {
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ty_python_semantic::types::check_types;
|
||||
use ty_python_semantic::{
|
||||
Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings,
|
||||
};
|
||||
use ty_python_semantic::{Program, ProgramSettings, PythonPlatform, SearchPathSettings};
|
||||
|
||||
#[test]
|
||||
fn check_file_skips_type_checking_when_file_cant_be_read() -> ruff_db::system::Result<()> {
|
||||
@@ -678,7 +677,7 @@ mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_version: PythonVersion::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings::new(vec![SystemPathBuf::from(".")]),
|
||||
},
|
||||
|
||||
@@ -10,10 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
use ty_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource, PythonVersionWithSource,
|
||||
SearchPathSettings,
|
||||
};
|
||||
use ty_python_semantic::{ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings};
|
||||
|
||||
use super::settings::{Settings, TerminalSettings};
|
||||
|
||||
@@ -96,17 +93,8 @@ impl Options {
|
||||
let python_version = self
|
||||
.environment
|
||||
.as_ref()
|
||||
.and_then(|env| env.python_version.as_ref())
|
||||
.map(|ranged_version| PythonVersionWithSource {
|
||||
version: **ranged_version,
|
||||
source: match ranged_version.source() {
|
||||
ValueSource::Cli => PythonVersionSource::Cli,
|
||||
ValueSource::File(path) => {
|
||||
PythonVersionSource::File(path.clone(), ranged_version.range())
|
||||
}
|
||||
},
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.and_then(|env| env.python_version.as_deref().copied())
|
||||
.unwrap_or(PythonVersion::latest_ty());
|
||||
let python_platform = self
|
||||
.environment
|
||||
.as_ref()
|
||||
|
||||
@@ -113,35 +113,3 @@ def _(
|
||||
reveal_type(f) # revealed: Unknown
|
||||
reveal_type(g) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Diagnostics for common errors
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
### Module-literal used when you meant to use a class from that module
|
||||
|
||||
It's pretty common in Python to accidentally use a module-literal type in a type expression when you
|
||||
*meant* to use a class by the same name that comes from that module. We emit a nice subdiagnostic
|
||||
for this case:
|
||||
|
||||
`foo.py`:
|
||||
|
||||
```py
|
||||
import datetime
|
||||
|
||||
def f(x: datetime): ... # error: [invalid-type-form]
|
||||
```
|
||||
|
||||
`PIL/Image.py`:
|
||||
|
||||
```py
|
||||
class Image: ...
|
||||
```
|
||||
|
||||
`bar.py`:
|
||||
|
||||
```py
|
||||
from PIL import Image
|
||||
|
||||
def g(x: Image): ... # error: [invalid-type-form]
|
||||
```
|
||||
|
||||
@@ -81,22 +81,23 @@ import typing
|
||||
|
||||
class ListSubclass(typing.List): ...
|
||||
|
||||
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
reveal_type(ListSubclass.__mro__)
|
||||
|
||||
class DictSubclass(typing.Dict): ...
|
||||
|
||||
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# TODO: should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
reveal_type(DictSubclass.__mro__)
|
||||
|
||||
class SetSubclass(typing.Set): ...
|
||||
|
||||
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
reveal_type(SetSubclass.__mro__)
|
||||
|
||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||
|
||||
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
reveal_type(FrozenSetSubclass.__mro__)
|
||||
|
||||
####################
|
||||
@@ -105,26 +106,30 @@ reveal_type(FrozenSetSubclass.__mro__)
|
||||
|
||||
class ChainMapSubclass(typing.ChainMap): ...
|
||||
|
||||
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# TODO: should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
reveal_type(ChainMapSubclass.__mro__)
|
||||
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# TODO: Should have one `Generic[]` element, not three(!)
|
||||
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], typing.Generic[_T], <class 'object'>]
|
||||
reveal_type(CounterSubclass.__mro__)
|
||||
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# TODO: Should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
reveal_type(DefaultDictSubclass.__mro__)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
||||
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
reveal_type(DequeSubclass.__mro__)
|
||||
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
# TODO: Should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
reveal_type(OrderedDictSubclass.__mro__)
|
||||
```
|
||||
|
||||
@@ -16,7 +16,8 @@ Alias: TypeAlias = int
|
||||
|
||||
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
|
||||
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
|
||||
# TODO: this is not correct. At runtime, this is `type` (`Alias` is just `<class 'int'>`).
|
||||
reveal_type(Alias) # revealed: typing.TypeAliasType
|
||||
|
||||
def g() -> TypeGuard[int]: ...
|
||||
def h() -> TypeIs[int]: ...
|
||||
|
||||
@@ -777,30 +777,6 @@ reveal_type(C.variable_with_class_default1) # revealed: str
|
||||
reveal_type(c_instance.variable_with_class_default1) # revealed: str
|
||||
```
|
||||
|
||||
#### Descriptor attributes as class variables
|
||||
|
||||
Whether they are explicitly qualified as `ClassVar`, or just have a class level default, we treat
|
||||
descriptor attributes as class variables. This test mainly makes sure that we do *not* treat them as
|
||||
instance variables. This would lead to a different outcome, since the `__get__` method would not be
|
||||
called (the descriptor protocol is not invoked for instance variables).
|
||||
|
||||
```py
|
||||
from typing import ClassVar
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance, owner) -> int:
|
||||
return 42
|
||||
|
||||
class C:
|
||||
a: ClassVar[Descriptor]
|
||||
b: Descriptor = Descriptor()
|
||||
c: ClassVar[Descriptor] = Descriptor()
|
||||
|
||||
reveal_type(C().a) # revealed: int
|
||||
reveal_type(C().b) # revealed: int
|
||||
reveal_type(C().c) # revealed: int
|
||||
```
|
||||
|
||||
### Inheritance of class/instance attributes
|
||||
|
||||
#### Instance variable defined in a base class
|
||||
@@ -1760,9 +1736,9 @@ reveal_type(False.real) # revealed: Literal[0]
|
||||
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
|
||||
|
||||
```py
|
||||
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
|
||||
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[Buffer], /) -> bytes
|
||||
reveal_type(b"foo".join)
|
||||
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
|
||||
# revealed: bound method Literal[b"foo"].endswith(suffix: Buffer | tuple[Buffer, ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
|
||||
reveal_type(b"foo".endswith)
|
||||
```
|
||||
|
||||
|
||||
@@ -313,8 +313,7 @@ reveal_type(A() + "foo") # revealed: A
|
||||
reveal_type("foo" + A()) # revealed: A
|
||||
|
||||
reveal_type(A() + b"foo") # revealed: A
|
||||
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
|
||||
reveal_type(b"foo" + A()) # revealed: bytes
|
||||
reveal_type(b"foo" + A()) # revealed: A
|
||||
|
||||
reveal_type(A() + ()) # revealed: A
|
||||
reveal_type(() + A()) # revealed: A
|
||||
|
||||
@@ -54,10 +54,8 @@ reveal_type(2**largest_u32) # revealed: int
|
||||
|
||||
def variable(x: int):
|
||||
reveal_type(x**2) # revealed: int
|
||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
||||
reveal_type(2**x) # revealed: int
|
||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
||||
reveal_type(x**x) # revealed: int
|
||||
reveal_type(2**x) # revealed: Any
|
||||
reveal_type(x**x) # revealed: Any
|
||||
```
|
||||
|
||||
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but
|
||||
@@ -72,34 +70,6 @@ reveal_type(2 ** (-1)) # revealed: float
|
||||
reveal_type((-1) ** (-1)) # revealed: float
|
||||
```
|
||||
|
||||
## Division and Modulus
|
||||
|
||||
Division works differently in Python than in Rust. If the result is negative and there is a
|
||||
remainder, the division rounds down (instead of towards zero). The remainder needs to be adjusted to
|
||||
compensate so that `(lhs // rhs) * rhs + (lhs % rhs) == lhs`:
|
||||
|
||||
```py
|
||||
reveal_type(256 % 129) # revealed: Literal[127]
|
||||
reveal_type(-256 % 129) # revealed: Literal[2]
|
||||
reveal_type(256 % -129) # revealed: Literal[-2]
|
||||
reveal_type(-256 % -129) # revealed: Literal[-127]
|
||||
|
||||
reveal_type(129 % 16) # revealed: Literal[1]
|
||||
reveal_type(-129 % 16) # revealed: Literal[15]
|
||||
reveal_type(129 % -16) # revealed: Literal[-15]
|
||||
reveal_type(-129 % -16) # revealed: Literal[-1]
|
||||
|
||||
reveal_type(10 // 8) # revealed: Literal[1]
|
||||
reveal_type(-10 // 8) # revealed: Literal[-2]
|
||||
reveal_type(10 // -8) # revealed: Literal[-2]
|
||||
reveal_type(-10 // -8) # revealed: Literal[1]
|
||||
|
||||
reveal_type(10 // 6) # revealed: Literal[1]
|
||||
reveal_type(-10 // 6) # revealed: Literal[-2]
|
||||
reveal_type(10 // -6) # revealed: Literal[-2]
|
||||
reveal_type(-10 // -6) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Division by Zero
|
||||
|
||||
This error is really outside the current Python type system, because e.g. `int.__truediv__` and
|
||||
|
||||
@@ -158,10 +158,10 @@ def _(flag: bool) -> None:
|
||||
def __new__(cls):
|
||||
return object.__new__(cls)
|
||||
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
# error: [call-possibly-unbound-method]
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
# error: [call-possibly-unbound-method]
|
||||
# error: [too-many-positional-arguments]
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
```
|
||||
|
||||
@@ -112,7 +112,7 @@ def _(flag: bool):
|
||||
|
||||
this_fails = ThisFails()
|
||||
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
# error: [call-possibly-unbound-method]
|
||||
reveal_type(this_fails[0]) # revealed: Unknown | str
|
||||
```
|
||||
|
||||
@@ -236,40 +236,6 @@ def _(flag: bool):
|
||||
return str(key)
|
||||
|
||||
c = C()
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
# error: [call-possibly-unbound-method]
|
||||
reveal_type(c[0]) # revealed: str
|
||||
```
|
||||
|
||||
## Dunder methods cannot be looked up on instances
|
||||
|
||||
Class-level annotations with no value assigned are considered instance-only, and aren't available as
|
||||
dunder methods:
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class C:
|
||||
__call__: Callable[..., None]
|
||||
|
||||
# error: [call-non-callable]
|
||||
C()()
|
||||
|
||||
# error: [invalid-assignment]
|
||||
_: Callable[..., None] = C()
|
||||
```
|
||||
|
||||
And of course the same is true if we have only an implicit assignment inside a method:
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.__call__ = lambda *a, **kw: None
|
||||
|
||||
# error: [call-non-callable]
|
||||
C()()
|
||||
|
||||
# error: [invalid-assignment]
|
||||
_: Callable[..., None] = C()
|
||||
```
|
||||
|
||||
@@ -109,50 +109,23 @@ def _(o: object):
|
||||
|
||||
### Unsupported operators for positive contributions
|
||||
|
||||
Raise an error if the given operator is unsupported for all positive contributions to the
|
||||
intersection type:
|
||||
|
||||
```py
|
||||
class NonContainer1: ...
|
||||
class NonContainer2: ...
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, NonContainer1):
|
||||
if isinstance(x, NonContainer2):
|
||||
reveal_type(x) # revealed: NonContainer1 & NonContainer2
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer1`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
Do not raise an error if at least one of the positive contributions to the intersection type support
|
||||
the operator:
|
||||
Raise an error if any of the positive contributions to the intersection type are unsupported for the
|
||||
given operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool:
|
||||
return False
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, NonContainer1):
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer2):
|
||||
reveal_type(x) # revealed: NonContainer1 & Container & NonContainer2
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & NonContainer
|
||||
|
||||
Do also raise an error if the intersection has no positive contributions at all, unless the operator
|
||||
is supported on `object`:
|
||||
|
||||
```py
|
||||
def _(x: object):
|
||||
if not isinstance(x, NonContainer1):
|
||||
reveal_type(x) # revealed: ~NonContainer1
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `object`, in comparing `Literal[2]` with `~NonContainer1`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
|
||||
reveal_type(2 is x) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
### Unsupported operators for negative contributions
|
||||
|
||||
@@ -369,65 +369,7 @@ To do
|
||||
|
||||
### `frozen`
|
||||
|
||||
If true (the default is False), assigning to fields will generate a diagnostic.
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MyFrozenClass:
|
||||
x: int
|
||||
|
||||
frozen_instance = MyFrozenClass(1)
|
||||
frozen_instance.x = 2 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
If `__setattr__()` or `__delattr__()` is defined in the class, we should emit a diagnostic.
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MyFrozenClass:
|
||||
x: int
|
||||
|
||||
# TODO: Emit a diagnostic here
|
||||
def __setattr__(self, name: str, value: object) -> None: ...
|
||||
|
||||
# TODO: Emit a diagnostic here
|
||||
def __delattr__(self, name: str) -> None: ...
|
||||
```
|
||||
|
||||
This also works for generic dataclasses:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MyFrozenGeneric[T]:
|
||||
x: T
|
||||
|
||||
frozen_instance = MyFrozenGeneric[int](1)
|
||||
frozen_instance.x = 2 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
When attempting to mutate an unresolved attribute on a frozen dataclass, only `unresolved-attribute`
|
||||
is emitted:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MyFrozenClass: ...
|
||||
|
||||
frozen = MyFrozenClass()
|
||||
frozen.x = 2 # error: [unresolved-attribute]
|
||||
```
|
||||
To do
|
||||
|
||||
### `match_args`
|
||||
|
||||
|
||||
@@ -298,25 +298,25 @@ python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
def _():
|
||||
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
|
||||
type X[T: (yield 1)] = int
|
||||
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
|
||||
# error: [invalid-syntax] "`yield` statement outside of a function"
|
||||
type X[T: (yield 1)] = int
|
||||
|
||||
def _():
|
||||
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
|
||||
type Y = (yield 1)
|
||||
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
|
||||
# error: [invalid-syntax] "`yield` statement outside of a function"
|
||||
type Y = (yield 1)
|
||||
|
||||
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "named expression cannot be used within a generic definition"
|
||||
def f[T](x: int) -> (y := 3):
|
||||
return x
|
||||
|
||||
def _():
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
|
||||
class C[T]((yield from [object])):
|
||||
pass
|
||||
# error: [invalid-syntax] "`yield from` statement outside of a function"
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
|
||||
class C[T]((yield from [object])):
|
||||
pass
|
||||
```
|
||||
|
||||
## `await` outside async function
|
||||
|
||||
@@ -397,19 +397,3 @@ async def i() -> typing.AsyncIterable:
|
||||
async def j() -> str: # error: [invalid-return-type]
|
||||
yield 42
|
||||
```
|
||||
|
||||
## Diagnostics for `invalid-return-type` on non-protocol subclasses of protocol classes
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
We emit a nice subdiagnostic in this situation explaining the probable error here:
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol
|
||||
|
||||
class Abstract(Protocol):
|
||||
def method(self) -> str: ...
|
||||
|
||||
class Concrete(Abstract):
|
||||
def method(self) -> str: ... # error: [invalid-return-type]
|
||||
```
|
||||
|
||||
@@ -24,7 +24,9 @@ class:
|
||||
|
||||
```py
|
||||
class Bad(Generic[T], Generic[T]): ... # error: [duplicate-base]
|
||||
class AlsoBad(Generic[T], Generic[S]): ... # error: [duplicate-base]
|
||||
|
||||
# TODO: should emit an error (fails at runtime)
|
||||
class AlsoBad(Generic[T], Generic[S]): ...
|
||||
```
|
||||
|
||||
You cannot use the same typevar more than once.
|
||||
|
||||
@@ -29,10 +29,8 @@ import parent.child.two
|
||||
`from.py`
|
||||
|
||||
```py
|
||||
from parent.child import one, two
|
||||
|
||||
reveal_type(one) # revealed: <module 'parent.child.one'>
|
||||
reveal_type(two) # revealed: <module 'parent.child.two'>
|
||||
# TODO: This should not be an error
|
||||
from parent.child import one, two # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## Regular package in namespace package
|
||||
@@ -107,42 +105,3 @@ reveal_type(x) # revealed: Unknown | Literal["module"]
|
||||
|
||||
import foo.bar # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## `from` import with namespace package
|
||||
|
||||
Regression test for <https://github.com/astral-sh/ty/issues/363>
|
||||
|
||||
`google/cloud/pubsub_v1/__init__.py`:
|
||||
|
||||
```py
|
||||
class PublisherClient: ...
|
||||
```
|
||||
|
||||
```py
|
||||
from google.cloud import pubsub_v1
|
||||
|
||||
reveal_type(pubsub_v1.PublisherClient) # revealed: <class 'PublisherClient'>
|
||||
```
|
||||
|
||||
## `from` root importing sub-packages
|
||||
|
||||
Regresssion test for <https://github.com/astral-sh/ty/issues/375>
|
||||
|
||||
`opentelemetry/trace/__init__.py`:
|
||||
|
||||
```py
|
||||
class Trace: ...
|
||||
```
|
||||
|
||||
`opentelemetry/metrics/__init__.py`:
|
||||
|
||||
```py
|
||||
class Metric: ...
|
||||
```
|
||||
|
||||
```py
|
||||
from opentelemetry import trace, metrics
|
||||
|
||||
reveal_type(trace) # revealed: <module 'opentelemetry.trace'>
|
||||
reveal_type(metrics) # revealed: <module 'opentelemetry.metrics'>
|
||||
```
|
||||
|
||||
@@ -173,7 +173,7 @@ if hasattr(DoesNotExist, "__mro__"):
|
||||
if not isinstance(DoesNotExist, type):
|
||||
reveal_type(DoesNotExist) # revealed: Unknown & ~type
|
||||
|
||||
class Foo(DoesNotExist): ... # error: [unsupported-base]
|
||||
class Foo(DoesNotExist): ... # error: [invalid-base]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
@@ -232,15 +232,11 @@ reveal_type(AA.__mro__) # revealed: tuple[<class 'AA'>, <class 'Z'>, Unknown, <
|
||||
|
||||
## `__bases__` includes a `Union`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
We don't support union types in a class's bases; a base must resolve to a single `ClassType`. If we
|
||||
find a union type in a class's bases, we infer the class's `__mro__` as being
|
||||
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
@@ -254,7 +250,7 @@ else:
|
||||
|
||||
reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
|
||||
# error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
# error: 11 [invalid-base] "Invalid class base with type `<class 'A'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Foo(x): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
@@ -263,8 +259,8 @@ reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'obje
|
||||
## `__bases__` is a union of a dynamic type and valid bases
|
||||
|
||||
If a dynamic type such as `Any` or `Unknown` is one of the elements in the union, and all other
|
||||
types *would be* valid class bases, we do not emit an `invalid-base` or `unsupported-base`
|
||||
diagnostic, and we use the dynamic type as a base to prevent further downstream errors.
|
||||
types *would be* valid class bases, we do not emit an `invalid-base` diagnostic and use the dynamic
|
||||
type as a base to prevent further downstream errors.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
@@ -303,8 +299,8 @@ else:
|
||||
reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
reveal_type(y) # revealed: <class 'C'> | <class 'D'>
|
||||
|
||||
# error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
# error: 14 [unsupported-base] "Unsupported class base with type `<class 'C'> | <class 'D'>`"
|
||||
# error: 11 [invalid-base] "Invalid class base with type `<class 'A'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
# error: 14 [invalid-base] "Invalid class base with type `<class 'C'> | <class 'D'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Foo(x, y): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
@@ -325,7 +321,7 @@ if returns_bool():
|
||||
else:
|
||||
foo = object
|
||||
|
||||
# error: 21 [unsupported-base] "Unsupported class base with type `<class 'Y'> | <class 'object'>`"
|
||||
# error: 21 [invalid-base] "Invalid class base with type `<class 'Y'> | <class 'object'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class PossibleError(foo, X): ...
|
||||
|
||||
reveal_type(PossibleError.__mro__) # revealed: tuple[<class 'PossibleError'>, Unknown, <class 'object'>]
|
||||
@@ -343,47 +339,12 @@ else:
|
||||
# revealed: tuple[<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>] | tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(B.__mro__)
|
||||
|
||||
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`"
|
||||
# error: 12 [invalid-base] "Invalid class base with type `<class 'B'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Z(A, B): ...
|
||||
|
||||
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
## `__bases__` lists that include objects that are not instances of `type`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
class Foo(2): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
A base that is not an instance of `type` but does have an `__mro_entries__` method will not raise an
|
||||
exception at runtime, so we issue `unsupported-base` rather than `invalid-base`:
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]:
|
||||
return ()
|
||||
|
||||
class Bar(Foo()): ... # error: [unsupported-base]
|
||||
```
|
||||
|
||||
But for objects that have badly defined `__mro_entries__`, `invalid-base` is emitted rather than
|
||||
`unsupported-base`:
|
||||
|
||||
```py
|
||||
class Bad1:
|
||||
def __mro_entries__(self, bases, extra_arg):
|
||||
return ()
|
||||
|
||||
class Bad2:
|
||||
def __mro_entries__(self, bases) -> int:
|
||||
return 42
|
||||
|
||||
class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
## `__bases__` lists with duplicate bases
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
@@ -527,45 +488,6 @@ reveal_type(unknown_object) # revealed: Unknown
|
||||
reveal_type(unknown_object.__mro__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## MROs of classes that use multiple inheritance with generic aliases and subscripted `Generic`
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar, Iterator
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class peekable(Generic[T], Iterator[T]): ...
|
||||
|
||||
# revealed: tuple[<class 'peekable[Unknown]'>, <class 'Iterator[T]'>, <class 'Iterable[T]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(peekable.__mro__)
|
||||
|
||||
class peekable2(Iterator[T], Generic[T]): ...
|
||||
|
||||
# revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T]'>, <class 'Iterable[T]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(peekable2.__mro__)
|
||||
|
||||
class Base: ...
|
||||
class Intermediate(Base, Generic[T]): ...
|
||||
class Sub(Intermediate[T], Base): ...
|
||||
|
||||
# revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T]'>, <class 'Base'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Sub.__mro__)
|
||||
```
|
||||
|
||||
## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol, TypeVar, Generic
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Foo(Protocol): ...
|
||||
class Bar(Protocol[T]): ...
|
||||
class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
|
||||
```
|
||||
|
||||
## Classes that inherit from themselves
|
||||
|
||||
These are invalid, but we need to be able to handle them gracefully without panicking.
|
||||
|
||||
@@ -146,13 +146,11 @@ def _(flag: bool):
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else "a"
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
# error: [invalid-argument-type]
|
||||
if isinstance(x, "a"):
|
||||
reveal_type(x) # revealed: Literal[1, "a"]
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
# error: [invalid-argument-type]
|
||||
if isinstance(x, "int"):
|
||||
reveal_type(x) # revealed: Literal[1, "a"]
|
||||
```
|
||||
|
||||
@@ -214,8 +214,7 @@ def flag() -> bool:
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
# error: [invalid-argument-type]
|
||||
if issubclass(t, "str"):
|
||||
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ def f() -> None:
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
reveal_type(IntOrStr.__value__) # revealed: @Todo(Support for `typing.TypeAlias`)
|
||||
reveal_type(IntOrStr.__value__) # revealed: Any
|
||||
```
|
||||
|
||||
## Invalid assignment
|
||||
|
||||
@@ -67,10 +67,12 @@ It's an error to include both bare `Protocol` and subscripted `Protocol[]` in th
|
||||
simultaneously:
|
||||
|
||||
```py
|
||||
class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base]
|
||||
# TODO: should emit a `[duplicate-bases]` error here:
|
||||
class DuplicateBases(Protocol, Protocol[T]):
|
||||
x: T
|
||||
|
||||
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>]
|
||||
# TODO: should not have `Protocol` or `Generic` multiple times
|
||||
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, typing.Protocol, typing.Generic, typing.Protocol[T], typing.Generic[T], <class 'object'>]
|
||||
reveal_type(DuplicateBases.__mro__)
|
||||
```
|
||||
|
||||
@@ -241,7 +243,8 @@ def f(
|
||||
Nonetheless, `Protocol` can still be used as the second argument to `issubclass()` at runtime:
|
||||
|
||||
```py
|
||||
# Could also be `Literal[True]`, but `bool` is fine:
|
||||
# TODO: Should be `Literal[True]`, but `bool` is also fine
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(issubclass(MyProtocol, Protocol)) # revealed: bool
|
||||
```
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ error[unsupported-operator]: Operator `|` is unsupported between objects of type
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
info: Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later
|
||||
info: Python 3.9 was assumed when resolving types because it was specified on the command line
|
||||
info: The inferred target version of your project is Python 3.9
|
||||
info: If using a pyproject.toml file, consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid.md - Tests for invalid types in type expressions - Diagnostics for common errors - Module-literal used when you meant to use a class from that module
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## foo.py
|
||||
|
||||
```
|
||||
1 | import datetime
|
||||
2 |
|
||||
3 | def f(x: datetime): ... # error: [invalid-type-form]
|
||||
```
|
||||
|
||||
## PIL/Image.py
|
||||
|
||||
```
|
||||
1 | class Image: ...
|
||||
```
|
||||
|
||||
## bar.py
|
||||
|
||||
```
|
||||
1 | from PIL import Image
|
||||
2 |
|
||||
3 | def g(x: Image): ... # error: [invalid-type-form]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-type-form]: Variable of type `<module 'datetime'>` is not allowed in a type expression
|
||||
--> src/foo.py:3:10
|
||||
|
|
||||
1 | import datetime
|
||||
2 |
|
||||
3 | def f(x: datetime): ... # error: [invalid-type-form]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
info: Did you mean to use the module's member `datetime.datetime` instead?
|
||||
info: rule `invalid-type-form` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-type-form]: Variable of type `<module 'PIL.Image'>` is not allowed in a type expression
|
||||
--> src/bar.py:3:10
|
||||
|
|
||||
1 | from PIL import Image
|
||||
2 |
|
||||
3 | def g(x: Image): ... # error: [invalid-type-form]
|
||||
| ^^^^^
|
||||
|
|
||||
info: Did you mean to use the module's member `Image.Image` instead?
|
||||
info: rule `invalid-type-form` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: mro.md - Method Resolution Order tests - Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol, TypeVar, Generic
|
||||
2 |
|
||||
3 | T = TypeVar("T")
|
||||
4 |
|
||||
5 | class Foo(Protocol): ...
|
||||
6 | class Bar(Protocol[T]): ...
|
||||
7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], <class 'Foo'>, <class 'Bar[T]'>]`
|
||||
--> src/mdtest_snippet.py:7:1
|
||||
|
|
||||
5 | class Foo(Protocol): ...
|
||||
6 | class Bar(Protocol[T]): ...
|
||||
7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `inconsistent-mro` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: mro.md - Method Resolution Order tests - `__bases__` includes a `Union`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def returns_bool() -> bool:
|
||||
4 | return True
|
||||
5 |
|
||||
6 | class A: ...
|
||||
7 | class B: ...
|
||||
8 |
|
||||
9 | if returns_bool():
|
||||
10 | x = A
|
||||
11 | else:
|
||||
12 | x = B
|
||||
13 |
|
||||
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
15 |
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:13
|
||||
|
|
||||
12 | x = B
|
||||
13 |
|
||||
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
| ^ `<class 'A'> | <class 'B'>`
|
||||
15 |
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
|
||||
--> src/mdtest_snippet.py:17:11
|
||||
|
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
| ^
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
|
||||
info: ty cannot resolve a consistent MRO for class `Foo` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:19:13
|
||||
|
|
||||
17 | class Foo(x): ...
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
|
||||
|
|
||||
|
||||
```
|
||||
@@ -1,97 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: mro.md - Method Resolution Order tests - `__bases__` lists that include objects that are not instances of `type`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Foo(2): ... # error: [invalid-base]
|
||||
2 | class Foo:
|
||||
3 | def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]:
|
||||
4 | return ()
|
||||
5 |
|
||||
6 | class Bar(Foo()): ... # error: [unsupported-base]
|
||||
7 | class Bad1:
|
||||
8 | def __mro_entries__(self, bases, extra_arg):
|
||||
9 | return ()
|
||||
10 |
|
||||
11 | class Bad2:
|
||||
12 | def __mro_entries__(self, bases) -> int:
|
||||
13 | return 42
|
||||
14 |
|
||||
15 | class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
16 | class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid class base with type `Literal[2]`
|
||||
--> src/mdtest_snippet.py:1:11
|
||||
|
|
||||
1 | class Foo(2): ... # error: [invalid-base]
|
||||
| ^
|
||||
2 | class Foo:
|
||||
3 | def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]:
|
||||
|
|
||||
info: Definition of class `Foo` will raise `TypeError` at runtime
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning[unsupported-base]: Unsupported class base with type `Foo`
|
||||
--> src/mdtest_snippet.py:6:11
|
||||
|
|
||||
4 | return ()
|
||||
5 |
|
||||
6 | class Bar(Foo()): ... # error: [unsupported-base]
|
||||
| ^^^^^
|
||||
7 | class Bad1:
|
||||
8 | def __mro_entries__(self, bases, extra_arg):
|
||||
|
|
||||
info: ty cannot resolve a consistent MRO for class `Bar` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid class base with type `Bad1`
|
||||
--> src/mdtest_snippet.py:15:15
|
||||
|
|
||||
13 | return 42
|
||||
14 |
|
||||
15 | class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
| ^^^^^^
|
||||
16 | class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
|
|
||||
info: Definition of class `BadSub1` will raise `TypeError` at runtime
|
||||
info: An instance type is only a valid class base if it has a valid `__mro_entries__` method
|
||||
info: Type `Bad1` has an `__mro_entries__` method, but it cannot be called with the expected arguments
|
||||
info: Expected a signature at least as permissive as `def __mro_entries__(self, bases: tuple[type, ...], /) -> tuple[type, ...]`
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid class base with type `Bad2`
|
||||
--> src/mdtest_snippet.py:16:15
|
||||
|
|
||||
15 | class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
16 | class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Definition of class `BadSub2` will raise `TypeError` at runtime
|
||||
info: An instance type is only a valid class base if it has a valid `__mro_entries__` method
|
||||
info: Type `Bad2` has an `__mro_entries__` method, but it does not return a tuple of types
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: return_type.md - Function return type - Diagnostics for `invalid-return-type` on non-protocol subclasses of protocol classes
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol
|
||||
2 |
|
||||
3 | class Abstract(Protocol):
|
||||
4 | def method(self) -> str: ...
|
||||
5 |
|
||||
6 | class Concrete(Abstract):
|
||||
7 | def method(self) -> str: ... # error: [invalid-return-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `str`
|
||||
--> src/mdtest_snippet.py:7:25
|
||||
|
|
||||
6 | class Concrete(Abstract):
|
||||
7 | def method(self) -> str: ... # error: [invalid-return-type]
|
||||
| ^^^
|
||||
|
|
||||
info: Only functions in stub files, methods on protocol classes, or methods with `@abstractmethod` are permitted to have empty bodies
|
||||
info: Class `Concrete` has `typing.Protocol` in its MRO, but it is not a protocol class
|
||||
info: Only classes that directly inherit from `typing.Protocol` or `typing_extensions.Protocol` are considered protocol classes
|
||||
--> src/mdtest_snippet.py:6:7
|
||||
|
|
||||
4 | def method(self) -> str: ...
|
||||
5 |
|
||||
6 | class Concrete(Abstract):
|
||||
| ^^^^^^^^^^^^^^^^^^ `Protocol` not present in `Concrete`'s immediate bases
|
||||
7 | def method(self) -> str: ... # error: [invalid-return-type]
|
||||
|
|
||||
info: See https://typing.python.org/en/latest/spec/protocol.html#
|
||||
info: rule `invalid-return-type` is enabled by default
|
||||
|
||||
```
|
||||
@@ -25,7 +25,8 @@ error[unresolved-reference]: Name `aiter` used when not defined
|
||||
| ^^^^^
|
||||
|
|
||||
info: `aiter` was added as a builtin in Python 3.10
|
||||
info: Python 3.9 was assumed when resolving types because it was specified on the command line
|
||||
info: The inferred target version of your project is Python 3.9
|
||||
info: If using a pyproject.toml file, consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -16,7 +16,7 @@ class Foo[T]: ...
|
||||
class Bar(Foo[Bar]): ...
|
||||
|
||||
reveal_type(Bar) # revealed: <class 'Bar'>
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, <class 'object'>]
|
||||
```
|
||||
|
||||
## Access to attributes declared in stubs
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
```py
|
||||
b = b"\x00abc\xff"
|
||||
|
||||
reveal_type(b[0]) # revealed: Literal[0]
|
||||
reveal_type(b[1]) # revealed: Literal[97]
|
||||
reveal_type(b[4]) # revealed: Literal[255]
|
||||
reveal_type(b[0]) # revealed: Literal[b"\x00"]
|
||||
reveal_type(b[1]) # revealed: Literal[b"a"]
|
||||
reveal_type(b[4]) # revealed: Literal[b"\xff"]
|
||||
|
||||
reveal_type(b[-1]) # revealed: Literal[255]
|
||||
reveal_type(b[-2]) # revealed: Literal[99]
|
||||
reveal_type(b[-5]) # revealed: Literal[0]
|
||||
reveal_type(b[-1]) # revealed: Literal[b"\xff"]
|
||||
reveal_type(b[-2]) # revealed: Literal[b"c"]
|
||||
reveal_type(b[-5]) # revealed: Literal[b"\x00"]
|
||||
|
||||
reveal_type(b[False]) # revealed: Literal[0]
|
||||
reveal_type(b[True]) # revealed: Literal[97]
|
||||
reveal_type(b[False]) # revealed: Literal[b"\x00"]
|
||||
reveal_type(b[True]) # revealed: Literal[b"a"]
|
||||
|
||||
x = b[5] # error: [index-out-of-bounds] "Index 5 is out of bounds for bytes literal `Literal[b"\x00abc\xff"]` with length 5"
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user