Compare commits
38 Commits
cjm/generi
...
david/stab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e83ce9e5d | ||
|
|
d998bea5de | ||
|
|
3a76557be2 | ||
|
|
2a217e80ca | ||
|
|
030a16cb5f | ||
|
|
0590b38214 | ||
|
|
8104b1e83b | ||
|
|
cf70c7863c | ||
|
|
faf54c0181 | ||
|
|
451c5db7a3 | ||
|
|
bd5b7f415f | ||
|
|
0230cbac2c | ||
|
|
2e94d37275 | ||
|
|
1e4377c9c6 | ||
|
|
1b4f7de840 | ||
|
|
9b52ae8991 | ||
|
|
97d7b46936 | ||
|
|
1eab59e681 | ||
|
|
e7f97a3e4b | ||
|
|
d17557f0ae | ||
|
|
8cbd433a31 | ||
|
|
65e48cb439 | ||
|
|
301d9985d8 | ||
|
|
cfbb914100 | ||
|
|
fe653de3dd | ||
|
|
a9f7521944 | ||
|
|
f8890b70c3 | ||
|
|
142c1bc760 | ||
|
|
c0f22928bd | ||
|
|
5bf5f3682a | ||
|
|
5913997c72 | ||
|
|
00f672a83b | ||
|
|
68b0386007 | ||
|
|
0ae07cdd1f | ||
|
|
0fb94c052e | ||
|
|
55df9271ba | ||
|
|
f301931159 | ||
|
|
7e9b0df18a |
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,8 +1,9 @@
|
||||
<!--
|
||||
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:
|
||||
Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following:
|
||||
|
||||
- Does this pull request include a summary of the change? (See below.)
|
||||
- Does this pull request include a descriptive title?
|
||||
- Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull
|
||||
requests.)
|
||||
- Does this pull request include references to any relevant issues?
|
||||
-->
|
||||
|
||||
|
||||
2
.github/workflows/mypy_primer.yaml
vendored
2
.github/workflows/mypy_primer.yaml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
echo "Project selector: $PRIMER_SELECTOR"
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@4b15cf3b07db69db67bbfaebfffb2a8a28040933" \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@968b2b61c05f84462d6fcc78d2f5205bbb8b98c2" \
|
||||
mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker ty \
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This guide is for Ruff. If you're looking to contribute to ty, please see [the ty contributing
|
||||
> guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md).
|
||||
|
||||
## The Basics
|
||||
|
||||
Ruff welcomes contributions in the form of pull requests.
|
||||
|
||||
25
Cargo.lock
generated
25
Cargo.lock
generated
@@ -478,7 +478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -487,7 +487,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -904,7 +904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1485,7 +1485,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.0",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1549,7 +1549,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3231,6 +3231,12 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-stable-hash"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
@@ -3241,7 +3247,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3254,7 +3260,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3640,7 +3646,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.2",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4153,6 +4159,7 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-stable-hash",
|
||||
"salsa",
|
||||
"serde",
|
||||
"smallvec",
|
||||
@@ -4638,7 +4645,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -125,6 +125,7 @@ rand = { version = "0.9.0" }
|
||||
rayon = { version = "1.10.0" }
|
||||
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 = "7edce6e248f35c8114b4b021cdb474a3fb2813b3" }
|
||||
schemars = { version = "0.8.16" }
|
||||
|
||||
@@ -255,7 +255,7 @@ indent-width = 4
|
||||
target-version = "py39"
|
||||
|
||||
[lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
|
||||
|
||||
@@ -59,40 +59,7 @@ type KeyDiagnosticFields = (
|
||||
Severity,
|
||||
);
|
||||
|
||||
// left: [
|
||||
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to function `skip_until` is incorrect", Error),
|
||||
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to function `skip_until` is incorrect", Error),
|
||||
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to function `skip_until` is incorrect", Error),
|
||||
// ]
|
||||
//right: [
|
||||
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to this function is incorrect", Error),
|
||||
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to this function is incorrect", Error),
|
||||
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to this function is incorrect", Error),
|
||||
// ]
|
||||
|
||||
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[
|
||||
(
|
||||
DiagnosticId::lint("invalid-argument-type"),
|
||||
Some("/src/tomllib/_parser.py"),
|
||||
Some(8224..8254),
|
||||
"Argument to function `skip_until` is incorrect",
|
||||
Severity::Error,
|
||||
),
|
||||
(
|
||||
DiagnosticId::lint("invalid-argument-type"),
|
||||
Some("/src/tomllib/_parser.py"),
|
||||
Some(16914..16948),
|
||||
"Argument to function `skip_until` is incorrect",
|
||||
Severity::Error,
|
||||
),
|
||||
(
|
||||
DiagnosticId::lint("invalid-argument-type"),
|
||||
Some("/src/tomllib/_parser.py"),
|
||||
Some(17319..17363),
|
||||
"Argument to function `skip_until` is incorrect",
|
||||
Severity::Error,
|
||||
),
|
||||
];
|
||||
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[];
|
||||
|
||||
fn tomllib_path(file: &TestFile) -> SystemPathBuf {
|
||||
SystemPathBuf::from("src").join(file.name())
|
||||
@@ -334,6 +301,62 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("ty_micro[many_tuple_assignments]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is a micro benchmark, but it is effectively identical to a code sample
|
||||
// observed in https://github.com/astral-sh/ty/issues/362
|
||||
setup_micro_case(
|
||||
r#"
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = ()
|
||||
if flag():
|
||||
t += (1,)
|
||||
if flag():
|
||||
t += (2,)
|
||||
if flag():
|
||||
t += (3,)
|
||||
if flag():
|
||||
t += (4,)
|
||||
if flag():
|
||||
t += (5,)
|
||||
if flag():
|
||||
t += (6,)
|
||||
if flag():
|
||||
t += (7,)
|
||||
if flag():
|
||||
t += (8,)
|
||||
if flag():
|
||||
t += (9,)
|
||||
if flag():
|
||||
t += (10,)
|
||||
if flag():
|
||||
t += (11,)
|
||||
|
||||
# Perform some kind of operation on the union type
|
||||
print(1 in t)
|
||||
"#,
|
||||
)
|
||||
},
|
||||
|case| {
|
||||
let Case { db, .. } = case;
|
||||
let result = db.check().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(check_file, benchmark_cold, benchmark_incremental);
|
||||
criterion_group!(micro, benchmark_many_string_assignments);
|
||||
criterion_group!(
|
||||
micro,
|
||||
benchmark_many_string_assignments,
|
||||
benchmark_many_tuple_assignments
|
||||
);
|
||||
criterion_main!(check_file, micro);
|
||||
|
||||
@@ -179,7 +179,7 @@ fn generate_command<'a>(output: &mut String, command: &'a Command, parents: &mut
|
||||
let id = format!("{name_key}--{}", arg.get_id());
|
||||
output.push_str(&format!("<dt id=\"{id}\">"));
|
||||
output.push_str(&format!(
|
||||
"<a href=\"#{id}\"<code>{}</code></a>",
|
||||
"<a href=\"#{id}\"><code>{}</code></a>",
|
||||
arg.get_id().to_string().to_uppercase(),
|
||||
));
|
||||
output.push_str("</dt>");
|
||||
|
||||
@@ -24,9 +24,6 @@ from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
from airflow.datasets import DatasetAliasEvent
|
||||
from airflow.hooks.base_hook import BaseHook
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.providers.mysql.datasets import mysql
|
||||
from airflow.providers.postgres.datasets import postgres
|
||||
from airflow.providers.trino.datasets import trino
|
||||
from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
||||
from airflow.sensors.base_sensor_operator import BaseSensorOperator
|
||||
from airflow.triggers.external_task import TaskStateTrigger
|
||||
@@ -78,14 +75,6 @@ BaseHook()
|
||||
# airflow.operators.subdag.*
|
||||
SubDagOperator()
|
||||
|
||||
# airflow.providers.mysql
|
||||
mysql.sanitize_uri
|
||||
|
||||
# airflow.providers.postgres
|
||||
postgres.sanitize_uri
|
||||
|
||||
# airflow.providers.trino
|
||||
trino.sanitize_uri
|
||||
|
||||
# airflow.secrets
|
||||
# get_connection
|
||||
@@ -155,3 +144,18 @@ should_hide_value_for_key
|
||||
from airflow.operators.python import get_current_context
|
||||
|
||||
get_current_context()
|
||||
|
||||
# airflow.providers.mysql
|
||||
from airflow.providers.mysql.datasets.mysql import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
# airflow.providers.postgres
|
||||
from airflow.providers.postgres.datasets.postgres import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
# airflow.providers.trino
|
||||
from airflow.providers.trino.datasets.trino import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
@@ -12,3 +12,38 @@ def test_xxx(_fixture): # Error arg
|
||||
|
||||
def test_xxx(*, _fixture): # Error kwonly
|
||||
pass
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17599
|
||||
|
||||
@pytest.mark.parametrize("_foo", [1, 2, 3])
|
||||
def test_thingy(_foo): # Ok defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"_test_input,_expected",
|
||||
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
|
||||
)
|
||||
def test_eval(_test_input, _expected): # OK defined in parametrize
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("_foo", [1, 2, 3])
|
||||
def test_thingy2(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(["_foo", "_bar"], [1, 2, 3])
|
||||
def test_thingy3(_foo, _bar): # OK defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(("_foo"), [1, 2, 3])
|
||||
def test_thingy4(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize([" _foo", " _bar "], [1, 2, 3])
|
||||
def test_thingy5(_foo, _bar): # OK defined in parametrize
|
||||
pass
|
||||
|
||||
x = "other"
|
||||
@pytest.mark.parametrize(x, [1, 2, 3])
|
||||
def test_thingy5(_foo, _bar): # known false negative, we don't try to resolve variables
|
||||
pass
|
||||
|
||||
@@ -110,3 +110,23 @@ b"TesT.WwW.ExamplE.CoM".split(b".")
|
||||
# https://github.com/astral-sh/ruff/issues/18042
|
||||
print("a,b".rsplit(","))
|
||||
print("a,b,c".rsplit(",", 1))
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18069
|
||||
|
||||
print("".split(maxsplit=0))
|
||||
print("".split(sep=None, maxsplit=0))
|
||||
print(" ".split(maxsplit=0))
|
||||
print(" ".split(sep=None, maxsplit=0))
|
||||
print(" x ".split(maxsplit=0))
|
||||
print(" x ".split(sep=None, maxsplit=0))
|
||||
print(" x ".split(maxsplit=0))
|
||||
print(" x ".split(sep=None, maxsplit=0))
|
||||
print("".rsplit(maxsplit=0))
|
||||
print("".rsplit(sep=None, maxsplit=0))
|
||||
print(" ".rsplit(maxsplit=0))
|
||||
print(" ".rsplit(sep=None, maxsplit=0))
|
||||
print(" x ".rsplit(maxsplit=0))
|
||||
print(" x ".rsplit(maxsplit=0))
|
||||
print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
print(" x ".rsplit(maxsplit=0))
|
||||
print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
@@ -6,14 +6,16 @@ from .mmap import error
|
||||
raise error
|
||||
|
||||
# Testing the modules
|
||||
import socket, mmap, select
|
||||
import socket, mmap, select, resource
|
||||
raise socket.error
|
||||
raise mmap.error
|
||||
raise select.error
|
||||
raise resource.error
|
||||
|
||||
raise socket.error()
|
||||
raise mmap.error(1)
|
||||
raise select.error(1, 2)
|
||||
raise resource.error(1, "strerror", "filename")
|
||||
|
||||
raise socket.error(
|
||||
1,
|
||||
@@ -30,6 +32,9 @@ raise error(1)
|
||||
from select import error
|
||||
raise error(1, 2)
|
||||
|
||||
from resource import error
|
||||
raise error(1, "strerror", "filename")
|
||||
|
||||
# Testing the names
|
||||
raise EnvironmentError
|
||||
raise IOError
|
||||
|
||||
@@ -8,13 +8,20 @@ use ruff_python_semantic::SemanticModel;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum Replacement {
|
||||
// There's no replacement or suggestion other than removal
|
||||
None,
|
||||
Name(&'static str),
|
||||
// The attribute name of a class has been changed.
|
||||
AttrName(&'static str),
|
||||
// Additional information. Used when there's replacement but they're not direct mapping.
|
||||
Message(&'static str),
|
||||
// Symbols updated in Airflow 3 with replacement
|
||||
// e.g., `airflow.datasets.Dataset` to `airflow.sdk.Asset`
|
||||
AutoImport {
|
||||
module: &'static str,
|
||||
name: &'static str,
|
||||
},
|
||||
// Symbols updated in Airflow 3 with only module changed. Used when we want to match multiple names.
|
||||
// e.g., `airflow.configuration.as_dict | get` to `airflow.configuration.conf.as_dict | get`
|
||||
SourceModuleMoved {
|
||||
module: &'static str,
|
||||
name: String,
|
||||
|
||||
@@ -57,28 +57,27 @@ impl Violation for Airflow3Removal {
|
||||
} = self;
|
||||
match replacement {
|
||||
Replacement::None
|
||||
| Replacement::Name(_)
|
||||
| Replacement::AttrName(_)
|
||||
| Replacement::Message(_)
|
||||
| Replacement::AutoImport { module: _, name: _ }
|
||||
| Replacement::SourceModuleMoved { module: _, name: _ } => {
|
||||
format!("`{deprecated}` is removed in Airflow 3.0")
|
||||
}
|
||||
Replacement::Message(message) => {
|
||||
format!("`{deprecated}` is removed in Airflow 3.0; {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Airflow3Removal { replacement, .. } = self;
|
||||
match replacement {
|
||||
Replacement::Name(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::None => None,
|
||||
Replacement::AttrName(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::Message(message) => Some((*message).to_string()),
|
||||
Replacement::AutoImport { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
Replacement::SourceModuleMoved { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,22 +277,22 @@ fn check_class_attribute(checker: &Checker, attribute_expr: &ExprAttribute) {
|
||||
|
||||
let replacement = match *qualname.segments() {
|
||||
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
|
||||
"dataset_factories" => Replacement::Name("asset_factories"),
|
||||
"dataset_uri_handlers" => Replacement::Name("asset_uri_handlers"),
|
||||
"dataset_factories" => Replacement::AttrName("asset_factories"),
|
||||
"dataset_uri_handlers" => Replacement::AttrName("asset_uri_handlers"),
|
||||
"dataset_to_openlineage_converters" => {
|
||||
Replacement::Name("asset_to_openlineage_converters")
|
||||
Replacement::AttrName("asset_to_openlineage_converters")
|
||||
}
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "lineage", "hook", "DatasetLineageInfo"] => match attr.as_str() {
|
||||
"dataset" => Replacement::Name("asset"),
|
||||
"dataset" => Replacement::AttrName("asset"),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Create the `Fix` first to avoid cloning `Replacement`.
|
||||
let fix = if let Replacement::Name(name) = replacement {
|
||||
let fix = if let Replacement::AttrName(name) = replacement {
|
||||
Some(Fix::safe_edit(Edit::range_replacement(
|
||||
name.to_string(),
|
||||
attr.range(),
|
||||
@@ -466,52 +465,52 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) {
|
||||
|
||||
let replacement = match qualname.segments() {
|
||||
["airflow", "datasets", "manager", "DatasetManager"] => match attr.as_str() {
|
||||
"register_dataset_change" => Replacement::Name("register_asset_change"),
|
||||
"create_datasets" => Replacement::Name("create_assets"),
|
||||
"notify_dataset_created" => Replacement::Name("notify_asset_created"),
|
||||
"notify_dataset_changed" => Replacement::Name("notify_asset_changed"),
|
||||
"notify_dataset_alias_created" => Replacement::Name("notify_asset_alias_created"),
|
||||
"register_dataset_change" => Replacement::AttrName("register_asset_change"),
|
||||
"create_datasets" => Replacement::AttrName("create_assets"),
|
||||
"notify_dataset_created" => Replacement::AttrName("notify_asset_created"),
|
||||
"notify_dataset_changed" => Replacement::AttrName("notify_asset_changed"),
|
||||
"notify_dataset_alias_created" => Replacement::AttrName("notify_asset_alias_created"),
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "lineage", "hook", "HookLineageCollector"] => match attr.as_str() {
|
||||
"create_dataset" => Replacement::Name("create_asset"),
|
||||
"add_input_dataset" => Replacement::Name("add_input_asset"),
|
||||
"add_output_dataset" => Replacement::Name("add_output_asset"),
|
||||
"collected_datasets" => Replacement::Name("collected_assets"),
|
||||
"create_dataset" => Replacement::AttrName("create_asset"),
|
||||
"add_input_dataset" => Replacement::AttrName("add_input_asset"),
|
||||
"add_output_dataset" => Replacement::AttrName("add_output_asset"),
|
||||
"collected_datasets" => Replacement::AttrName("collected_assets"),
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "providers", "amazon", "auth_manager", "aws_auth_manager", "AwsAuthManager"] => {
|
||||
match attr.as_str() {
|
||||
"is_authorized_dataset" => Replacement::Name("is_authorized_asset"),
|
||||
"is_authorized_dataset" => Replacement::AttrName("is_authorized_asset"),
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
|
||||
"initialize_providers_dataset_uri_resources" => {
|
||||
Replacement::Name("initialize_providers_asset_uri_resources")
|
||||
Replacement::AttrName("initialize_providers_asset_uri_resources")
|
||||
}
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "secrets", "local_filesystem", "LocalFilesystemBackend"] => match attr.as_str()
|
||||
{
|
||||
"get_connections" => Replacement::Name("get_connection"),
|
||||
"get_connections" => Replacement::AttrName("get_connection"),
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "datasets", ..] | ["airflow", "Dataset"] => match attr.as_str() {
|
||||
"iter_datasets" => Replacement::Name("iter_assets"),
|
||||
"iter_dataset_aliases" => Replacement::Name("iter_asset_aliases"),
|
||||
"iter_datasets" => Replacement::AttrName("iter_assets"),
|
||||
"iter_dataset_aliases" => Replacement::AttrName("iter_asset_aliases"),
|
||||
_ => return,
|
||||
},
|
||||
segments => {
|
||||
if is_airflow_secret_backend(segments) {
|
||||
match attr.as_str() {
|
||||
"get_conn_uri" => Replacement::Name("get_conn_value"),
|
||||
"get_connections" => Replacement::Name("get_connection"),
|
||||
"get_conn_uri" => Replacement::AttrName("get_conn_value"),
|
||||
"get_connections" => Replacement::AttrName("get_connection"),
|
||||
_ => return,
|
||||
}
|
||||
} else if is_airflow_hook(segments) {
|
||||
match attr.as_str() {
|
||||
"get_connections" => Replacement::Name("get_connection"),
|
||||
"get_connections" => Replacement::AttrName("get_connection"),
|
||||
_ => return,
|
||||
}
|
||||
} else {
|
||||
@@ -520,7 +519,7 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) {
|
||||
}
|
||||
};
|
||||
// Create the `Fix` first to avoid cloning `Replacement`.
|
||||
let fix = if let Replacement::Name(name) = replacement {
|
||||
let fix = if let Replacement::AttrName(name) = replacement {
|
||||
Some(Fix::safe_edit(Edit::range_replacement(
|
||||
name.to_string(),
|
||||
attr.range(),
|
||||
@@ -566,12 +565,12 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
let replacement = match qualified_name.segments() {
|
||||
// airflow.PY\d{1,2}
|
||||
["airflow", "PY36" | "PY37" | "PY38" | "PY39" | "PY310" | "PY311" | "PY312"] => {
|
||||
Replacement::Name("sys.version_info")
|
||||
Replacement::Message("Use `sys.version_info` instead")
|
||||
}
|
||||
|
||||
// airflow.api_connexion.security
|
||||
["airflow", "api_connexion", "security", "requires_access"] => {
|
||||
Replacement::Name("airflow.api_connexion.security.requires_access_*")
|
||||
Replacement::Message("Use `airflow.api_connexion.security.requires_access_*` instead")
|
||||
}
|
||||
["airflow", "api_connexion", "security", "requires_access_dataset"] => {
|
||||
Replacement::AutoImport {
|
||||
@@ -626,9 +625,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
["airflow", "datasets", "DatasetAliasEvent"] => Replacement::None,
|
||||
|
||||
// airflow.hooks
|
||||
["airflow", "hooks", "base_hook", "BaseHook"] => {
|
||||
Replacement::Name("airflow.hooks.base.BaseHook")
|
||||
}
|
||||
["airflow", "hooks", "base_hook", "BaseHook"] => Replacement::AutoImport {
|
||||
module: "airflow.hooks.base",
|
||||
name: "BaseHook",
|
||||
},
|
||||
|
||||
// airflow.lineage.hook
|
||||
["airflow", "lineage", "hook", "DatasetLineageInfo"] => Replacement::AutoImport {
|
||||
@@ -664,9 +664,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
},
|
||||
|
||||
// airflow.notifications
|
||||
["airflow", "notifications", "basenotifier", "BaseNotifier"] => {
|
||||
Replacement::Name("airflow.sdk.BaseNotifier")
|
||||
}
|
||||
["airflow", "notifications", "basenotifier", "BaseNotifier"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "BaseNotifier",
|
||||
},
|
||||
|
||||
// airflow.operators
|
||||
["airflow", "operators", "subdag", ..] => {
|
||||
@@ -691,7 +692,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
|
||||
// airflow.sensors
|
||||
["airflow", "sensors", "base_sensor_operator", "BaseSensorOperator"] => {
|
||||
Replacement::Name("airflow.sdk.bases.sensor.BaseSensorOperator")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.sdk.bases.sensor",
|
||||
name: "BaseSensorOperator",
|
||||
}
|
||||
}
|
||||
|
||||
// airflow.timetables
|
||||
@@ -720,23 +724,36 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
|
||||
// airflow.utils.dates
|
||||
["dates", "date_range"] => Replacement::None,
|
||||
["dates", "days_ago"] => Replacement::Name("pendulum.today('UTC').add(days=-N, ...)"),
|
||||
["dates", "days_ago"] => {
|
||||
Replacement::Message("Use `pendulum.today('UTC').add(days=-N, ...)` instead")
|
||||
}
|
||||
["dates", "parse_execution_date" | "round_time" | "scale_time_units" | "infer_time_unit"] => {
|
||||
Replacement::None
|
||||
}
|
||||
|
||||
// airflow.utils.file
|
||||
["file", "TemporaryDirectory"] => Replacement::Name("tempfile.TemporaryDirectory"),
|
||||
["file", "mkdirs"] => Replacement::Name("pathlib.Path({path}).mkdir"),
|
||||
["file", "TemporaryDirectory"] => Replacement::AutoImport {
|
||||
module: "tempfile",
|
||||
name: "TemporaryDirectory",
|
||||
},
|
||||
["file", "mkdirs"] => Replacement::Message("Use `pathlib.Path({path}).mkdir` instead"),
|
||||
|
||||
// airflow.utils.helpers
|
||||
["helpers", "chain"] => Replacement::Name("airflow.sdk.chain"),
|
||||
["helpers", "cross_downstream"] => Replacement::Name("airflow.sdk.cross_downstream"),
|
||||
["helpers", "chain"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "chain",
|
||||
},
|
||||
["helpers", "cross_downstream"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "cross_downstream",
|
||||
},
|
||||
|
||||
// TODO: update it as SourceModuleMoved
|
||||
// airflow.utils.log.secrets_masker
|
||||
["log", "secrets_masker"] => {
|
||||
Replacement::Name("airflow.sdk.execution_time.secrets_masker")
|
||||
}
|
||||
["log", "secrets_masker"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk.execution_time",
|
||||
name: "secrets_masker",
|
||||
},
|
||||
|
||||
// airflow.utils.state
|
||||
["state", "SHUTDOWN" | "terminating_states"] => Replacement::None,
|
||||
@@ -751,18 +768,20 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
// airflow.www
|
||||
// TODO: www has been removed
|
||||
["airflow", "www", "auth", "has_access"] => {
|
||||
Replacement::Name("airflow.www.auth.has_access_*")
|
||||
Replacement::Message("Use `airflow.www.auth.has_access_*` instead")
|
||||
}
|
||||
["airflow", "www", "auth", "has_access_dataset"] => Replacement::AutoImport {
|
||||
module: "airflow.www.auth",
|
||||
name: "has_access_asset",
|
||||
},
|
||||
["airflow", "www", "utils", "get_sensitive_variables_fields"] => {
|
||||
Replacement::Name("airflow.utils.log.secrets_masker.get_sensitive_variables_fields")
|
||||
}
|
||||
["airflow", "www", "utils", "should_hide_value_for_key"] => {
|
||||
Replacement::Name("airflow.utils.log.secrets_masker.should_hide_value_for_key")
|
||||
}
|
||||
["airflow", "www", "utils", "get_sensitive_variables_fields"] => Replacement::AutoImport {
|
||||
module: "airflow.utils.log.secrets_masker",
|
||||
name: "get_sensitive_variables_fields",
|
||||
},
|
||||
["airflow", "www", "utils", "should_hide_value_for_key"] => Replacement::AutoImport {
|
||||
module: "airflow.utils.log.secrets_masker",
|
||||
name: "should_hide_value_for_key",
|
||||
},
|
||||
|
||||
// airflow.providers.amazon
|
||||
["airflow", "providers", "amazon", "aws", "datasets", "s3", rest] => match *rest {
|
||||
@@ -774,9 +793,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.providers.amazon.aws.assets.s3",
|
||||
name: "convert_asset_to_openlineage",
|
||||
},
|
||||
"sanitize_uri" => {
|
||||
Replacement::Name("airflow.providers.amazon.aws.assets.s3.sanitize_uri")
|
||||
}
|
||||
"sanitize_uri" => Replacement::AutoImport {
|
||||
module: "airflow.providers.amazon.aws.assets.s3",
|
||||
name: "sanitize_uri",
|
||||
},
|
||||
_ => return,
|
||||
},
|
||||
["airflow", "providers", "amazon", "aws", "auth_manager", "avp", "entities", "AvpEntities", "DATASET"] => {
|
||||
@@ -797,9 +817,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.providers.common.io.assets.file",
|
||||
name: "convert_asset_to_openlineage",
|
||||
},
|
||||
"sanitize_uri" => {
|
||||
Replacement::Name("airflow.providers.common.io.assets.file.sanitize_uri")
|
||||
}
|
||||
"sanitize_uri" => Replacement::AutoImport {
|
||||
module: "airflow.providers.common.io.assets.file",
|
||||
name: "sanitize_uri",
|
||||
},
|
||||
_ => return,
|
||||
},
|
||||
|
||||
@@ -826,20 +847,28 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.providers.google.assets.gcs",
|
||||
name: "convert_asset_to_openlineage",
|
||||
},
|
||||
["gcs", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.google.assets.gcs.sanitize_uri")
|
||||
}
|
||||
["gcs", "sanitize_uri"] => Replacement::AutoImport {
|
||||
module: "airflow.providers.google.assets.gcs",
|
||||
name: "sanitize_uri",
|
||||
},
|
||||
|
||||
_ => return,
|
||||
},
|
||||
|
||||
// airflow.providers.mysql
|
||||
["airflow", "providers", "mysql", "datasets", "mysql", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.mysql.assets.mysql.sanitize_uri")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.providers.mysql.assets.mysql",
|
||||
name: "sanitize_uri",
|
||||
}
|
||||
}
|
||||
|
||||
// airflow.providers.postgres
|
||||
["airflow", "providers", "postgres", "datasets", "postgres", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.postgres.assets.postgres.sanitize_uri")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.providers.postgres.assets.postgres",
|
||||
name: "sanitize_uri",
|
||||
}
|
||||
}
|
||||
|
||||
// airflow.providers.openlineage
|
||||
@@ -859,7 +888,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
|
||||
// airflow.providers.trino
|
||||
["airflow", "providers", "trino", "datasets", "trino", "sanitize_uri"] => {
|
||||
Replacement::Name("airflow.providers.trino.assets.trino.sanitize_uri")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.providers.trino.assets.trino",
|
||||
name: "sanitize_uri",
|
||||
}
|
||||
}
|
||||
|
||||
_ => return,
|
||||
@@ -949,7 +981,7 @@ fn diagnostic_for_argument(
|
||||
Airflow3Removal {
|
||||
deprecated: deprecated.to_string(),
|
||||
replacement: match replacement {
|
||||
Some(name) => Replacement::Name(name),
|
||||
Some(name) => Replacement::AttrName(name),
|
||||
None => Replacement::None,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -53,7 +53,8 @@ impl Violation for Airflow3SuggestedUpdate {
|
||||
} = self;
|
||||
match replacement {
|
||||
Replacement::None
|
||||
| Replacement::Name(_)
|
||||
| Replacement::AttrName(_)
|
||||
| Replacement::Message(_)
|
||||
| Replacement::AutoImport { module: _, name: _ }
|
||||
| Replacement::SourceModuleMoved { module: _, name: _ } => {
|
||||
format!(
|
||||
@@ -61,27 +62,21 @@ impl Violation for Airflow3SuggestedUpdate {
|
||||
It still works in Airflow 3.0 but is expected to be removed in a future version."
|
||||
)
|
||||
}
|
||||
Replacement::Message(message) => {
|
||||
format!(
|
||||
"`{deprecated}` is removed in Airflow 3.0; \
|
||||
It still works in Airflow 3.0 but is expected to be removed in a future version.; \
|
||||
{message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Airflow3SuggestedUpdate { replacement, .. } = self;
|
||||
match replacement {
|
||||
Replacement::Name(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::None => None,
|
||||
Replacement::AttrName(name) => Some(format!("Use `{name}` instead")),
|
||||
Replacement::Message(message) => Some((*message).to_string()),
|
||||
Replacement::AutoImport { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
Replacement::SourceModuleMoved { module, name } => {
|
||||
Some(format!("Use `{module}.{name}` instead"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +121,7 @@ fn diagnostic_for_argument(
|
||||
Airflow3SuggestedUpdate {
|
||||
deprecated: deprecated.to_string(),
|
||||
replacement: match replacement {
|
||||
Some(name) => Replacement::Name(name),
|
||||
Some(name) => Replacement::AttrName(name),
|
||||
None => Replacement::None,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0
|
||||
|
|
||||
5 | name = "test_plugin"
|
||||
6 | # --- Invalid extensions start
|
||||
@@ -10,8 +10,9 @@ AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0; This
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
9 | hooks = [PluginHook]
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0
|
||||
|
|
||||
6 | # --- Invalid extensions start
|
||||
7 | operators = [PluginOperator]
|
||||
@@ -20,8 +21,9 @@ AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0; This e
|
||||
9 | hooks = [PluginHook]
|
||||
10 | executors = [PluginExecutor]
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0
|
||||
|
|
||||
7 | operators = [PluginOperator]
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
@@ -30,8 +32,9 @@ AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0; This ext
|
||||
10 | executors = [PluginExecutor]
|
||||
11 | # --- Invalid extensions end
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0
|
||||
|
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
9 | hooks = [PluginHook]
|
||||
@@ -40,3 +43,4 @@ AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0; Thi
|
||||
11 | # --- Invalid extensions end
|
||||
12 | macros = [plugin_macro]
|
||||
|
|
||||
= help: This extension should just be imported as a regular python module.
|
||||
|
||||
@@ -258,10 +258,11 @@ AIR301_args.py:90:16: AIR301 `filename_template` is removed in Airflow 3.0
|
||||
92 | FabAuthManager(None)
|
||||
|
|
||||
|
||||
AIR301_args.py:92:15: AIR301 `appbuilder` is removed in Airflow 3.0; The constructor takes no parameter now
|
||||
AIR301_args.py:92:15: AIR301 `appbuilder` is removed in Airflow 3.0
|
||||
|
|
||||
90 | GCSTaskHandler(filename_template="/tmp/test")
|
||||
91 |
|
||||
92 | FabAuthManager(None)
|
||||
| ^^^^^^ AIR301
|
||||
|
|
||||
= help: The constructor takes no parameter now
|
||||
|
||||
@@ -1,448 +1,488 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
AIR301_names.py:56:1: AIR301 `airflow.PY36` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:1: AIR301 `airflow.PY36` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:7: AIR301 `airflow.PY37` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:7: AIR301 `airflow.PY37` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:13: AIR301 `airflow.PY38` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:13: AIR301 `airflow.PY38` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:19: AIR301 `airflow.PY39` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:19: AIR301 `airflow.PY39` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:25: AIR301 `airflow.PY310` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:25: AIR301 `airflow.PY310` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:32: AIR301 `airflow.PY311` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:32: AIR301 `airflow.PY311` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:56:39: AIR301 `airflow.PY312` is removed in Airflow 3.0
|
||||
AIR301_names.py:53:39: AIR301 `airflow.PY312` is removed in Airflow 3.0
|
||||
|
|
||||
55 | # airflow root
|
||||
56 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
52 | # airflow root
|
||||
53 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^ AIR301
|
||||
57 |
|
||||
58 | # airflow.api_connexion.security
|
||||
54 |
|
||||
55 | # airflow.api_connexion.security
|
||||
|
|
||||
= help: Use `sys.version_info` instead
|
||||
|
||||
AIR301_names.py:59:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
|
||||
AIR301_names.py:56:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
|
||||
|
|
||||
58 | # airflow.api_connexion.security
|
||||
59 | requires_access
|
||||
55 | # airflow.api_connexion.security
|
||||
56 | requires_access
|
||||
| ^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.api_connexion.security.requires_access_*` instead
|
||||
|
||||
AIR301_names.py:63:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:1: AIR301 `airflow.configuration.get` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.get` instead
|
||||
|
||||
AIR301_names.py:63:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:6: AIR301 `airflow.configuration.getboolean` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.getboolean` instead
|
||||
|
||||
AIR301_names.py:63:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:18: AIR301 `airflow.configuration.getfloat` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.getfloat` instead
|
||||
|
||||
AIR301_names.py:63:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:28: AIR301 `airflow.configuration.getint` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.getint` instead
|
||||
|
||||
AIR301_names.py:63:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:36: AIR301 `airflow.configuration.has_option` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.has_option` instead
|
||||
|
||||
AIR301_names.py:63:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:48: AIR301 `airflow.configuration.remove_option` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.remove_option` instead
|
||||
|
||||
AIR301_names.py:63:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:63: AIR301 `airflow.configuration.as_dict` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.as_dict` instead
|
||||
|
||||
AIR301_names.py:63:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0
|
||||
AIR301_names.py:60:72: AIR301 `airflow.configuration.set` is removed in Airflow 3.0
|
||||
|
|
||||
62 | # airflow.configuration
|
||||
63 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
59 | # airflow.configuration
|
||||
60 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
| ^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.configuration.conf.set` instead
|
||||
|
||||
AIR301_names.py:67:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0; The whole `airflow.contrib` module has been removed.
|
||||
AIR301_names.py:64:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0
|
||||
|
|
||||
66 | # airflow.contrib.*
|
||||
67 | AWSAthenaHook()
|
||||
63 | # airflow.contrib.*
|
||||
64 | AWSAthenaHook()
|
||||
| ^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: The whole `airflow.contrib` module has been removed.
|
||||
|
||||
AIR301_names.py:71:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
|
||||
AIR301_names.py:68:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
|
||||
|
|
||||
70 | # airflow.datasets
|
||||
71 | DatasetAliasEvent()
|
||||
67 | # airflow.datasets
|
||||
68 | DatasetAliasEvent()
|
||||
| ^^^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
|
||||
AIR301_names.py:75:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
|
||||
AIR301_names.py:72:1: AIR301 `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
|
||||
|
|
||||
74 | # airflow.hooks
|
||||
75 | BaseHook()
|
||||
71 | # airflow.hooks
|
||||
72 | BaseHook()
|
||||
| ^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.hooks.base.BaseHook` instead
|
||||
|
||||
AIR301_names.py:79:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0; The whole `airflow.subdag` module has been removed.
|
||||
AIR301_names.py:76:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0
|
||||
|
|
||||
78 | # airflow.operators.subdag.*
|
||||
79 | SubDagOperator()
|
||||
75 | # airflow.operators.subdag.*
|
||||
76 | SubDagOperator()
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
80 |
|
||||
81 | # airflow.providers.mysql
|
||||
|
|
||||
= help: The whole `airflow.subdag` module has been removed.
|
||||
|
||||
AIR301_names.py:82:7: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0
|
||||
AIR301_names.py:85:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0
|
||||
|
|
||||
81 | # airflow.providers.mysql
|
||||
82 | mysql.sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
83 |
|
||||
84 | # airflow.providers.postgres
|
||||
|
|
||||
= help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:85:10: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
84 | # airflow.providers.postgres
|
||||
85 | postgres.sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
86 |
|
||||
87 | # airflow.providers.trino
|
||||
|
|
||||
= help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:88:7: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
87 | # airflow.providers.trino
|
||||
88 | trino.sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
89 |
|
||||
90 | # airflow.secrets
|
||||
|
|
||||
= help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:96:1: AIR301 `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0
|
||||
|
|
||||
95 | # airflow.sensors.base_sensor_operator
|
||||
96 | BaseSensorOperator()
|
||||
84 | # airflow.sensors.base_sensor_operator
|
||||
85 | BaseSensorOperator()
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.sdk.bases.sensor.BaseSensorOperator` instead
|
||||
|
||||
AIR301_names.py:100:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
|
||||
AIR301_names.py:89:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
|
||||
|
|
||||
88 | # airflow.triggers.external_task
|
||||
89 | TaskStateTrigger()
|
||||
| ^^^^^^^^^^^^^^^^ AIR301
|
||||
90 |
|
||||
91 | # airflow.utils.date
|
||||
|
|
||||
|
||||
AIR301_names.py:92:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
|
|
||||
91 | # airflow.utils.date
|
||||
92 | dates.date_range
|
||||
| ^^^^^^^^^^ AIR301
|
||||
93 | dates.days_ago
|
||||
|
|
||||
|
||||
AIR301_names.py:93:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
|
|
||||
91 | # airflow.utils.date
|
||||
92 | dates.date_range
|
||||
93 | dates.days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
94 |
|
||||
95 | date_range
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:95:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
|
|
||||
93 | dates.days_ago
|
||||
94 |
|
||||
95 | date_range
|
||||
| ^^^^^^^^^^ AIR301
|
||||
96 | days_ago
|
||||
97 | infer_time_unit
|
||||
|
|
||||
|
||||
AIR301_names.py:96:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
|
|
||||
95 | date_range
|
||||
96 | days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
97 | infer_time_unit
|
||||
98 | parse_execution_date
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:97:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
|
||||
|
|
||||
95 | date_range
|
||||
96 | days_ago
|
||||
97 | infer_time_unit
|
||||
| ^^^^^^^^^^^^^^^ AIR301
|
||||
98 | parse_execution_date
|
||||
99 | round_time
|
||||
|
|
||||
|
||||
AIR301_names.py:98:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
|
||||
|
|
||||
99 | # airflow.triggers.external_task
|
||||
100 | TaskStateTrigger()
|
||||
96 | days_ago
|
||||
97 | infer_time_unit
|
||||
98 | parse_execution_date
|
||||
| ^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
99 | round_time
|
||||
100 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:99:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
|
||||
|
|
||||
97 | infer_time_unit
|
||||
98 | parse_execution_date
|
||||
99 | round_time
|
||||
| ^^^^^^^^^^ AIR301
|
||||
100 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:100:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
|
||||
|
|
||||
98 | parse_execution_date
|
||||
99 | round_time
|
||||
100 | scale_time_units
|
||||
| ^^^^^^^^^^^^^^^^ AIR301
|
||||
101 |
|
||||
102 | # airflow.utils.date
|
||||
102 | # This one was not deprecated.
|
||||
|
|
||||
|
||||
AIR301_names.py:103:7: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
AIR301_names.py:107:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
|
||||
|
|
||||
102 | # airflow.utils.date
|
||||
103 | dates.date_range
|
||||
| ^^^^^^^^^^ AIR301
|
||||
104 | dates.days_ago
|
||||
|
|
||||
|
||||
AIR301_names.py:104:7: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
|
|
||||
102 | # airflow.utils.date
|
||||
103 | dates.date_range
|
||||
104 | dates.days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
105 |
|
||||
106 | date_range
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:106:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
|
|
||||
104 | dates.days_ago
|
||||
105 |
|
||||
106 | date_range
|
||||
106 | # airflow.utils.dag_cycle_tester
|
||||
107 | test_cycle
|
||||
| ^^^^^^^^^^ AIR301
|
||||
107 | days_ago
|
||||
108 | infer_time_unit
|
||||
|
|
||||
|
||||
AIR301_names.py:107:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
AIR301_names.py:111:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
|
||||
|
|
||||
106 | date_range
|
||||
107 | days_ago
|
||||
| ^^^^^^^^ AIR301
|
||||
108 | infer_time_unit
|
||||
109 | parse_execution_date
|
||||
|
|
||||
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301_names.py:108:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
|
||||
|
|
||||
106 | date_range
|
||||
107 | days_ago
|
||||
108 | infer_time_unit
|
||||
| ^^^^^^^^^^^^^^^ AIR301
|
||||
109 | parse_execution_date
|
||||
110 | round_time
|
||||
|
|
||||
|
||||
AIR301_names.py:109:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
|
||||
|
|
||||
107 | days_ago
|
||||
108 | infer_time_unit
|
||||
109 | parse_execution_date
|
||||
| ^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
110 | round_time
|
||||
111 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:110:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
|
||||
|
|
||||
108 | infer_time_unit
|
||||
109 | parse_execution_date
|
||||
110 | round_time
|
||||
| ^^^^^^^^^^ AIR301
|
||||
111 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301_names.py:111:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
|
||||
|
|
||||
109 | parse_execution_date
|
||||
110 | round_time
|
||||
111 | scale_time_units
|
||||
| ^^^^^^^^^^^^^^^^ AIR301
|
||||
110 | # airflow.utils.db
|
||||
111 | create_session
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
112 |
|
||||
113 | # This one was not deprecated.
|
||||
113 | # airflow.utils.decorators
|
||||
|
|
||||
|
||||
AIR301_names.py:118:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
|
||||
AIR301_names.py:114:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0
|
||||
|
|
||||
117 | # airflow.utils.dag_cycle_tester
|
||||
118 | test_cycle
|
||||
| ^^^^^^^^^^ AIR301
|
||||
|
|
||||
|
||||
AIR301_names.py:122:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
|
||||
|
|
||||
121 | # airflow.utils.db
|
||||
122 | create_session
|
||||
113 | # airflow.utils.decorators
|
||||
114 | apply_defaults
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
123 |
|
||||
124 | # airflow.utils.decorators
|
||||
115 |
|
||||
116 | # airflow.utils.file
|
||||
|
|
||||
= help: `apply_defaults` is now unconditionally done and can be safely removed.
|
||||
|
||||
AIR301_names.py:125:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0; `apply_defaults` is now unconditionally done and can be safely removed.
|
||||
AIR301_names.py:117:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0
|
||||
|
|
||||
124 | # airflow.utils.decorators
|
||||
125 | apply_defaults
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
126 |
|
||||
127 | # airflow.utils.file
|
||||
|
|
||||
|
||||
AIR301_names.py:128:1: AIR301 `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0
|
||||
|
|
||||
127 | # airflow.utils.file
|
||||
128 | TemporaryDirectory()
|
||||
116 | # airflow.utils.file
|
||||
117 | TemporaryDirectory()
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR301
|
||||
129 | mkdirs
|
||||
118 | mkdirs
|
||||
|
|
||||
= help: Use `tempfile.TemporaryDirectory` instead
|
||||
|
||||
AIR301_names.py:129:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
|
||||
AIR301_names.py:118:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
|
||||
|
|
||||
127 | # airflow.utils.file
|
||||
128 | TemporaryDirectory()
|
||||
129 | mkdirs
|
||||
116 | # airflow.utils.file
|
||||
117 | TemporaryDirectory()
|
||||
118 | mkdirs
|
||||
| ^^^^^^ AIR301
|
||||
130 |
|
||||
131 | # airflow.utils.helpers
|
||||
119 |
|
||||
120 | # airflow.utils.helpers
|
||||
|
|
||||
= help: Use `pathlib.Path({path}).mkdir` instead
|
||||
|
||||
AIR301_names.py:132:1: AIR301 `airflow.utils.helpers.chain` is removed in Airflow 3.0
|
||||
AIR301_names.py:121:1: AIR301 [*] `airflow.utils.helpers.chain` is removed in Airflow 3.0
|
||||
|
|
||||
131 | # airflow.utils.helpers
|
||||
132 | helper_chain
|
||||
120 | # airflow.utils.helpers
|
||||
121 | helper_chain
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
133 | helper_cross_downstream
|
||||
122 | helper_cross_downstream
|
||||
|
|
||||
= help: Use `airflow.sdk.chain` instead
|
||||
|
||||
AIR301_names.py:133:1: AIR301 `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0
|
||||
ℹ Safe fix
|
||||
48 48 | from airflow.utils.trigger_rule import TriggerRule
|
||||
49 49 | from airflow.www.auth import has_access
|
||||
50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
|
||||
51 |+from airflow.sdk import chain
|
||||
51 52 |
|
||||
52 53 | # airflow root
|
||||
53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
--------------------------------------------------------------------------------
|
||||
118 119 | mkdirs
|
||||
119 120 |
|
||||
120 121 | # airflow.utils.helpers
|
||||
121 |-helper_chain
|
||||
122 |+chain
|
||||
122 123 | helper_cross_downstream
|
||||
123 124 |
|
||||
124 125 | # airflow.utils.log
|
||||
|
||||
AIR301_names.py:122:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0
|
||||
|
|
||||
131 | # airflow.utils.helpers
|
||||
132 | helper_chain
|
||||
133 | helper_cross_downstream
|
||||
120 | # airflow.utils.helpers
|
||||
121 | helper_chain
|
||||
122 | helper_cross_downstream
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
134 |
|
||||
135 | # airflow.utils.log
|
||||
123 |
|
||||
124 | # airflow.utils.log
|
||||
|
|
||||
= help: Use `airflow.sdk.cross_downstream` instead
|
||||
|
||||
AIR301_names.py:136:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0
|
||||
ℹ Safe fix
|
||||
48 48 | from airflow.utils.trigger_rule import TriggerRule
|
||||
49 49 | from airflow.www.auth import has_access
|
||||
50 50 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
|
||||
51 |+from airflow.sdk import cross_downstream
|
||||
51 52 |
|
||||
52 53 | # airflow root
|
||||
53 54 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
--------------------------------------------------------------------------------
|
||||
119 120 |
|
||||
120 121 | # airflow.utils.helpers
|
||||
121 122 | helper_chain
|
||||
122 |-helper_cross_downstream
|
||||
123 |+cross_downstream
|
||||
123 124 |
|
||||
124 125 | # airflow.utils.log
|
||||
125 126 | secrets_masker
|
||||
|
||||
AIR301_names.py:125:1: AIR301 `airflow.utils.log.secrets_masker` is removed in Airflow 3.0
|
||||
|
|
||||
135 | # airflow.utils.log
|
||||
136 | secrets_masker
|
||||
124 | # airflow.utils.log
|
||||
125 | secrets_masker
|
||||
| ^^^^^^^^^^^^^^ AIR301
|
||||
137 |
|
||||
138 | # airflow.utils.state
|
||||
126 |
|
||||
127 | # airflow.utils.state
|
||||
|
|
||||
= help: Use `airflow.sdk.execution_time.secrets_masker` instead
|
||||
|
||||
AIR301_names.py:139:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
|
||||
AIR301_names.py:128:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
|
||||
|
|
||||
138 | # airflow.utils.state
|
||||
139 | SHUTDOWN
|
||||
127 | # airflow.utils.state
|
||||
128 | SHUTDOWN
|
||||
| ^^^^^^^^ AIR301
|
||||
140 | terminating_states
|
||||
129 | terminating_states
|
||||
|
|
||||
|
||||
AIR301_names.py:140:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
|
||||
AIR301_names.py:129:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
|
||||
|
|
||||
138 | # airflow.utils.state
|
||||
139 | SHUTDOWN
|
||||
140 | terminating_states
|
||||
127 | # airflow.utils.state
|
||||
128 | SHUTDOWN
|
||||
129 | terminating_states
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR301
|
||||
141 |
|
||||
142 | # airflow.utils.trigger_rule
|
||||
130 |
|
||||
131 | # airflow.utils.trigger_rule
|
||||
|
|
||||
|
||||
AIR301_names.py:143:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
|
||||
AIR301_names.py:132:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
|
||||
|
|
||||
142 | # airflow.utils.trigger_rule
|
||||
143 | TriggerRule.DUMMY
|
||||
131 | # airflow.utils.trigger_rule
|
||||
132 | TriggerRule.DUMMY
|
||||
| ^^^^^ AIR301
|
||||
144 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
133 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
|
|
||||
|
||||
AIR301_names.py:144:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
|
||||
AIR301_names.py:133:13: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
|
||||
|
|
||||
142 | # airflow.utils.trigger_rule
|
||||
143 | TriggerRule.DUMMY
|
||||
144 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
131 | # airflow.utils.trigger_rule
|
||||
132 | TriggerRule.DUMMY
|
||||
133 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
|
||||
AIR301_names.py:148:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
|
||||
AIR301_names.py:137:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
|
||||
|
|
||||
147 | # airflow.www.auth
|
||||
148 | has_access
|
||||
136 | # airflow.www.auth
|
||||
137 | has_access
|
||||
| ^^^^^^^^^^ AIR301
|
||||
149 |
|
||||
150 | # airflow.www.utils
|
||||
138 |
|
||||
139 | # airflow.www.utils
|
||||
|
|
||||
= help: Use `airflow.www.auth.has_access_*` instead
|
||||
|
||||
AIR301_names.py:151:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
|
||||
AIR301_names.py:140:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
|
||||
|
|
||||
150 | # airflow.www.utils
|
||||
151 | get_sensitive_variables_fields
|
||||
139 | # airflow.www.utils
|
||||
140 | get_sensitive_variables_fields
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
152 | should_hide_value_for_key
|
||||
141 | should_hide_value_for_key
|
||||
|
|
||||
= help: Use `airflow.utils.log.secrets_masker.get_sensitive_variables_fields` instead
|
||||
|
||||
AIR301_names.py:152:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
|
||||
AIR301_names.py:141:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
|
||||
|
|
||||
150 | # airflow.www.utils
|
||||
151 | get_sensitive_variables_fields
|
||||
152 | should_hide_value_for_key
|
||||
139 | # airflow.www.utils
|
||||
140 | get_sensitive_variables_fields
|
||||
141 | should_hide_value_for_key
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
153 |
|
||||
154 | # airflow.operators.python
|
||||
142 |
|
||||
143 | # airflow.operators.python
|
||||
|
|
||||
= help: Use `airflow.utils.log.secrets_masker.should_hide_value_for_key` instead
|
||||
|
||||
AIR301_names.py:157:1: AIR301 `airflow.operators.python.get_current_context` is removed in Airflow 3.0
|
||||
AIR301_names.py:146:1: AIR301 `airflow.operators.python.get_current_context` is removed in Airflow 3.0
|
||||
|
|
||||
155 | from airflow.operators.python import get_current_context
|
||||
156 |
|
||||
157 | get_current_context()
|
||||
144 | from airflow.operators.python import get_current_context
|
||||
145 |
|
||||
146 | get_current_context()
|
||||
| ^^^^^^^^^^^^^^^^^^^ AIR301
|
||||
147 |
|
||||
148 | # airflow.providers.mysql
|
||||
|
|
||||
= help: Use `airflow.sdk.get_current_context` instead
|
||||
|
||||
AIR301_names.py:151:1: AIR301 `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
149 | from airflow.providers.mysql.datasets.mysql import sanitize_uri
|
||||
150 |
|
||||
151 | sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
152 |
|
||||
153 | # airflow.providers.postgres
|
||||
|
|
||||
= help: Use `airflow.providers.mysql.assets.mysql.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:156:1: AIR301 `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
154 | from airflow.providers.postgres.datasets.postgres import sanitize_uri
|
||||
155 |
|
||||
156 | sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
157 |
|
||||
158 | # airflow.providers.trino
|
||||
|
|
||||
= help: Use `airflow.providers.postgres.assets.postgres.sanitize_uri` instead
|
||||
|
||||
AIR301_names.py:161:1: AIR301 `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0
|
||||
|
|
||||
159 | from airflow.providers.trino.datasets.trino import sanitize_uri
|
||||
160 |
|
||||
161 | sanitize_uri
|
||||
| ^^^^^^^^^^^^ AIR301
|
||||
|
|
||||
= help: Use `airflow.providers.trino.assets.trino.sanitize_uri` instead
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::helpers::map_callable;
|
||||
use ruff_python_ast::name::UnqualifiedName;
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
@@ -11,6 +12,7 @@ use ruff_python_semantic::SemanticModel;
|
||||
use ruff_source_file::LineRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{TextLen, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits;
|
||||
@@ -807,10 +809,51 @@ fn check_fixture_returns(checker: &Checker, name: &str, body: &[Stmt], returns:
|
||||
}
|
||||
|
||||
/// PT019
|
||||
fn check_test_function_args(checker: &Checker, parameters: &Parameters) {
|
||||
fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorators: &[Decorator]) {
|
||||
let mut named_parametrize = FxHashSet::default();
|
||||
for decorator in decorators.iter().filter(|decorator| {
|
||||
UnqualifiedName::from_expr(map_callable(&decorator.expression))
|
||||
.is_some_and(|name| matches!(name.segments(), ["pytest", "mark", "parametrize"]))
|
||||
}) {
|
||||
let Some(call_expr) = decorator.expression.as_call_expr() else {
|
||||
continue;
|
||||
};
|
||||
let Some(first_arg) = call_expr.arguments.find_argument_value("argnames", 0) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match first_arg {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
named_parametrize.extend(
|
||||
value
|
||||
.to_str()
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|param| !param.is_empty() && param.starts_with('_')),
|
||||
);
|
||||
}
|
||||
|
||||
Expr::Name(_) => return,
|
||||
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
if elts.iter().any(Expr::is_name_expr) =>
|
||||
{
|
||||
return
|
||||
}
|
||||
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
named_parametrize.extend(
|
||||
elts.iter()
|
||||
.filter_map(Expr::as_string_literal_expr)
|
||||
.map(|param| param.value.to_str().trim())
|
||||
.filter(|param| !param.is_empty() && param.starts_with('_')),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for parameter in parameters.iter_non_variadic_params() {
|
||||
let name = parameter.name();
|
||||
if name.starts_with('_') {
|
||||
if name.starts_with('_') && !named_parametrize.contains(name.as_str()) {
|
||||
checker.report_diagnostic(Diagnostic::new(
|
||||
PytestFixtureParamWithoutValue {
|
||||
name: name.to_string(),
|
||||
@@ -915,6 +958,6 @@ pub(crate) fn fixture(
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::PytestFixtureParamWithoutValue) && name.starts_with("test_") {
|
||||
check_test_function_args(checker, parameters);
|
||||
check_test_function_args(checker, parameters, decorators);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PT019.py:9:14: PT019 Fixture `_fixture` without value is injected as parameter, use `@pytest.mark.usefixtures` instead
|
||||
|
|
||||
@@ -15,3 +14,19 @@ PT019.py:13:17: PT019 Fixture `_fixture` without value is injected as parameter,
|
||||
| ^^^^^^^^ PT019
|
||||
14 | pass
|
||||
|
|
||||
|
||||
PT019.py:31:24: PT019 Fixture `_bar` without value is injected as parameter, use `@pytest.mark.usefixtures` instead
|
||||
|
|
||||
30 | @pytest.mark.parametrize("_foo", [1, 2, 3])
|
||||
31 | def test_thingy2(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
| ^^^^ PT019
|
||||
32 | pass
|
||||
|
|
||||
|
||||
PT019.py:39:24: PT019 Fixture `_bar` without value is injected as parameter, use `@pytest.mark.usefixtures` instead
|
||||
|
|
||||
38 | @pytest.mark.parametrize(("_foo"), [1, 2, 3])
|
||||
39 | def test_thingy4(_foo, _bar): # Error _bar is not defined in parametrize
|
||||
| ^^^^ PT019
|
||||
40 | pass
|
||||
|
|
||||
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn split_static_string(
|
||||
let sep_arg = arguments.find_argument_value("sep", 0);
|
||||
let split_replacement = if let Some(sep) = sep_arg {
|
||||
match sep {
|
||||
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value),
|
||||
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value, direction),
|
||||
Expr::StringLiteral(sep_value) => {
|
||||
let sep_value_str = sep_value.value.to_str();
|
||||
Some(split_sep(
|
||||
@@ -99,7 +99,7 @@ pub(crate) fn split_static_string(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
split_default(str_value, maxsplit_value)
|
||||
split_default(str_value, maxsplit_value, direction)
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(SplitStaticString, call.range());
|
||||
@@ -144,7 +144,11 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
|
||||
})
|
||||
}
|
||||
|
||||
fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option<Expr> {
|
||||
fn split_default(
|
||||
str_value: &StringLiteralValue,
|
||||
max_split: i32,
|
||||
direction: Direction,
|
||||
) -> Option<Expr> {
|
||||
// From the Python documentation:
|
||||
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
|
||||
// > consecutive whitespace are regarded as a single separator, and the result will contain
|
||||
@@ -152,6 +156,7 @@ fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option<Expr>
|
||||
// > Consequently, splitting an empty string or a string consisting of just whitespace with
|
||||
// > a None separator returns [].
|
||||
// https://docs.python.org/3/library/stdtypes.html#str.split
|
||||
let string_val = str_value.to_str();
|
||||
match max_split.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
// Autofix for `maxsplit` without separator not yet implemented, as
|
||||
@@ -160,14 +165,30 @@ fn split_default(str_value: &StringLiteralValue, max_split: i32) -> Option<Expr>
|
||||
None
|
||||
}
|
||||
Ordering::Equal => {
|
||||
let list_items: Vec<&str> = vec![str_value.to_str()];
|
||||
// Behavior for maxsplit = 0 when sep is None:
|
||||
// - If the string is empty or all whitespace, result is [].
|
||||
// - Otherwise:
|
||||
// - " x ".split(maxsplit=0) -> ['x ']
|
||||
// - " x ".rsplit(maxsplit=0) -> [' x']
|
||||
// - "".split(maxsplit=0) -> []
|
||||
// - " ".split(maxsplit=0) -> []
|
||||
let processed_str = if direction == Direction::Left {
|
||||
string_val.trim_start()
|
||||
} else {
|
||||
string_val.trim_end()
|
||||
};
|
||||
let list_items: &[_] = if processed_str.is_empty() {
|
||||
&[]
|
||||
} else {
|
||||
&[processed_str]
|
||||
};
|
||||
Some(construct_replacement(
|
||||
&list_items,
|
||||
list_items,
|
||||
str_value.first_literal_flags(),
|
||||
))
|
||||
}
|
||||
Ordering::Less => {
|
||||
let list_items: Vec<&str> = str_value.to_str().split_whitespace().collect();
|
||||
let list_items: Vec<&str> = string_val.split_whitespace().collect();
|
||||
Some(construct_replacement(
|
||||
&list_items,
|
||||
str_value.first_literal_flags(),
|
||||
|
||||
@@ -890,6 +890,8 @@ SIM905.py:111:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
111 |-print("a,b".rsplit(","))
|
||||
111 |+print(["a", "b"])
|
||||
112 112 | print("a,b,c".rsplit(",", 1))
|
||||
113 113 |
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
|
||||
SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
@@ -897,6 +899,8 @@ SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
111 | print("a,b".rsplit(","))
|
||||
112 | print("a,b,c".rsplit(",", 1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
113 |
|
||||
114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
@@ -906,3 +910,353 @@ SIM905.py:112:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
111 111 | print("a,b".rsplit(","))
|
||||
112 |-print("a,b,c".rsplit(",", 1))
|
||||
112 |+print(["a,b", "c"])
|
||||
113 113 |
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 115 |
|
||||
|
||||
SIM905.py:116:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 |
|
||||
116 | print("".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
113 113 |
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 115 |
|
||||
116 |-print("".split(maxsplit=0))
|
||||
116 |+print([])
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:117:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
116 | print("".split(maxsplit=0))
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
114 114 | # https://github.com/astral-sh/ruff/issues/18069
|
||||
115 115 |
|
||||
116 116 | print("".split(maxsplit=0))
|
||||
117 |-print("".split(sep=None, maxsplit=0))
|
||||
117 |+print([])
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
|
||||
SIM905.py:118:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
116 | print("".split(maxsplit=0))
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
115 115 |
|
||||
116 116 | print("".split(maxsplit=0))
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 |-print(" ".split(maxsplit=0))
|
||||
118 |+print([])
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:119:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
117 | print("".split(sep=None, maxsplit=0))
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
116 116 | print("".split(maxsplit=0))
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 |-print(" ".split(sep=None, maxsplit=0))
|
||||
119 |+print([])
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
|
||||
SIM905.py:120:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
118 | print(" ".split(maxsplit=0))
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
117 117 | print("".split(sep=None, maxsplit=0))
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 |-print(" x ".split(maxsplit=0))
|
||||
120 |+print(["x "])
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:121:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
118 118 | print(" ".split(maxsplit=0))
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 |-print(" x ".split(sep=None, maxsplit=0))
|
||||
121 |+print(["x "])
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:122:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
120 | print(" x ".split(maxsplit=0))
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
119 119 | print(" ".split(sep=None, maxsplit=0))
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 |-print(" x ".split(maxsplit=0))
|
||||
122 |+print(["x "])
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:123:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
120 120 | print(" x ".split(maxsplit=0))
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 |-print(" x ".split(sep=None, maxsplit=0))
|
||||
123 |+print(["x "])
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:124:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
122 | print(" x ".split(maxsplit=0))
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
121 121 | print(" x ".split(sep=None, maxsplit=0))
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 |-print("".rsplit(maxsplit=0))
|
||||
124 |+print([])
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:125:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
122 122 | print(" x ".split(maxsplit=0))
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 |-print("".rsplit(sep=None, maxsplit=0))
|
||||
125 |+print([])
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:126:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
124 | print("".rsplit(maxsplit=0))
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
123 123 | print(" x ".split(sep=None, maxsplit=0))
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 |-print(" ".rsplit(maxsplit=0))
|
||||
126 |+print([])
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:127:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
124 124 | print("".rsplit(maxsplit=0))
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 |-print(" ".rsplit(sep=None, maxsplit=0))
|
||||
127 |+print([])
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:128:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
126 | print(" ".rsplit(maxsplit=0))
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
125 125 | print("".rsplit(sep=None, maxsplit=0))
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 |-print(" x ".rsplit(maxsplit=0))
|
||||
128 |+print([" x"])
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
|
||||
SIM905.py:129:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
126 126 | print(" ".rsplit(maxsplit=0))
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 |-print(" x ".rsplit(maxsplit=0))
|
||||
129 |+print([" x"])
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:130:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
127 127 | print(" ".rsplit(sep=None, maxsplit=0))
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 |-print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
130 |+print([" x"])
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:131:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
128 128 | print(" x ".rsplit(maxsplit=0))
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 |-print(" x ".rsplit(maxsplit=0))
|
||||
131 |+print([" x"])
|
||||
132 132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
|
||||
SIM905.py:132:7: SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
|
|
||||
130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM905
|
||||
|
|
||||
= help: Replace with list literal
|
||||
|
||||
ℹ Safe fix
|
||||
129 129 | print(" x ".rsplit(maxsplit=0))
|
||||
130 130 | print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
131 131 | print(" x ".rsplit(maxsplit=0))
|
||||
132 |-print(" x ".rsplit(sep=None, maxsplit=0))
|
||||
132 |+print([" x"])
|
||||
|
||||
@@ -64,7 +64,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
[
|
||||
"" | "builtins",
|
||||
"EnvironmentError" | "IOError" | "WindowsError"
|
||||
] | ["mmap" | "select" | "socket" | "os", "error"]
|
||||
] | ["mmap" | "resource" | "select" | "socket" | "os", "error"]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
UP024_2.py:10:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
8 | # Testing the modules
|
||||
9 | import socket, mmap, select
|
||||
9 | import socket, mmap, select, resource
|
||||
10 | raise socket.error
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
11 | raise mmap.error
|
||||
@@ -15,32 +15,33 @@ UP024_2.py:10:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
ℹ Safe fix
|
||||
7 7 |
|
||||
8 8 | # Testing the modules
|
||||
9 9 | import socket, mmap, select
|
||||
9 9 | import socket, mmap, select, resource
|
||||
10 |-raise socket.error
|
||||
10 |+raise OSError
|
||||
11 11 | raise mmap.error
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
13 13 | raise resource.error
|
||||
|
||||
UP024_2.py:11:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
9 | import socket, mmap, select
|
||||
9 | import socket, mmap, select, resource
|
||||
10 | raise socket.error
|
||||
11 | raise mmap.error
|
||||
| ^^^^^^^^^^ UP024
|
||||
12 | raise select.error
|
||||
13 | raise resource.error
|
||||
|
|
||||
= help: Replace `mmap.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | # Testing the modules
|
||||
9 9 | import socket, mmap, select
|
||||
9 9 | import socket, mmap, select, resource
|
||||
10 10 | raise socket.error
|
||||
11 |-raise mmap.error
|
||||
11 |+raise OSError
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
|
||||
UP024_2.py:12:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
@@ -48,355 +49,416 @@ UP024_2.py:12:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
11 | raise mmap.error
|
||||
12 | raise select.error
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
13 |
|
||||
14 | raise socket.error()
|
||||
13 | raise resource.error
|
||||
|
|
||||
= help: Replace `select.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 | import socket, mmap, select
|
||||
9 9 | import socket, mmap, select, resource
|
||||
10 10 | raise socket.error
|
||||
11 11 | raise mmap.error
|
||||
12 |-raise select.error
|
||||
12 |+raise OSError
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
15 15 | raise mmap.error(1)
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
|
||||
UP024_2.py:14:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:13:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
11 | raise mmap.error
|
||||
12 | raise select.error
|
||||
13 |
|
||||
14 | raise socket.error()
|
||||
13 | raise resource.error
|
||||
| ^^^^^^^^^^^^^^ UP024
|
||||
14 |
|
||||
15 | raise socket.error()
|
||||
|
|
||||
= help: Replace `resource.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 | raise socket.error
|
||||
11 11 | raise mmap.error
|
||||
12 12 | raise select.error
|
||||
13 |-raise resource.error
|
||||
13 |+raise OSError
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
16 16 | raise mmap.error(1)
|
||||
|
||||
UP024_2.py:15:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
13 | raise resource.error
|
||||
14 |
|
||||
15 | raise socket.error()
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
15 | raise mmap.error(1)
|
||||
16 | raise select.error(1, 2)
|
||||
16 | raise mmap.error(1)
|
||||
17 | raise select.error(1, 2)
|
||||
|
|
||||
= help: Replace `socket.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 | raise mmap.error
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
14 |-raise socket.error()
|
||||
14 |+raise OSError()
|
||||
15 15 | raise mmap.error(1)
|
||||
16 16 | raise select.error(1, 2)
|
||||
17 17 |
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
15 |-raise socket.error()
|
||||
15 |+raise OSError()
|
||||
16 16 | raise mmap.error(1)
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
|
||||
UP024_2.py:15:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:16:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
14 | raise socket.error()
|
||||
15 | raise mmap.error(1)
|
||||
15 | raise socket.error()
|
||||
16 | raise mmap.error(1)
|
||||
| ^^^^^^^^^^ UP024
|
||||
16 | raise select.error(1, 2)
|
||||
17 | raise select.error(1, 2)
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
|
|
||||
= help: Replace `mmap.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | raise select.error
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
15 |-raise mmap.error(1)
|
||||
15 |+raise OSError(1)
|
||||
16 16 | raise select.error(1, 2)
|
||||
17 17 |
|
||||
18 18 | raise socket.error(
|
||||
13 13 | raise resource.error
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
16 |-raise mmap.error(1)
|
||||
16 |+raise OSError(1)
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
19 19 |
|
||||
|
||||
UP024_2.py:16:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:17:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
14 | raise socket.error()
|
||||
15 | raise mmap.error(1)
|
||||
16 | raise select.error(1, 2)
|
||||
15 | raise socket.error()
|
||||
16 | raise mmap.error(1)
|
||||
17 | raise select.error(1, 2)
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
17 |
|
||||
18 | raise socket.error(
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
|
|
||||
= help: Replace `select.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 |
|
||||
14 14 | raise socket.error()
|
||||
15 15 | raise mmap.error(1)
|
||||
16 |-raise select.error(1, 2)
|
||||
16 |+raise OSError(1, 2)
|
||||
17 17 |
|
||||
18 18 | raise socket.error(
|
||||
19 19 | 1,
|
||||
14 14 |
|
||||
15 15 | raise socket.error()
|
||||
16 16 | raise mmap.error(1)
|
||||
17 |-raise select.error(1, 2)
|
||||
17 |+raise OSError(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
19 19 |
|
||||
20 20 | raise socket.error(
|
||||
|
||||
UP024_2.py:18:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
16 | raise select.error(1, 2)
|
||||
17 |
|
||||
18 | raise socket.error(
|
||||
16 | raise mmap.error(1)
|
||||
17 | raise select.error(1, 2)
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
| ^^^^^^^^^^^^^^ UP024
|
||||
19 |
|
||||
20 | raise socket.error(
|
||||
|
|
||||
= help: Replace `resource.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | raise socket.error()
|
||||
16 16 | raise mmap.error(1)
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 |-raise resource.error(1, "strerror", "filename")
|
||||
18 |+raise OSError(1, "strerror", "filename")
|
||||
19 19 |
|
||||
20 20 | raise socket.error(
|
||||
21 21 | 1,
|
||||
|
||||
UP024_2.py:20:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
18 | raise resource.error(1, "strerror", "filename")
|
||||
19 |
|
||||
20 | raise socket.error(
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
19 | 1,
|
||||
20 | 2,
|
||||
21 | 1,
|
||||
22 | 2,
|
||||
|
|
||||
= help: Replace `socket.error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | raise mmap.error(1)
|
||||
16 16 | raise select.error(1, 2)
|
||||
17 17 |
|
||||
18 |-raise socket.error(
|
||||
18 |+raise OSError(
|
||||
19 19 | 1,
|
||||
20 20 | 2,
|
||||
21 21 | 3,
|
||||
17 17 | raise select.error(1, 2)
|
||||
18 18 | raise resource.error(1, "strerror", "filename")
|
||||
19 19 |
|
||||
20 |-raise socket.error(
|
||||
20 |+raise OSError(
|
||||
21 21 | 1,
|
||||
22 22 | 2,
|
||||
23 23 | 3,
|
||||
|
||||
UP024_2.py:25:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:27:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
24 | from mmap import error
|
||||
25 | raise error
|
||||
26 | from mmap import error
|
||||
27 | raise error
|
||||
| ^^^^^ UP024
|
||||
26 |
|
||||
27 | from socket import error
|
||||
28 |
|
||||
29 | from socket import error
|
||||
|
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
22 22 | )
|
||||
23 23 |
|
||||
24 24 | from mmap import error
|
||||
25 |-raise error
|
||||
25 |+raise OSError
|
||||
26 26 |
|
||||
27 27 | from socket import error
|
||||
28 28 | raise error(1)
|
||||
24 24 | )
|
||||
25 25 |
|
||||
26 26 | from mmap import error
|
||||
27 |-raise error
|
||||
27 |+raise OSError
|
||||
28 28 |
|
||||
29 29 | from socket import error
|
||||
30 30 | raise error(1)
|
||||
|
||||
UP024_2.py:28:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:30:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
27 | from socket import error
|
||||
28 | raise error(1)
|
||||
29 | from socket import error
|
||||
30 | raise error(1)
|
||||
| ^^^^^ UP024
|
||||
29 |
|
||||
30 | from select import error
|
||||
31 |
|
||||
32 | from select import error
|
||||
|
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
25 25 | raise error
|
||||
26 26 |
|
||||
27 27 | from socket import error
|
||||
28 |-raise error(1)
|
||||
28 |+raise OSError(1)
|
||||
29 29 |
|
||||
30 30 | from select import error
|
||||
31 31 | raise error(1, 2)
|
||||
27 27 | raise error
|
||||
28 28 |
|
||||
29 29 | from socket import error
|
||||
30 |-raise error(1)
|
||||
30 |+raise OSError(1)
|
||||
31 31 |
|
||||
32 32 | from select import error
|
||||
33 33 | raise error(1, 2)
|
||||
|
||||
UP024_2.py:31:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:33:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
30 | from select import error
|
||||
31 | raise error(1, 2)
|
||||
32 | from select import error
|
||||
33 | raise error(1, 2)
|
||||
| ^^^^^ UP024
|
||||
32 |
|
||||
33 | # Testing the names
|
||||
34 |
|
||||
35 | from resource import error
|
||||
|
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
28 28 | raise error(1)
|
||||
29 29 |
|
||||
30 30 | from select import error
|
||||
31 |-raise error(1, 2)
|
||||
31 |+raise OSError(1, 2)
|
||||
32 32 |
|
||||
33 33 | # Testing the names
|
||||
34 34 | raise EnvironmentError
|
||||
|
||||
UP024_2.py:34:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
33 | # Testing the names
|
||||
34 | raise EnvironmentError
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
35 | raise IOError
|
||||
36 | raise WindowsError
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
31 31 | raise error(1, 2)
|
||||
32 32 |
|
||||
33 33 | # Testing the names
|
||||
34 |-raise EnvironmentError
|
||||
34 |+raise OSError
|
||||
35 35 | raise IOError
|
||||
36 36 | raise WindowsError
|
||||
37 37 |
|
||||
|
||||
UP024_2.py:35:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
33 | # Testing the names
|
||||
34 | raise EnvironmentError
|
||||
35 | raise IOError
|
||||
| ^^^^^^^ UP024
|
||||
36 | raise WindowsError
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
32 32 |
|
||||
33 33 | # Testing the names
|
||||
34 34 | raise EnvironmentError
|
||||
35 |-raise IOError
|
||||
35 |+raise OSError
|
||||
36 36 | raise WindowsError
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
30 30 | raise error(1)
|
||||
31 31 |
|
||||
32 32 | from select import error
|
||||
33 |-raise error(1, 2)
|
||||
33 |+raise OSError(1, 2)
|
||||
34 34 |
|
||||
35 35 | from resource import error
|
||||
36 36 | raise error(1, "strerror", "filename")
|
||||
|
||||
UP024_2.py:36:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
34 | raise EnvironmentError
|
||||
35 | raise IOError
|
||||
36 | raise WindowsError
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
35 | from resource import error
|
||||
36 | raise error(1, "strerror", "filename")
|
||||
| ^^^^^ UP024
|
||||
37 |
|
||||
38 | raise EnvironmentError()
|
||||
38 | # Testing the names
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
= help: Replace `error` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | # Testing the names
|
||||
34 34 | raise EnvironmentError
|
||||
35 35 | raise IOError
|
||||
36 |-raise WindowsError
|
||||
36 |+raise OSError
|
||||
33 33 | raise error(1, 2)
|
||||
34 34 |
|
||||
35 35 | from resource import error
|
||||
36 |-raise error(1, "strerror", "filename")
|
||||
36 |+raise OSError(1, "strerror", "filename")
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
39 39 | raise IOError(1)
|
||||
|
||||
UP024_2.py:38:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
36 | raise WindowsError
|
||||
37 |
|
||||
38 | raise EnvironmentError()
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
39 | raise IOError(1)
|
||||
40 | raise WindowsError(1, 2)
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
35 35 | raise IOError
|
||||
36 36 | raise WindowsError
|
||||
37 37 |
|
||||
38 |-raise EnvironmentError()
|
||||
38 |+raise OSError()
|
||||
39 39 | raise IOError(1)
|
||||
40 40 | raise WindowsError(1, 2)
|
||||
41 41 |
|
||||
38 38 | # Testing the names
|
||||
39 39 | raise EnvironmentError
|
||||
|
||||
UP024_2.py:39:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
38 | raise EnvironmentError()
|
||||
39 | raise IOError(1)
|
||||
| ^^^^^^^ UP024
|
||||
40 | raise WindowsError(1, 2)
|
||||
38 | # Testing the names
|
||||
39 | raise EnvironmentError
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
40 | raise IOError
|
||||
41 | raise WindowsError
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
36 36 | raise WindowsError
|
||||
36 36 | raise error(1, "strerror", "filename")
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
39 |-raise IOError(1)
|
||||
39 |+raise OSError(1)
|
||||
40 40 | raise WindowsError(1, 2)
|
||||
41 41 |
|
||||
42 42 | raise EnvironmentError(
|
||||
38 38 | # Testing the names
|
||||
39 |-raise EnvironmentError
|
||||
39 |+raise OSError
|
||||
40 40 | raise IOError
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
|
||||
UP024_2.py:40:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
38 | raise EnvironmentError()
|
||||
39 | raise IOError(1)
|
||||
40 | raise WindowsError(1, 2)
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
41 |
|
||||
42 | raise EnvironmentError(
|
||||
38 | # Testing the names
|
||||
39 | raise EnvironmentError
|
||||
40 | raise IOError
|
||||
| ^^^^^^^ UP024
|
||||
41 | raise WindowsError
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
37 37 |
|
||||
38 38 | raise EnvironmentError()
|
||||
39 39 | raise IOError(1)
|
||||
40 |-raise WindowsError(1, 2)
|
||||
40 |+raise OSError(1, 2)
|
||||
41 41 |
|
||||
42 42 | raise EnvironmentError(
|
||||
43 43 | 1,
|
||||
38 38 | # Testing the names
|
||||
39 39 | raise EnvironmentError
|
||||
40 |-raise IOError
|
||||
40 |+raise OSError
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
|
||||
UP024_2.py:42:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:41:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
40 | raise WindowsError(1, 2)
|
||||
41 |
|
||||
42 | raise EnvironmentError(
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
43 | 1,
|
||||
44 | 2,
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
39 39 | raise IOError(1)
|
||||
40 40 | raise WindowsError(1, 2)
|
||||
41 41 |
|
||||
42 |-raise EnvironmentError(
|
||||
42 |+raise OSError(
|
||||
43 43 | 1,
|
||||
44 44 | 2,
|
||||
45 45 | 3,
|
||||
|
||||
UP024_2.py:48:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
46 | )
|
||||
47 |
|
||||
48 | raise WindowsError
|
||||
39 | raise EnvironmentError
|
||||
40 | raise IOError
|
||||
41 | raise WindowsError
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
49 | raise EnvironmentError(1)
|
||||
50 | raise IOError(1, 2)
|
||||
42 |
|
||||
43 | raise EnvironmentError()
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
45 45 | 3,
|
||||
46 46 | )
|
||||
47 47 |
|
||||
48 |-raise WindowsError
|
||||
48 |+raise OSError
|
||||
49 49 | raise EnvironmentError(1)
|
||||
50 50 | raise IOError(1, 2)
|
||||
38 38 | # Testing the names
|
||||
39 39 | raise EnvironmentError
|
||||
40 40 | raise IOError
|
||||
41 |-raise WindowsError
|
||||
41 |+raise OSError
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
44 44 | raise IOError(1)
|
||||
|
||||
UP024_2.py:49:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:43:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
48 | raise WindowsError
|
||||
49 | raise EnvironmentError(1)
|
||||
41 | raise WindowsError
|
||||
42 |
|
||||
43 | raise EnvironmentError()
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
50 | raise IOError(1, 2)
|
||||
44 | raise IOError(1)
|
||||
45 | raise WindowsError(1, 2)
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
46 46 | )
|
||||
47 47 |
|
||||
48 48 | raise WindowsError
|
||||
49 |-raise EnvironmentError(1)
|
||||
49 |+raise OSError(1)
|
||||
50 50 | raise IOError(1, 2)
|
||||
40 40 | raise IOError
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
43 |-raise EnvironmentError()
|
||||
43 |+raise OSError()
|
||||
44 44 | raise IOError(1)
|
||||
45 45 | raise WindowsError(1, 2)
|
||||
46 46 |
|
||||
|
||||
UP024_2.py:50:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
UP024_2.py:44:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
48 | raise WindowsError
|
||||
49 | raise EnvironmentError(1)
|
||||
50 | raise IOError(1, 2)
|
||||
43 | raise EnvironmentError()
|
||||
44 | raise IOError(1)
|
||||
| ^^^^^^^ UP024
|
||||
45 | raise WindowsError(1, 2)
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 | raise WindowsError
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
44 |-raise IOError(1)
|
||||
44 |+raise OSError(1)
|
||||
45 45 | raise WindowsError(1, 2)
|
||||
46 46 |
|
||||
47 47 | raise EnvironmentError(
|
||||
|
||||
UP024_2.py:45:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
43 | raise EnvironmentError()
|
||||
44 | raise IOError(1)
|
||||
45 | raise WindowsError(1, 2)
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
46 |
|
||||
47 | raise EnvironmentError(
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
42 42 |
|
||||
43 43 | raise EnvironmentError()
|
||||
44 44 | raise IOError(1)
|
||||
45 |-raise WindowsError(1, 2)
|
||||
45 |+raise OSError(1, 2)
|
||||
46 46 |
|
||||
47 47 | raise EnvironmentError(
|
||||
48 48 | 1,
|
||||
|
||||
UP024_2.py:47:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
45 | raise WindowsError(1, 2)
|
||||
46 |
|
||||
47 | raise EnvironmentError(
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
48 | 1,
|
||||
49 | 2,
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
44 44 | raise IOError(1)
|
||||
45 45 | raise WindowsError(1, 2)
|
||||
46 46 |
|
||||
47 |-raise EnvironmentError(
|
||||
47 |+raise OSError(
|
||||
48 48 | 1,
|
||||
49 49 | 2,
|
||||
50 50 | 3,
|
||||
|
||||
UP024_2.py:53:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
51 | )
|
||||
52 |
|
||||
53 | raise WindowsError
|
||||
| ^^^^^^^^^^^^ UP024
|
||||
54 | raise EnvironmentError(1)
|
||||
55 | raise IOError(1, 2)
|
||||
|
|
||||
= help: Replace `WindowsError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
50 50 | 3,
|
||||
51 51 | )
|
||||
52 52 |
|
||||
53 |-raise WindowsError
|
||||
53 |+raise OSError
|
||||
54 54 | raise EnvironmentError(1)
|
||||
55 55 | raise IOError(1, 2)
|
||||
|
||||
UP024_2.py:54:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
53 | raise WindowsError
|
||||
54 | raise EnvironmentError(1)
|
||||
| ^^^^^^^^^^^^^^^^ UP024
|
||||
55 | raise IOError(1, 2)
|
||||
|
|
||||
= help: Replace `EnvironmentError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
51 51 | )
|
||||
52 52 |
|
||||
53 53 | raise WindowsError
|
||||
54 |-raise EnvironmentError(1)
|
||||
54 |+raise OSError(1)
|
||||
55 55 | raise IOError(1, 2)
|
||||
|
||||
UP024_2.py:55:7: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
53 | raise WindowsError
|
||||
54 | raise EnvironmentError(1)
|
||||
55 | raise IOError(1, 2)
|
||||
| ^^^^^^^ UP024
|
||||
|
|
||||
= help: Replace `IOError` with builtin `OSError`
|
||||
|
||||
ℹ Safe fix
|
||||
47 47 |
|
||||
48 48 | raise WindowsError
|
||||
49 49 | raise EnvironmentError(1)
|
||||
50 |-raise IOError(1, 2)
|
||||
50 |+raise OSError(1, 2)
|
||||
52 52 |
|
||||
53 53 | raise WindowsError
|
||||
54 54 | raise EnvironmentError(1)
|
||||
55 |-raise IOError(1, 2)
|
||||
55 |+raise OSError(1, 2)
|
||||
|
||||
@@ -29,6 +29,14 @@ use crate::{checkers::ast::Checker, importer::ImportRequest};
|
||||
/// pairwise(letters) # ("A", "B"), ("B", "C"), ("C", "D")
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// The fix is always marked unsafe because it assumes that slicing an object
|
||||
/// (e.g., `obj[1:]`) produces a value with the same type and iteration behavior
|
||||
/// as the original object, which is not guaranteed for user-defined types that
|
||||
/// override `__getitem__` without properly handling slices. Moreover, the fix
|
||||
/// could delete comments.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `itertools.pairwise`](https://docs.python.org/3/library/itertools.html#itertools.pairwise)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -234,9 +234,6 @@ pub fn walk_stmt<V: Transformer + ?Sized>(visitor: &V, stmt: &mut Stmt) {
|
||||
visitor.visit_expr(test);
|
||||
visitor.visit_body(body);
|
||||
for clause in elif_else_clauses {
|
||||
if let Some(test) = &mut clause.test {
|
||||
visitor.visit_expr(test);
|
||||
}
|
||||
walk_elif_else_clause(visitor, clause);
|
||||
}
|
||||
}
|
||||
|
||||
141
crates/ty/CONTRIBUTING.md
Normal file
141
crates/ty/CONTRIBUTING.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Contributing to ty
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to ty.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This guide is for ty. If you're looking to contribute to Ruff, please see
|
||||
> [the Ruff contributing guide](../../CONTRIBUTING.md).
|
||||
|
||||
## The Basics
|
||||
|
||||
We welcome contributions in the form of pull requests.
|
||||
|
||||
For small changes (e.g., bug fixes), feel free to submit a PR.
|
||||
|
||||
For larger changes (e.g. new diagnostics, new functionality, new configuration options), consider
|
||||
creating an [**issue**](https://github.com/astral-sh/ty/issues) outlining your proposed change.
|
||||
You can also join us on [Discord](https://discord.com/invite/astral-sh) to discuss your idea with the
|
||||
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ty/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
|
||||
in the issue tracker, along with [bugs](https://github.com/astral-sh/ty/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
that are ready for contributions.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
ty is written in Rust. You'll need to install the
|
||||
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
|
||||
|
||||
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
|
||||
run Python utility commands.
|
||||
|
||||
You can optionally install pre-commit hooks to automatically run the validation checks
|
||||
when making a commit:
|
||||
|
||||
```shell
|
||||
uv tool install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
We recommend [nextest](https://nexte.st/) to run ty's test suite (via `cargo nextest run`),
|
||||
though it's not strictly necessary:
|
||||
|
||||
```shell
|
||||
cargo install cargo-nextest --locked
|
||||
```
|
||||
|
||||
Throughout this guide, any usages of `cargo test` can be replaced with `cargo nextest run`,
|
||||
if you choose to install `nextest`.
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run ty locally from the repository root with:
|
||||
|
||||
```shell
|
||||
cargo run --bin ty -- check --project /path/to/project/
|
||||
```
|
||||
|
||||
Prior to opening a pull request, ensure that your code has been auto-formatted,
|
||||
and that it passes both the lint and test validation checks:
|
||||
|
||||
```shell
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
|
||||
cargo test # Rust testing
|
||||
uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your pull request, but running them locally
|
||||
will save you time and expedite the merge process.
|
||||
|
||||
If you're using VS Code, you can also install the recommended [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) extension to get these checks while editing.
|
||||
|
||||
Include the text `[ty]` at the beginning of your pull request title, to distinguish ty pull requests
|
||||
from Ruff ones.
|
||||
|
||||
Your pull request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
prior to merging.
|
||||
|
||||
### Debugging ty
|
||||
|
||||
ty can optionally emit extensive tracing output, which can be very useful in understanding its
|
||||
operation and debugging issues; see [`crates/ty/docs/tracing.md`](./docs/tracing.md) for details.
|
||||
|
||||
### Project Structure
|
||||
|
||||
The codebase is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html),
|
||||
such that all crates are contained in a flat `crates` directory.
|
||||
|
||||
The vast majority of ty's code lives in the `ty_python_semantic` crate (located at
|
||||
`crates/ty_python_semantic`). As a contributor, that's the crate that'll probably be most relevant
|
||||
to you.
|
||||
|
||||
At the time of writing, the repository includes the following ty-specific crates (in addition to
|
||||
crates shared with Ruff, such as `ruff_db`, `ruff_python_ast`, and `ruff_python_parser`):
|
||||
|
||||
- `ty_python_semantic`: The core type checker, which includes the type inference engine and
|
||||
semantic analysis.
|
||||
- `ty_test`: The Markdown-based test framework for ty, "mdtest".
|
||||
- `ty`: The command-line interface.
|
||||
- `ty_ide`: IDE features (hover, go-to-definition, autocomplete) for the language server.
|
||||
- `ty_project`: Discovery and representation of a Python project to be checked by ty.
|
||||
- `ty_server`: The ty language server.
|
||||
- `ty_vendored`: A vendored copy of [typeshed](https://github.com/python/typeshed), which holds type
|
||||
annotations for the Python standard library.
|
||||
- `ty_wasm`: library crate for exposing ty as a WebAssembly module. Powers the
|
||||
[ty Playground](https://play.ty.dev/).
|
||||
|
||||
## Writing tests
|
||||
|
||||
Core type checking tests are written as Markdown code blocks.
|
||||
They can be found in [`crates/ty_python_semantic/resources/mdtest`][resources-mdtest].
|
||||
See [`crates/ty_test/README.md`][mdtest-readme] for more information
|
||||
on the test framework itself.
|
||||
|
||||
Any ty pull request to improve ty's type inference or type checking logic should include mdtests
|
||||
demonstrating the effect of the change.
|
||||
|
||||
We write mdtests in a "literate" style, with prose explaining the motivation of each test, and any
|
||||
context necessary to understand the feature being demonstrated.
|
||||
|
||||
### Property tests
|
||||
|
||||
ty uses property-based testing to test the core type relations. These tests are located in
|
||||
[`crates/ty_python_semantic/src/types/property_tests.rs`](../ty_python_semantic/src/types/property_tests.rs).
|
||||
|
||||
The property tests do not run in CI on every PR, just once daily. It is advisable to run them
|
||||
locally after modifying core type relation methods (`is_subtype_of`, `is_equivalent_to`, etc.) to
|
||||
ensure that the changes do not break any of the properties.
|
||||
|
||||
## Ecosystem CI (mypy-primer)
|
||||
|
||||
GitHub Actions will run your changes against a number of real-world projects from GitHub and
|
||||
report on any linter or formatter differences. See [`crates/ty/docs/mypy_primer.md`](./docs/mypy_primer.md)
|
||||
for instructions on running these checks locally.
|
||||
|
||||
## Coding guidelines
|
||||
|
||||
We use the [Salsa](https:://github.com/salsa-rs/salsa) library for incremental computation. Many
|
||||
methods take a Salsa database (usually `db: &'db dyn Db`) as an argument. This should always be the
|
||||
first argument (or second after `self`).
|
||||
|
||||
[mdtest-readme]: ../ty_test/README.md
|
||||
[resources-mdtest]: ../ty_python_semantic/resources/mdtest
|
||||
@@ -1,25 +1,9 @@
|
||||
# ty
|
||||
|
||||
ty is an extremely fast type checker.
|
||||
Currently, it is a work-in-progress and not ready for user testing.
|
||||
Currently, it is a work-in-progress and not ready for production use.
|
||||
|
||||
ty is designed to prioritize good type inference, even in unannotated code,
|
||||
and aims to avoid false positives.
|
||||
The Rust code for ty lives in this repository; see [CONTRIBUTING.md](CONTRIBUTING.md) for more
|
||||
information on contributing to ty.
|
||||
|
||||
While ty will produce similar results to mypy and pyright on many codebases,
|
||||
100% compatibility with these tools is a non-goal.
|
||||
On some codebases, ty's design decisions lead to different outcomes
|
||||
than you would get from running one of these more established tools.
|
||||
|
||||
## Contributing
|
||||
|
||||
Core type checking tests are written as Markdown code blocks.
|
||||
They can be found in [`ty_python_semantic/resources/mdtest`][resources-mdtest].
|
||||
See [`ty_test/README.md`][mdtest-readme] for more information
|
||||
on the test framework itself.
|
||||
|
||||
The list of open issues can be found [here][open-issues].
|
||||
|
||||
[mdtest-readme]: ../ty_test/README.md
|
||||
[open-issues]: https://github.com/astral-sh/ty/issues
|
||||
[resources-mdtest]: ../ty_python_semantic/resources/mdtest
|
||||
See [the ty repo](https://github.com/astral-sh/ty) for ty documentation and releases.
|
||||
|
||||
@@ -30,7 +30,7 @@ ty check [OPTIONS] [PATH]...
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
|
||||
<dl class="cli-reference"><dt id="ty-check--paths"><a href="#ty-check--paths"<code>PATHS</code></a></dt><dd><p>List of files or directories to check [default: the project root]</p>
|
||||
<dl class="cli-reference"><dt id="ty-check--paths"><a href="#ty-check--paths"><code>PATHS</code></a></dt><dd><p>List of files or directories to check [default: the project root]</p>
|
||||
</dd></dl>
|
||||
|
||||
<h3 class="cli-reference">Options</h3>
|
||||
@@ -125,7 +125,7 @@ ty generate-shell-completion <SHELL>
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
|
||||
<dl class="cli-reference"><dt id="ty-generate-shell-completion--shell"><a href="#ty-generate-shell-completion--shell"<code>SHELL</code></a></dt></dl>
|
||||
<dl class="cli-reference"><dt id="ty-generate-shell-completion--shell"><a href="#ty-generate-shell-completion--shell"><code>SHELL</code></a></dt></dl>
|
||||
|
||||
<h3 class="cli-reference">Options</h3>
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ respect-ignore-files = false
|
||||
|
||||
Configures the enabled rules and their severity.
|
||||
|
||||
See [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules.
|
||||
See [the rules documentation](https://ty.dev/rules) for a list of all available rules.
|
||||
|
||||
Valid severities are:
|
||||
|
||||
|
||||
@@ -50,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#L84)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L85)
|
||||
</details>
|
||||
|
||||
## `conflicting-argument-forms`
|
||||
@@ -81,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#L115)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L116)
|
||||
</details>
|
||||
|
||||
## `conflicting-declarations`
|
||||
@@ -111,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#L141)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142)
|
||||
</details>
|
||||
|
||||
## `conflicting-metaclass`
|
||||
@@ -142,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#L166)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L167)
|
||||
</details>
|
||||
|
||||
## `cyclic-class-definition`
|
||||
@@ -173,7 +173,7 @@ 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#L192)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193)
|
||||
</details>
|
||||
|
||||
## `division-by-zero`
|
||||
@@ -196,7 +196,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
|
||||
### 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#L218)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L219)
|
||||
</details>
|
||||
|
||||
## `duplicate-base`
|
||||
@@ -222,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#L236)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237)
|
||||
</details>
|
||||
|
||||
## `escape-character-in-forward-annotation`
|
||||
@@ -359,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#L257)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258)
|
||||
</details>
|
||||
|
||||
## `inconsistent-mro`
|
||||
@@ -388,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#L343)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L344)
|
||||
</details>
|
||||
|
||||
## `index-out-of-bounds`
|
||||
@@ -413,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#L367)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L368)
|
||||
</details>
|
||||
|
||||
## `invalid-argument-type`
|
||||
@@ -439,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#L387)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388)
|
||||
</details>
|
||||
|
||||
## `invalid-assignment`
|
||||
@@ -466,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#L427)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428)
|
||||
</details>
|
||||
|
||||
## `invalid-attribute-access`
|
||||
@@ -499,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#L1311)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312)
|
||||
</details>
|
||||
|
||||
## `invalid-base`
|
||||
@@ -513,7 +513,7 @@ 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#L449)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450)
|
||||
</details>
|
||||
|
||||
## `invalid-context-manager`
|
||||
@@ -539,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#L458)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L459)
|
||||
</details>
|
||||
|
||||
## `invalid-declaration`
|
||||
@@ -567,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#L479)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L480)
|
||||
</details>
|
||||
|
||||
## `invalid-exception-caught`
|
||||
@@ -608,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#L502)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L503)
|
||||
</details>
|
||||
|
||||
## `invalid-generic-class`
|
||||
@@ -639,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#L538)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L539)
|
||||
</details>
|
||||
|
||||
## `invalid-legacy-type-variable`
|
||||
@@ -672,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#L564)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565)
|
||||
</details>
|
||||
|
||||
## `invalid-metaclass`
|
||||
@@ -704,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#L592)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L593)
|
||||
</details>
|
||||
|
||||
## `invalid-overload`
|
||||
@@ -752,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#L619)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L620)
|
||||
</details>
|
||||
|
||||
## `invalid-parameter-default`
|
||||
@@ -777,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#L662)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663)
|
||||
</details>
|
||||
|
||||
## `invalid-protocol`
|
||||
@@ -810,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#L315)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L316)
|
||||
</details>
|
||||
|
||||
## `invalid-raise`
|
||||
@@ -858,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#L682)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683)
|
||||
</details>
|
||||
|
||||
## `invalid-return-type`
|
||||
@@ -882,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#L408)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409)
|
||||
</details>
|
||||
|
||||
## `invalid-super-argument`
|
||||
@@ -926,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#L725)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L726)
|
||||
</details>
|
||||
|
||||
## `invalid-syntax-in-forward-annotation`
|
||||
@@ -969,7 +969,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#L764)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
|
||||
</details>
|
||||
|
||||
## `invalid-type-form`
|
||||
@@ -997,7 +997,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#L788)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L789)
|
||||
</details>
|
||||
|
||||
## `invalid-type-variable-constraints`
|
||||
@@ -1031,7 +1031,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#L811)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812)
|
||||
</details>
|
||||
|
||||
## `missing-argument`
|
||||
@@ -1055,7 +1055,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#L840)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841)
|
||||
</details>
|
||||
|
||||
## `no-matching-overload`
|
||||
@@ -1083,7 +1083,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#L859)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L860)
|
||||
</details>
|
||||
|
||||
## `non-subscriptable`
|
||||
@@ -1106,7 +1106,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#L882)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L883)
|
||||
</details>
|
||||
|
||||
## `not-iterable`
|
||||
@@ -1131,7 +1131,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#L900)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901)
|
||||
</details>
|
||||
|
||||
## `parameter-already-assigned`
|
||||
@@ -1157,7 +1157,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#L951)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L952)
|
||||
</details>
|
||||
|
||||
## `raw-string-type-annotation`
|
||||
@@ -1216,7 +1216,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#L1287)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1288)
|
||||
</details>
|
||||
|
||||
## `subclass-of-final-class`
|
||||
@@ -1244,7 +1244,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#L1042)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043)
|
||||
</details>
|
||||
|
||||
## `too-many-positional-arguments`
|
||||
@@ -1270,7 +1270,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#L1087)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1088)
|
||||
</details>
|
||||
|
||||
## `type-assertion-failure`
|
||||
@@ -1297,7 +1297,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#L1065)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1066)
|
||||
</details>
|
||||
|
||||
## `unavailable-implicit-super-arguments`
|
||||
@@ -1341,7 +1341,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#L1108)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1109)
|
||||
</details>
|
||||
|
||||
## `unknown-argument`
|
||||
@@ -1367,7 +1367,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#L1165)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1166)
|
||||
</details>
|
||||
|
||||
## `unresolved-attribute`
|
||||
@@ -1394,7 +1394,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#L1186)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1187)
|
||||
</details>
|
||||
|
||||
## `unresolved-import`
|
||||
@@ -1418,7 +1418,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#L1208)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1209)
|
||||
</details>
|
||||
|
||||
## `unresolved-reference`
|
||||
@@ -1442,7 +1442,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#L1227)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1228)
|
||||
</details>
|
||||
|
||||
## `unsupported-bool-conversion`
|
||||
@@ -1478,7 +1478,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#L920)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L921)
|
||||
</details>
|
||||
|
||||
## `unsupported-operator`
|
||||
@@ -1505,7 +1505,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#L1246)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1247)
|
||||
</details>
|
||||
|
||||
## `zero-stepsize-in-slice`
|
||||
@@ -1529,7 +1529,7 @@ 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#L1268)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269)
|
||||
</details>
|
||||
|
||||
## `call-possibly-unbound-method`
|
||||
@@ -1547,7 +1547,7 @@ 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#L102)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L103)
|
||||
</details>
|
||||
|
||||
## `invalid-ignore-comment`
|
||||
@@ -1603,7 +1603,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#L972)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L973)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-import`
|
||||
@@ -1634,7 +1634,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#L994)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L995)
|
||||
</details>
|
||||
|
||||
## `redundant-cast`
|
||||
@@ -1660,7 +1660,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#L1339)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1340)
|
||||
</details>
|
||||
|
||||
## `undefined-reveal`
|
||||
@@ -1683,7 +1683,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#L1147)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148)
|
||||
</details>
|
||||
|
||||
## `unknown-rule`
|
||||
@@ -1740,7 +1740,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#L1020)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1021)
|
||||
</details>
|
||||
|
||||
## `unused-ignore-comment`
|
||||
|
||||
@@ -31,7 +31,7 @@ pub struct Options {
|
||||
|
||||
/// Configures the enabled rules and their severity.
|
||||
///
|
||||
/// See [the rules documentation](https://github.com/astral-sh/ty/blob/main/docs/rules.md) for a list of all available rules.
|
||||
/// See [the rules documentation](https://ty.dev/rules) for a list of all available rules.
|
||||
///
|
||||
/// Valid severities are:
|
||||
///
|
||||
|
||||
@@ -19,7 +19,7 @@ class Shape:
|
||||
reveal_type(self) # revealed: Self
|
||||
return self
|
||||
|
||||
def nested_type(self) -> list[Self]:
|
||||
def nested_type(self: Self) -> list[Self]:
|
||||
return [self]
|
||||
|
||||
def nested_func(self: Self) -> Self:
|
||||
@@ -33,9 +33,7 @@ class Shape:
|
||||
reveal_type(self) # revealed: Unknown
|
||||
return self
|
||||
|
||||
# TODO: should be `list[Shape]`
|
||||
reveal_type(Shape().nested_type()) # revealed: list[Self]
|
||||
|
||||
reveal_type(Shape().nested_type()) # revealed: list[Shape]
|
||||
reveal_type(Shape().nested_func()) # revealed: Shape
|
||||
|
||||
class Circle(Shape):
|
||||
|
||||
@@ -13,11 +13,10 @@ from typing_extensions import TypeVarTuple
|
||||
Ts = TypeVarTuple("Ts")
|
||||
|
||||
def append_int(*args: *Ts) -> tuple[*Ts, int]:
|
||||
# TODO: tuple[*Ts]
|
||||
reveal_type(args) # revealed: tuple[Unknown, ...]
|
||||
reveal_type(args) # revealed: @Todo(PEP 646)
|
||||
|
||||
return (*args, 1)
|
||||
|
||||
# TODO should be tuple[Literal[True], Literal["a"], int]
|
||||
reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(append_int(True, "a")) # revealed: @Todo(PEP 646)
|
||||
```
|
||||
|
||||
@@ -15,16 +15,13 @@ R_co = TypeVar("R_co", covariant=True)
|
||||
Alias: TypeAlias = int
|
||||
|
||||
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||
# TODO: should understand the annotation
|
||||
reveal_type(args) # revealed: tuple[Unknown, ...]
|
||||
|
||||
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
|
||||
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
|
||||
|
||||
def g() -> TypeGuard[int]: ...
|
||||
def h() -> TypeIs[int]: ...
|
||||
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
|
||||
# TODO: should understand the annotation
|
||||
reveal_type(args) # revealed: tuple[Unknown, ...]
|
||||
reveal_type(args) # revealed: tuple[@Todo(Support for `typing.ParamSpec`), ...]
|
||||
reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)]
|
||||
return callback(42, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -56,13 +56,12 @@ reveal_type(a) # revealed: tuple[()]
|
||||
reveal_type(b) # revealed: tuple[int]
|
||||
reveal_type(c) # revealed: tuple[str, int]
|
||||
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
|
||||
reveal_type(e) # revealed: tuple[str, ...]
|
||||
|
||||
reveal_type(f) # revealed: @Todo(PEP 646)
|
||||
reveal_type(g) # revealed: @Todo(PEP 646)
|
||||
|
||||
# TODO: homogeneous tuples, PEP-646 tuples, generics
|
||||
reveal_type(e) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(f) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(g) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(h) # revealed: tuple[list[int], list[int]]
|
||||
|
||||
reveal_type(i) # revealed: tuple[str | int, str | int]
|
||||
reveal_type(j) # revealed: tuple[str | int]
|
||||
```
|
||||
|
||||
@@ -1462,6 +1462,14 @@ Instance attributes also take precedence over the `__getattr__` method:
|
||||
reveal_type(c.instance_attr) # revealed: str
|
||||
```
|
||||
|
||||
Importantly, `__getattr__` is only called if attributes are accessed on instances, not if they are
|
||||
accessed on the class itself:
|
||||
|
||||
```py
|
||||
# error: [unresolved-attribute]
|
||||
CustomGetAttr.whatever
|
||||
```
|
||||
|
||||
### Type of the `name` parameter
|
||||
|
||||
If the `name` parameter of the `__getattr__` method is annotated with a (union of) literal type(s),
|
||||
@@ -1676,7 +1684,7 @@ functions are instances of that class:
|
||||
```py
|
||||
def f(): ...
|
||||
|
||||
reveal_type(f.__defaults__) # revealed: @Todo(full tuple[...] support) | None
|
||||
reveal_type(f.__defaults__) # revealed: tuple[Any, ...] | None
|
||||
reveal_type(f.__kwdefaults__) # revealed: dict[str, Any] | None
|
||||
```
|
||||
|
||||
@@ -1730,7 +1738,7 @@ All attribute access on literal `bytes` types is currently delegated to `builtin
|
||||
```py
|
||||
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
|
||||
reveal_type(b"foo".join)
|
||||
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
|
||||
# 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
|
||||
reveal_type(b"foo".endswith)
|
||||
```
|
||||
|
||||
|
||||
@@ -317,8 +317,7 @@ reveal_type(A() + b"foo") # revealed: A
|
||||
reveal_type(b"foo" + A()) # revealed: bytes
|
||||
|
||||
reveal_type(A() + ()) # revealed: A
|
||||
# TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances
|
||||
reveal_type(() + A()) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(() + A()) # revealed: A
|
||||
|
||||
literal_string_instance = "foo" * 1_000_000_000
|
||||
# the test is not testing what it's meant to be testing if this isn't a `LiteralString`:
|
||||
|
||||
@@ -17,6 +17,6 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
||||
|
||||
```py
|
||||
def _(x: tuple[int, ...], y: tuple[str, ...]):
|
||||
reveal_type(x + y) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(x + (1, 2)) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||
reveal_type(x + (1, 2)) # revealed: tuple[int, ...]
|
||||
```
|
||||
|
||||
@@ -54,7 +54,7 @@ type(b"Foo", (), {})
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
type("Foo", Base, {})
|
||||
|
||||
# TODO: this should be an error
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
type("Foo", (1, 2), {})
|
||||
|
||||
# TODO: this should be an error
|
||||
|
||||
@@ -13,3 +13,21 @@ def f(x: f):
|
||||
|
||||
reveal_type(f) # revealed: def f(x: Unknown) -> Unknown
|
||||
```
|
||||
|
||||
## Unpacking
|
||||
|
||||
See: <https://github.com/astral-sh/ty/issues/364>
|
||||
|
||||
```py
|
||||
class Point:
|
||||
def __init__(self, x: int = 0, y: int = 0) -> None:
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def replace_with(self, other: "Point") -> None:
|
||||
self.x, self.y = other.x, other.y
|
||||
|
||||
p = Point()
|
||||
reveal_type(p.x) # revealed: Unknown | int
|
||||
reveal_type(p.y) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
@@ -616,6 +616,25 @@ reveal_type(C.__init__) # revealed: (field: str | int = int) -> None
|
||||
|
||||
To do
|
||||
|
||||
## `dataclass.fields`
|
||||
|
||||
Dataclasses have `__dataclass_fields__` in them, which makes them a subtype of the
|
||||
`DataclassInstance` protocol.
|
||||
|
||||
Here, we verify that dataclasses can be passed to `dataclasses.fields` without any errors, and that
|
||||
the return type of `dataclasses.fields` is correct.
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass, fields
|
||||
|
||||
@dataclass
|
||||
class Foo:
|
||||
x: int
|
||||
|
||||
reveal_type(Foo.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
reveal_type(fields(Foo)) # revealed: tuple[Field[Any], ...]
|
||||
```
|
||||
|
||||
## Other special cases
|
||||
|
||||
### `dataclasses.dataclass`
|
||||
|
||||
@@ -4,11 +4,277 @@
|
||||
|
||||
## Calls to overloaded functions
|
||||
|
||||
TODO: Note that we do not yet support the `@overload` decorator to define overloaded functions in
|
||||
real Python code. We are instead testing a special-cased function where we create an overloaded
|
||||
signature internally. Update this to an `@overload` function in the Python snippet itself once we
|
||||
can.
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(x: int) -> int: ...
|
||||
@overload
|
||||
def f(x: str) -> str: ...
|
||||
def f(x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
## Call to function with many unmatched overloads
|
||||
|
||||
Note that it would be fine to use `pow` here as an example of a routine with many overloads, but at
|
||||
time of writing (2025-05-14), ty doesn't support some of the type signatures of those overloads.
|
||||
Which in turn makes snapshotting a bit annoying, since the output can depend on how ty is compiled
|
||||
(because of how `Todo` types are dealt with when `debug_assertions` is enabled versus disabled).
|
||||
|
||||
```py
|
||||
type("Foo", ()) # error: [no-matching-overload]
|
||||
from typing import overload
|
||||
|
||||
class Foo: ...
|
||||
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: str, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: float, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: float, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
def foo(a, b, c): ...
|
||||
|
||||
foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
## Call to function with too many unmatched overloads
|
||||
|
||||
This is like the above example, but has an excessive number of overloads to the point that ty will
|
||||
cut off the list in the diagnostic and emit a message stating the number of omitted overloads.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
class Foo: ...
|
||||
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: str, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: float, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: float, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[str], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[float], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[float], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: str, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: str, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: bool, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: bool, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: float, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: float, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: float, c: float): ...
|
||||
def foo(a, b, c): ...
|
||||
|
||||
foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
## Calls to overloaded functions with lots of parameters
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(
|
||||
lion: int,
|
||||
turtle: int,
|
||||
tortoise: int,
|
||||
goat: int,
|
||||
capybara: int,
|
||||
chicken: int,
|
||||
ostrich: int,
|
||||
gorilla: int,
|
||||
giraffe: int,
|
||||
condor: int,
|
||||
kangaroo: int,
|
||||
anaconda: int,
|
||||
tarantula: int,
|
||||
millipede: int,
|
||||
leopard: int,
|
||||
hyena: int,
|
||||
) -> int: ...
|
||||
@overload
|
||||
def f(
|
||||
lion: str,
|
||||
turtle: str,
|
||||
tortoise: str,
|
||||
goat: str,
|
||||
capybara: str,
|
||||
chicken: str,
|
||||
ostrich: str,
|
||||
gorilla: str,
|
||||
giraffe: str,
|
||||
condor: str,
|
||||
kangaroo: str,
|
||||
anaconda: str,
|
||||
tarantula: str,
|
||||
millipede: str,
|
||||
leopard: str,
|
||||
hyena: str,
|
||||
) -> str: ...
|
||||
def f(
|
||||
lion: int | str,
|
||||
turtle: int | str,
|
||||
tortoise: int | str,
|
||||
goat: int | str,
|
||||
capybara: int | str,
|
||||
chicken: int | str,
|
||||
ostrict: int | str,
|
||||
gorilla: int | str,
|
||||
giraffe: int | str,
|
||||
condor: int | str,
|
||||
kangaroo: int | str,
|
||||
anaconda: int | str,
|
||||
tarantula: int | str,
|
||||
millipede: int | str,
|
||||
leopard: int | str,
|
||||
hyena: int | str,
|
||||
) -> int | str:
|
||||
return 0
|
||||
|
||||
f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Diagnostics for unresolved references
|
||||
|
||||
## New builtin used on old Python version
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
aiter # error: [unresolved-reference]
|
||||
```
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## Basic functionality
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`assert_never` makes sure that the type of the argument is `Never`. If it is not, a
|
||||
`type-assertion-failure` diagnostic is emitted.
|
||||
|
||||
@@ -58,7 +60,7 @@ def if_else_isinstance_error(obj: A | B):
|
||||
elif isinstance(obj, C):
|
||||
pass
|
||||
else:
|
||||
# error: [type-assertion-failure] "Expected type `Never`, got `B & ~A & ~C` instead"
|
||||
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||
assert_never(obj)
|
||||
|
||||
def if_else_singletons_success(obj: Literal[1, "a"] | None):
|
||||
@@ -79,7 +81,7 @@ def if_else_singletons_error(obj: Literal[1, "a"] | None):
|
||||
elif obj is None:
|
||||
pass
|
||||
else:
|
||||
# error: [type-assertion-failure] "Expected type `Never`, got `Literal["a"]` instead"
|
||||
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||
assert_never(obj)
|
||||
|
||||
def match_singletons_success(obj: Literal[1, "a"] | None):
|
||||
@@ -92,7 +94,7 @@ def match_singletons_success(obj: Literal[1, "a"] | None):
|
||||
pass
|
||||
case _ as obj:
|
||||
# TODO: Ideally, we would not emit an error here
|
||||
# error: [type-assertion-failure] "Expected type `Never`, got `@Todo"
|
||||
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||
assert_never(obj)
|
||||
|
||||
def match_singletons_error(obj: Literal[1, "a"] | None):
|
||||
@@ -106,6 +108,6 @@ def match_singletons_error(obj: Literal[1, "a"] | None):
|
||||
case _ as obj:
|
||||
# TODO: We should emit an error here, but the message should
|
||||
# show the type `Literal["a"]` instead of `@Todo(…)`.
|
||||
# error: [type-assertion-failure] "Expected type `Never`, got `@Todo"
|
||||
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||
assert_never(obj)
|
||||
```
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## Basic
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ def foo(
|
||||
x: type[AttributeError],
|
||||
y: tuple[type[OSError], type[RuntimeError]],
|
||||
z: tuple[type[BaseException], ...],
|
||||
zz: tuple[type[TypeError | RuntimeError], ...],
|
||||
zzz: type[BaseException] | tuple[type[BaseException], ...],
|
||||
):
|
||||
try:
|
||||
help()
|
||||
@@ -53,8 +55,11 @@ def foo(
|
||||
except y as f:
|
||||
reveal_type(f) # revealed: OSError | RuntimeError
|
||||
except z as g:
|
||||
# TODO: should be `BaseException`
|
||||
reveal_type(g) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(g) # revealed: BaseException
|
||||
except zz as h:
|
||||
reveal_type(h) # revealed: TypeError | RuntimeError
|
||||
except zzz as i:
|
||||
reveal_type(i) # revealed: BaseException
|
||||
```
|
||||
|
||||
## Invalid exception handlers
|
||||
@@ -86,9 +91,9 @@ def foo(
|
||||
# error: [invalid-exception-caught]
|
||||
except y as f:
|
||||
reveal_type(f) # revealed: OSError | RuntimeError | Unknown
|
||||
# error: [invalid-exception-caught]
|
||||
except z as g:
|
||||
# TODO: should emit a diagnostic here:
|
||||
reveal_type(g) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(g) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Object raised is not an exception
|
||||
|
||||
@@ -22,9 +22,7 @@ except* BaseException as e:
|
||||
try:
|
||||
help()
|
||||
except* OSError as e:
|
||||
# TODO: more precise would be `ExceptionGroup[OSError]` --Alex
|
||||
# (needs homogeneous tuples + generics)
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[BaseException]
|
||||
reveal_type(e) # revealed: ExceptionGroup[OSError]
|
||||
```
|
||||
|
||||
## `except*` with multiple exceptions
|
||||
@@ -33,9 +31,7 @@ except* OSError as e:
|
||||
try:
|
||||
help()
|
||||
except* (TypeError, AttributeError) as e:
|
||||
# TODO: more precise would be `ExceptionGroup[TypeError | AttributeError]` --Alex
|
||||
# (needs homogeneous tuples + generics)
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[BaseException]
|
||||
reveal_type(e) # revealed: ExceptionGroup[TypeError | AttributeError]
|
||||
```
|
||||
|
||||
## `except*` with mix of `Exception`s and `BaseException`s
|
||||
@@ -44,8 +40,7 @@ except* (TypeError, AttributeError) as e:
|
||||
try:
|
||||
help()
|
||||
except* (KeyboardInterrupt, AttributeError) as e:
|
||||
# TODO: more precise would be `BaseExceptionGroup[KeyboardInterrupt | AttributeError]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[BaseException]
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[KeyboardInterrupt | AttributeError]
|
||||
```
|
||||
|
||||
## Invalid `except*` handlers
|
||||
@@ -54,10 +49,10 @@ except* (KeyboardInterrupt, AttributeError) as e:
|
||||
try:
|
||||
help()
|
||||
except* 3 as e: # error: [invalid-exception-caught]
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[BaseException]
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[Unknown]
|
||||
|
||||
try:
|
||||
help()
|
||||
except* (AttributeError, 42) as e: # error: [invalid-exception-caught]
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[BaseException]
|
||||
reveal_type(e) # revealed: BaseExceptionGroup[AttributeError | Unknown]
|
||||
```
|
||||
|
||||
@@ -25,8 +25,7 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5,
|
||||
reveal_type(f) # revealed: Literal[4]
|
||||
reveal_type(g) # revealed: Unknown | Literal[5]
|
||||
reveal_type(h) # revealed: Literal[6]
|
||||
# TODO: should be `tuple[object, ...]`
|
||||
reveal_type(args) # revealed: tuple[Unknown, ...]
|
||||
reveal_type(args) # revealed: tuple[object, ...]
|
||||
reveal_type(kwargs) # revealed: dict[str, str]
|
||||
```
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ def f(cond: bool) -> str:
|
||||
return "hello" if cond else NotImplemented
|
||||
|
||||
def f(cond: bool) -> int:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `int`, found `Literal["hello"]`"
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `int`, found `Literal["hello"]`"
|
||||
return "hello" if cond else NotImplemented
|
||||
```
|
||||
|
||||
|
||||
@@ -66,18 +66,68 @@ reveal_type(f("string")) # revealed: Literal["string"]
|
||||
## Inferring “deep” generic parameter types
|
||||
|
||||
The matching up of call arguments and discovery of constraints on typevars can be a recursive
|
||||
process for arbitrarily-nested generic types in parameters.
|
||||
process for arbitrarily-nested generic classes and protocols in parameters.
|
||||
|
||||
TODO: Note that we can currently only infer a specialization for a generic protocol when the
|
||||
argument _explicitly_ implements the protocol by listing it as a base class.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def f(x: list[T]) -> T:
|
||||
class CanIndex(Protocol[T]):
|
||||
def __getitem__(self, index: int) -> T: ...
|
||||
|
||||
class ExplicitlyImplements(CanIndex[T]): ...
|
||||
|
||||
def takes_in_list(x: list[T]) -> list[T]:
|
||||
return x
|
||||
|
||||
def takes_in_protocol(x: CanIndex[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: float
|
||||
reveal_type(f([1.0, 2.0])) # revealed: Unknown
|
||||
def deep_list(x: list[str]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deeper_list(x: list[set[str]]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[set[str]]
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deep_explicit(x: ExplicitlyImplements[str]) -> None:
|
||||
reveal_type(takes_in_protocol(x)) # revealed: str
|
||||
|
||||
def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||
reveal_type(takes_in_protocol(x)) # revealed: set[str]
|
||||
|
||||
def takes_in_type(x: type[T]) -> type[T]:
|
||||
return x
|
||||
|
||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
||||
```
|
||||
|
||||
This also works when passing in arguments that are subclasses of the parameter type.
|
||||
|
||||
```py
|
||||
class Sub(list[int]): ...
|
||||
class GenericSub(list[T]): ...
|
||||
|
||||
reveal_type(takes_in_list(Sub())) # revealed: list[int]
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
|
||||
|
||||
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown
|
||||
|
||||
class ExplicitSub(ExplicitlyImplements[int]): ...
|
||||
class ExplicitGenericSub(ExplicitlyImplements[T]): ...
|
||||
|
||||
reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int
|
||||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
@@ -144,7 +194,7 @@ def good_return(x: T) -> T:
|
||||
return x
|
||||
|
||||
def bad_return(x: T) -> T:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `int`"
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`"
|
||||
return x + 1
|
||||
```
|
||||
|
||||
@@ -162,7 +212,7 @@ def different_types(cond: bool, t: T, s: S) -> T:
|
||||
if cond:
|
||||
return t
|
||||
else:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `S`"
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`"
|
||||
return s
|
||||
|
||||
def same_types(cond: bool, t1: T, t2: T) -> T:
|
||||
|
||||
@@ -61,14 +61,68 @@ reveal_type(f("string")) # revealed: Literal["string"]
|
||||
## Inferring “deep” generic parameter types
|
||||
|
||||
The matching up of call arguments and discovery of constraints on typevars can be a recursive
|
||||
process for arbitrarily-nested generic types in parameters.
|
||||
process for arbitrarily-nested generic classes and protocols in parameters.
|
||||
|
||||
TODO: Note that we can currently only infer a specialization for a generic protocol when the
|
||||
argument _explicitly_ implements the protocol by listing it as a base class.
|
||||
|
||||
```py
|
||||
def f[T](x: list[T]) -> T:
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
class CanIndex(Protocol[S]):
|
||||
def __getitem__(self, index: int) -> S: ...
|
||||
|
||||
class ExplicitlyImplements[T](CanIndex[T]): ...
|
||||
|
||||
def takes_in_list[T](x: list[T]) -> list[T]:
|
||||
return x
|
||||
|
||||
def takes_in_protocol[T](x: CanIndex[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: float
|
||||
reveal_type(f([1.0, 2.0])) # revealed: Unknown
|
||||
def deep_list(x: list[str]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deeper_list(x: list[set[str]]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[set[str]]
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deep_explicit(x: ExplicitlyImplements[str]) -> None:
|
||||
reveal_type(takes_in_protocol(x)) # revealed: str
|
||||
|
||||
def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||
reveal_type(takes_in_protocol(x)) # revealed: set[str]
|
||||
|
||||
def takes_in_type[T](x: type[T]) -> type[T]:
|
||||
return x
|
||||
|
||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
||||
```
|
||||
|
||||
This also works when passing in arguments that are subclasses of the parameter type.
|
||||
|
||||
```py
|
||||
class Sub(list[int]): ...
|
||||
class GenericSub[T](list[T]): ...
|
||||
|
||||
reveal_type(takes_in_list(Sub())) # revealed: list[int]
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
|
||||
|
||||
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown
|
||||
|
||||
class ExplicitSub(ExplicitlyImplements[int]): ...
|
||||
class ExplicitGenericSub[T](ExplicitlyImplements[T]): ...
|
||||
|
||||
reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int
|
||||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
@@ -125,7 +179,7 @@ def good_return[T: int](x: T) -> T:
|
||||
return x
|
||||
|
||||
def bad_return[T: int](x: T) -> T:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `int`"
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`"
|
||||
return x + 1
|
||||
```
|
||||
|
||||
@@ -138,7 +192,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T:
|
||||
if cond:
|
||||
return t
|
||||
else:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `S`"
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`"
|
||||
return s
|
||||
|
||||
def same_types[T](cond: bool, t1: T, t2: T) -> T:
|
||||
|
||||
108
crates/ty_python_semantic/resources/mdtest/import/cyclic.md
Normal file
108
crates/ty_python_semantic/resources/mdtest/import/cyclic.md
Normal file
@@ -0,0 +1,108 @@
|
||||
## Cyclic imports
|
||||
|
||||
### Regression tests
|
||||
|
||||
#### Issue 261
|
||||
|
||||
See: <https://github.com/astral-sh/ty/issues/261>
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from foo import bar
|
||||
|
||||
reveal_type(bar) # revealed: <module 'foo.bar'>
|
||||
```
|
||||
|
||||
`foo/__init__.py`:
|
||||
|
||||
```py
|
||||
from foo import bar
|
||||
|
||||
__all__ = ["bar"]
|
||||
```
|
||||
|
||||
`foo/bar/__init__.py`:
|
||||
|
||||
```py
|
||||
# empty
|
||||
```
|
||||
|
||||
#### Issue 113
|
||||
|
||||
See: <https://github.com/astral-sh/ty/issues/113>
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from pkg.sub import A
|
||||
|
||||
# TODO: This should be `<class 'A'>`
|
||||
reveal_type(A) # revealed: Never
|
||||
```
|
||||
|
||||
`pkg/outer.py`:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
```
|
||||
|
||||
`pkg/sub/__init__.py`:
|
||||
|
||||
```py
|
||||
from ..outer import *
|
||||
from .inner import *
|
||||
```
|
||||
|
||||
`pkg/sub/inner.py`:
|
||||
|
||||
```py
|
||||
from pkg.sub import A
|
||||
```
|
||||
|
||||
### Actual cycle
|
||||
|
||||
The following example fails at runtime. Ideally, we would emit a diagnostic here. For now, we only
|
||||
make sure that this does not lead to a module resolution cycle.
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from module import x
|
||||
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
# error: [unresolved-import]
|
||||
from module import x
|
||||
```
|
||||
|
||||
### Normal self-referential import
|
||||
|
||||
Some modules like `sys` in typeshed import themselves. Here, we make sure that this does not lead to
|
||||
cycles or unresolved imports.
|
||||
|
||||
`module/__init__.py`:
|
||||
|
||||
```py
|
||||
import module # self-referential import
|
||||
|
||||
from module.sub import x
|
||||
```
|
||||
|
||||
`module/sub.py`:
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from module import x
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
@@ -177,6 +177,23 @@ if not isinstance(DoesNotExist, type):
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
## Inheritance from `type[Any]` and `type[Unknown]`
|
||||
|
||||
Inheritance from `type[Any]` and `type[Unknown]` is also permitted, in keeping with the gradual
|
||||
guarantee:
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import Unknown, Intersection
|
||||
|
||||
def f(x: type[Any], y: Intersection[Unknown, type[Any]]):
|
||||
class Foo(x): ...
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
|
||||
|
||||
class Bar(y): ...
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
## `__bases__` lists that cause errors at runtime
|
||||
|
||||
If the class's `__bases__` cause an exception to be raised at runtime and therefore the class
|
||||
|
||||
@@ -149,3 +149,20 @@ Person = namedtuple("Person", ["id", "name", "age"], defaults=[None])
|
||||
alice = Person(1, "Alice", 42)
|
||||
bob = Person(2, "Bob")
|
||||
```
|
||||
|
||||
## NamedTuple with custom `__getattr__`
|
||||
|
||||
This is a regression test for <https://github.com/astral-sh/ty/issues/322>. Make sure that the
|
||||
`__getattr__` method does not interfere with the `NamedTuple` behavior.
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
|
||||
class Vec2(NamedTuple):
|
||||
x: float = 0.0
|
||||
y: float = 0.0
|
||||
|
||||
def __getattr__(self, attrs: str): ...
|
||||
|
||||
Vec2(0.0, 0.0)
|
||||
```
|
||||
|
||||
@@ -15,12 +15,24 @@ def f(x: Foo):
|
||||
if hasattr(x, "spam"):
|
||||
reveal_type(x) # revealed: Foo & <Protocol with members 'spam'>
|
||||
reveal_type(x.spam) # revealed: object
|
||||
else:
|
||||
reveal_type(x) # revealed: Foo & ~<Protocol with members 'spam'>
|
||||
|
||||
# TODO: should error and reveal `Unknown`
|
||||
reveal_type(x.spam) # revealed: @Todo(map_with_boundness: intersections with negative contributions)
|
||||
|
||||
if hasattr(x, "not-an-identifier"):
|
||||
reveal_type(x) # revealed: Foo
|
||||
else:
|
||||
reveal_type(x) # revealed: Foo
|
||||
|
||||
def y(x: Bar):
|
||||
if hasattr(x, "spam"):
|
||||
reveal_type(x) # revealed: Never
|
||||
reveal_type(x.spam) # revealed: Never
|
||||
else:
|
||||
reveal_type(x) # revealed: Bar
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(x.spam) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -68,10 +68,7 @@ y: MyIntOrStr = None
|
||||
|
||||
```py
|
||||
type ListOrSet[T] = list[T] | set[T]
|
||||
|
||||
# TODO: Should be `tuple[typing.TypeVar | typing.ParamSpec | typing.TypeVarTuple, ...]`,
|
||||
# as specified in the `typeshed` stubs.
|
||||
reveal_type(ListOrSet.__type_params__) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(ListOrSet.__type_params__) # revealed: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
```
|
||||
|
||||
## `TypeAliasType` properties
|
||||
|
||||
@@ -8,7 +8,8 @@ is unbound.
|
||||
|
||||
```py
|
||||
reveal_type(__name__) # revealed: str
|
||||
reveal_type(__file__) # revealed: str | None
|
||||
# Typeshed says this is str | None, but for a pure-Python on-disk module its always str
|
||||
reveal_type(__file__) # revealed: str
|
||||
reveal_type(__loader__) # revealed: LoaderProtocol | None
|
||||
reveal_type(__package__) # revealed: str | None
|
||||
reveal_type(__doc__) # revealed: str | None
|
||||
@@ -39,6 +40,39 @@ reveal_type(__dict__)
|
||||
reveal_type(__init__)
|
||||
```
|
||||
|
||||
## `ModuleType` globals combined with explicit assignments and declarations
|
||||
|
||||
A `ModuleType` attribute can be overridden in the global scope with a different type, but it must be
|
||||
a type assignable to the declaration on `ModuleType` unless it is accompanied by an explicit
|
||||
redeclaration:
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
__file__ = None
|
||||
__path__: list[str] = []
|
||||
__doc__: int # error: [invalid-declaration] "Cannot declare type `int` for inferred type `str | None`"
|
||||
# error: [invalid-declaration] "Cannot shadow implicit global attribute `__package__` with declaration of type `int`"
|
||||
__package__: int = 42
|
||||
__spec__ = 42 # error: [invalid-assignment] "Object of type `Literal[42]` is not assignable to `ModuleSpec | None`"
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import module
|
||||
|
||||
reveal_type(module.__file__) # revealed: Unknown | None
|
||||
reveal_type(module.__path__) # revealed: list[str]
|
||||
reveal_type(module.__doc__) # revealed: Unknown
|
||||
reveal_type(module.__spec__) # revealed: Unknown | ModuleSpec | None
|
||||
|
||||
def nested_scope():
|
||||
global __loader__
|
||||
reveal_type(__loader__) # revealed: LoaderProtocol | None
|
||||
__loader__ = 56 # error: [invalid-assignment] "Object of type `Literal[56]` is not assignable to `LoaderProtocol | None`"
|
||||
```
|
||||
|
||||
## Accessed as attributes
|
||||
|
||||
`ModuleType` attributes can also be accessed as attributes on module-literal types. The special
|
||||
@@ -52,6 +86,10 @@ import typing
|
||||
reveal_type(typing.__name__) # revealed: str
|
||||
reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = ellipsis) -> None
|
||||
|
||||
# For a stub module, we don't know that `__file__` is a string (at runtime it may be entirely
|
||||
# unset, but we follow typeshed here):
|
||||
reveal_type(typing.__file__) # revealed: str | None
|
||||
|
||||
# These come from `builtins.object`, not `types.ModuleType`:
|
||||
reveal_type(typing.__eq__) # revealed: bound method ModuleType.__eq__(value: object, /) -> bool
|
||||
|
||||
@@ -100,16 +138,16 @@ defined as a global, however, a name lookup should union the `ModuleType` type w
|
||||
conditionally defined type:
|
||||
|
||||
```py
|
||||
__file__ = 42
|
||||
__file__ = "foo"
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
if returns_bool():
|
||||
__name__ = 1
|
||||
__name__ = 1 # error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
|
||||
|
||||
reveal_type(__file__) # revealed: Literal[42]
|
||||
reveal_type(__name__) # revealed: Literal[1] | str
|
||||
reveal_type(__file__) # revealed: Literal["foo"]
|
||||
reveal_type(__name__) # revealed: str
|
||||
```
|
||||
|
||||
## Conditionally global or `ModuleType` attribute, with annotation
|
||||
@@ -117,12 +155,14 @@ reveal_type(__name__) # revealed: Literal[1] | str
|
||||
The same is true if the name is annotated:
|
||||
|
||||
```py
|
||||
# error: [invalid-declaration] "Cannot shadow implicit global attribute `__file__` with declaration of type `int`"
|
||||
__file__: int = 42
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
if returns_bool():
|
||||
# error: [invalid-declaration] "Cannot shadow implicit global attribute `__name__` with declaration of type `int`"
|
||||
__name__: int = 1
|
||||
|
||||
reveal_type(__file__) # revealed: Literal[42]
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assert_never.md - `assert_never` - Basic functionality
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import assert_never, Never, Any
|
||||
2 | from ty_extensions import Unknown
|
||||
3 |
|
||||
4 | def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
|
||||
5 | assert_never(never) # fine
|
||||
6 |
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
14 |
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:7:5
|
||||
|
|
||||
5 | assert_never(never) # fine
|
||||
6 |
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^-^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[0]`
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Literal[0]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:8:5
|
||||
|
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[""]`
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Literal[""]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:9:5
|
||||
|
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^----^
|
||||
| |
|
||||
| Inferred type of argument is `None`
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `None` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:10:5
|
||||
|
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `list[Unknown]`
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `list[Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
|
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `dict[Unknown, Unknown]`
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:12:5
|
||||
|
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `tuple[()]`
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `tuple[()]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:13:5
|
||||
|
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--------------------^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[1]`
|
||||
14 |
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Literal[1]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:15:5
|
||||
|
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
14 |
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^----^
|
||||
| |
|
||||
| Inferred type of argument is `Any`
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Any` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:16:5
|
||||
|
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^-------^
|
||||
| |
|
||||
| Inferred type of argument is `Unknown`
|
||||
|
|
||||
info: `Never` and `Unknown` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assert_type.md - `assert_type` - Basic
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import assert_type
|
||||
2 |
|
||||
3 | def _(x: int):
|
||||
4 | assert_type(x, int) # fine
|
||||
5 | assert_type(x, str) # error: [type-assertion-failure]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `str`
|
||||
--> src/mdtest_snippet.py:5:5
|
||||
|
|
||||
3 | def _(x: int):
|
||||
4 | assert_type(x, int) # fine
|
||||
5 | assert_type(x, str) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^-^^^^^^
|
||||
| |
|
||||
| Inferred type of argument is `int`
|
||||
|
|
||||
info: `str` and `int` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user