Compare commits
25 Commits
0.14.0
...
cjm/subscr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb11171a9b | ||
|
|
66885e4bce | ||
|
|
8248193ed9 | ||
|
|
b086ffe921 | ||
|
|
537ec5f012 | ||
|
|
db91ac7dce | ||
|
|
75f3c0e8e6 | ||
|
|
f0d0b57900 | ||
|
|
b0c6217e0b | ||
|
|
f054b8a55e | ||
|
|
b9c84add07 | ||
|
|
150ea92d03 | ||
|
|
697998f836 | ||
|
|
3771f1567c | ||
|
|
6b94e620fe | ||
|
|
db80febb6b | ||
|
|
f95eb90951 | ||
|
|
1f1542db51 | ||
|
|
abbbe8f3af | ||
|
|
5d3a35e071 | ||
|
|
ff386b4797 | ||
|
|
1bf4969c96 | ||
|
|
2be73e9afb | ||
|
|
7a347c4370 | ||
|
|
70b23a4fd0 |
24
.github/workflows/ci.yaml
vendored
24
.github/workflows/ci.yaml
vendored
@@ -707,6 +707,24 @@ jobs:
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
ty-completion-evaluation:
|
||||
name: "ty completion evaluation"
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Run ty completion evaluation"
|
||||
run: cargo run --release --package ty_completion_eval -- all --threshold 0.1 --tasks /tmp/completion-evaluation-tasks.csv
|
||||
- name: "Ensure there are no changes"
|
||||
run: diff ./crates/ty_completion_eval/completion-evaluation-tasks.csv /tmp/completion-evaluation-tasks.csv
|
||||
|
||||
python-package:
|
||||
name: "python package"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -749,7 +767,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22
|
||||
- name: "Cache pre-commit"
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
@@ -911,7 +929,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
github.ref == 'refs/heads/main' ||
|
||||
(needs.determine_changes.outputs.formatter == 'true' || needs.determine_changes.outputs.linter == 'true')
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
@@ -946,7 +964,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,8 @@ exclude: |
|
||||
crates/ruff_python_formatter/resources/.*|
|
||||
crates/ruff_python_formatter/tests/snapshots/.*|
|
||||
crates/ruff_python_resolver/resources/.*|
|
||||
crates/ruff_python_resolver/tests/snapshots/.*
|
||||
crates/ruff_python_resolver/tests/snapshots/.*|
|
||||
crates/ty_completion_eval/truth/.*
|
||||
)$
|
||||
|
||||
repos:
|
||||
|
||||
@@ -321,10 +321,16 @@ them to [PyPI](https://pypi.org/project/ruff/).
|
||||
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
|
||||
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
|
||||
|
||||
### Creating a new release
|
||||
### Installing tools
|
||||
|
||||
1. Install `uv`: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||
|
||||
1. Install `npm`: `brew install npm` or similar
|
||||
|
||||
### Creating a new release
|
||||
|
||||
Commit each step of this process separately for easier review.
|
||||
|
||||
1. Run `./scripts/release.sh`; this command will:
|
||||
|
||||
- Generate a temporary virtual environment with `rooster`
|
||||
@@ -337,6 +343,7 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
|
||||
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||
- Square brackets (eg, `[ruff]` project name) will be automatically escaped by `pre-commit`
|
||||
|
||||
Additionally, for minor releases:
|
||||
|
||||
@@ -376,13 +383,13 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
|
||||
1. Verify the GitHub release:
|
||||
|
||||
1. The Changelog should match the content of `CHANGELOG.md`
|
||||
1. Append the contributors from the `scripts/release.sh` script
|
||||
1. The changelog should match the content of `CHANGELOG.md`
|
||||
|
||||
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
||||
|
||||
1. One can determine if an update is needed when
|
||||
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.
|
||||
1. Run `uv run --only-dev --no-sync scripts/update_schemastore.py --proto <https|ssh>`
|
||||
1. Once run successfully, you should follow the link in the output to create a PR.
|
||||
|
||||
1. If needed, update the [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp) and
|
||||
|
||||
42
Cargo.lock
generated
42
Cargo.lock
generated
@@ -818,6 +818,27 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.5.0"
|
||||
@@ -4228,6 +4249,26 @@ dependencies = [
|
||||
"ty_python_semantic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_completion_eval"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bstr",
|
||||
"clap",
|
||||
"csv",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_text_size",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"ty_ide",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_ide"
|
||||
version = "0.0.0"
|
||||
@@ -4402,6 +4443,7 @@ dependencies = [
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
"memchr",
|
||||
"path-slash",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
|
||||
@@ -43,6 +43,7 @@ ruff_workspace = { path = "crates/ruff_workspace" }
|
||||
|
||||
ty = { path = "crates/ty" }
|
||||
ty_combine = { path = "crates/ty_combine" }
|
||||
ty_completion_eval = { path = "crates/ty_completion_eval" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
@@ -69,6 +70,7 @@ camino = { version = "1.1.7" }
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.6.0" }
|
||||
clearscreen = { version = "4.0.0" }
|
||||
csv = { version = "1.3.1" }
|
||||
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
|
||||
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
|
||||
colored = { version = "3.0.0" }
|
||||
@@ -203,7 +205,7 @@ wild = { version = "2" }
|
||||
zip = { version = "0.6.6", default-features = false }
|
||||
|
||||
[workspace.metadata.cargo-shear]
|
||||
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2"]
|
||||
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2", "ty_completion_eval"]
|
||||
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
@@ -232,7 +232,7 @@ static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLo
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
600,
|
||||
630,
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -227,3 +227,32 @@ async def read_thing(query: str):
|
||||
@app.get("/things/{ thing_id : str }")
|
||||
async def read_thing(query: str):
|
||||
return {"query": query}
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20680
|
||||
# These should NOT trigger FAST003 because FastAPI doesn't recognize them as path parameters
|
||||
|
||||
# Non-ASCII characters in parameter name
|
||||
@app.get("/f1/{用户身份}")
|
||||
async def f1():
|
||||
return locals()
|
||||
|
||||
# Space in parameter name
|
||||
@app.get("/f2/{x: str}")
|
||||
async def f2():
|
||||
return locals()
|
||||
|
||||
# Non-ASCII converter
|
||||
@app.get("/f3/{complex_number:ℂ}")
|
||||
async def f3():
|
||||
return locals()
|
||||
|
||||
# Mixed non-ASCII characters
|
||||
@app.get("/f4/{用户_id}")
|
||||
async def f4():
|
||||
return locals()
|
||||
|
||||
# Space in parameter name with converter
|
||||
@app.get("/f5/{param: int}")
|
||||
async def f5():
|
||||
return locals()
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_implicit_concat.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_implicit_concat.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import logging
|
||||
|
||||
variablename = "value"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.info(f"a" f"b {variablename}")
|
||||
log.info("a " f"b {variablename}")
|
||||
log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
@@ -56,3 +56,11 @@ f"{str(object=3)}"
|
||||
f"{str(x for x in [])}"
|
||||
|
||||
f"{str((x for x in []))}"
|
||||
|
||||
# Debug text cases - should not trigger RUF010
|
||||
f"{str(1)=}"
|
||||
f"{ascii(1)=}"
|
||||
f"{repr(1)=}"
|
||||
f"{str('hello')=}"
|
||||
f"{ascii('hello')=}"
|
||||
f"{repr('hello')=}"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::iter::Peekable;
|
||||
use std::ops::Range;
|
||||
use std::str::CharIndices;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use regex::{CaptureMatches, Regex};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprSubscript, Parameter, ParameterWithDefault};
|
||||
use ruff_python_semantic::{BindingKind, Modules, ScopeKind, SemanticModel};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::Fix;
|
||||
@@ -165,11 +165,6 @@ pub(crate) fn fastapi_unused_path_parameter(
|
||||
|
||||
// Check if any of the path parameters are not in the function signature.
|
||||
for (path_param, range) in path_params {
|
||||
// Ignore invalid identifiers (e.g., `user-id`, as opposed to `user_id`)
|
||||
if !is_identifier(path_param) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the path parameter is already in the function or the dependency signature,
|
||||
// we don't need to do anything.
|
||||
if named_args.contains(&path_param) {
|
||||
@@ -461,15 +456,19 @@ fn parameter_alias<'a>(parameter: &'a Parameter, semantic: &SemanticModel) -> Op
|
||||
/// the parameter name. For example, `/{x}` is a valid parameter, but `/{ x }` is treated literally.
|
||||
#[derive(Debug)]
|
||||
struct PathParamIterator<'a> {
|
||||
input: &'a str,
|
||||
chars: Peekable<CharIndices<'a>>,
|
||||
inner: CaptureMatches<'a, 'a>,
|
||||
}
|
||||
|
||||
impl<'a> PathParamIterator<'a> {
|
||||
fn new(input: &'a str) -> Self {
|
||||
PathParamIterator {
|
||||
input,
|
||||
chars: input.char_indices().peekable(),
|
||||
/// Matches the Starlette pattern for path parameters with optional converters from
|
||||
/// <https://github.com/Kludex/starlette/blob/e18637c68e36d112b1983bc0c8b663681e6a4c50/starlette/routing.py#L121>
|
||||
static FASTAPI_PATH_PARAM_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::[a-zA-Z_][a-zA-Z0-9_]*)?\}").unwrap()
|
||||
});
|
||||
|
||||
Self {
|
||||
inner: FASTAPI_PATH_PARAM_REGEX.captures_iter(input),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,19 +477,10 @@ impl<'a> Iterator for PathParamIterator<'a> {
|
||||
type Item = (&'a str, Range<usize>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some((start, c)) = self.chars.next() {
|
||||
if c == '{' {
|
||||
if let Some((end, _)) = self.chars.by_ref().find(|&(_, ch)| ch == '}') {
|
||||
let param_content = &self.input[start + 1..end];
|
||||
// We ignore text after a colon, since those are path converters
|
||||
// See also: https://fastapi.tiangolo.com/tutorial/path-params/?h=path#path-convertor
|
||||
let param_name_end = param_content.find(':').unwrap_or(param_content.len());
|
||||
let param_name = ¶m_content[..param_name_end];
|
||||
|
||||
return Some((param_name, start..end + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
self.inner
|
||||
.next()
|
||||
// Extract the first capture group (the path parameter), but return the range of the
|
||||
// whole match (everything in braces and including the braces themselves).
|
||||
.and_then(|capture| Some((capture.get(1)?.as_str(), capture.get(0)?.range())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,10 @@ use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe for `map` calls that contain
|
||||
/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead
|
||||
/// to a duplicate keyword argument error.
|
||||
/// This rule's fix is marked as unsafe. While adding `strict=False` preserves
|
||||
/// the runtime behavior, it can obscure situations where the iterables are of
|
||||
/// unequal length. Ruff prefers to alert users so they can choose the intended
|
||||
/// behavior themselves.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `map`](https://docs.python.org/3/library/functions.html#map)
|
||||
@@ -73,17 +74,7 @@ pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
// If the function call contains `**kwargs`, mark the fix as unsafe.
|
||||
if call
|
||||
.arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
Applicability::Unsafe,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe for `zip` calls that contain
|
||||
/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead
|
||||
/// to a duplicate keyword argument error.
|
||||
/// This rule's fix is marked as unsafe. While adding `strict=False` preserves
|
||||
/// the runtime behavior, it can obscure situations where the iterables are of
|
||||
/// unequal length. Ruff prefers to alert users so they can choose the intended
|
||||
/// behavior themselves.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `zip`](https://docs.python.org/3/library/functions.html#zip)
|
||||
@@ -68,17 +69,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
// If the function call contains `**kwargs`, mark the fix as unsafe.
|
||||
if call
|
||||
.arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
Applicability::Unsafe,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
assertion_line: 156
|
||||
---
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:4:1
|
||||
@@ -19,6 +20,7 @@ help: Add explicit value for parameter `strict=`
|
||||
5 | zip(range(3))
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:5:1
|
||||
@@ -39,6 +41,7 @@ help: Add explicit value for parameter `strict=`
|
||||
6 | zip("a", "b")
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:6:1
|
||||
@@ -59,6 +62,7 @@ help: Add explicit value for parameter `strict=`
|
||||
7 | zip("a", "b", *zip("c"))
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:7:1
|
||||
@@ -79,6 +83,7 @@ help: Add explicit value for parameter `strict=`
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
10 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:7:16
|
||||
@@ -99,6 +104,7 @@ help: Add explicit value for parameter `strict=`
|
||||
8 | zip(zip("a"), strict=False)
|
||||
9 | zip(zip("a", strict=True))
|
||||
10 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:8:5
|
||||
@@ -118,6 +124,7 @@ help: Add explicit value for parameter `strict=`
|
||||
9 | zip(zip("a", strict=True))
|
||||
10 |
|
||||
11 | # OK
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:9:1
|
||||
@@ -138,6 +145,7 @@ help: Add explicit value for parameter `strict=`
|
||||
10 |
|
||||
11 | # OK
|
||||
12 | zip(range(3), strict=True)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:24:1
|
||||
@@ -156,6 +164,7 @@ help: Add explicit value for parameter `strict=`
|
||||
25 | zip([1, 2, 3], repeat(1, times=4))
|
||||
26 |
|
||||
27 | import builtins
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:25:1
|
||||
@@ -176,6 +185,7 @@ help: Add explicit value for parameter `strict=`
|
||||
26 |
|
||||
27 | import builtins
|
||||
28 | # Still an error even though it uses the qualified name
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B905 [*] `zip()` without an explicit `strict=` parameter
|
||||
--> B905.py:29:1
|
||||
@@ -191,3 +201,4 @@ help: Add explicit value for parameter `strict=`
|
||||
28 | # Still an error even though it uses the qualified name
|
||||
- builtins.zip([1, 2, 3])
|
||||
29 + builtins.zip([1, 2, 3], strict=False)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
assertion_line: 112
|
||||
---
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:5:1
|
||||
@@ -20,6 +21,7 @@ help: Add explicit value for parameter `strict=`
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:6:1
|
||||
@@ -40,6 +42,7 @@ help: Add explicit value for parameter `strict=`
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:7:1
|
||||
@@ -61,6 +64,7 @@ help: Add explicit value for parameter `strict=`
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:9:1
|
||||
@@ -81,6 +85,7 @@ help: Add explicit value for parameter `strict=`
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:12:1
|
||||
@@ -99,6 +104,7 @@ help: Add explicit value for parameter `strict=`
|
||||
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
14 |
|
||||
15 | import builtins
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:13:1
|
||||
@@ -119,6 +125,7 @@ help: Add explicit value for parameter `strict=`
|
||||
14 |
|
||||
15 | import builtins
|
||||
16 | # Still an error even though it uses the qualified name
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:17:1
|
||||
@@ -139,3 +146,4 @@ help: Add explicit value for parameter `strict=`
|
||||
18 |
|
||||
19 | # OK
|
||||
20 | map(lambda x: x, [1, 2, 3], strict=True)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -23,6 +23,7 @@ mod tests {
|
||||
#[test_case(Path::new("G003.py"))]
|
||||
#[test_case(Path::new("G004.py"))]
|
||||
#[test_case(Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Path::new("G004_implicit_concat.py"))]
|
||||
#[test_case(Path::new("G010.py"))]
|
||||
#[test_case(Path::new("G101_1.py"))]
|
||||
#[test_case(Path::new("G101_2.py"))]
|
||||
@@ -52,6 +53,7 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_implicit_concat.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -42,38 +42,52 @@ fn logging_f_string(
|
||||
// Default to double quotes if we can't determine it.
|
||||
let quote_str = f_string
|
||||
.value
|
||||
.f_strings()
|
||||
.iter()
|
||||
.map(|part| match part {
|
||||
ast::FStringPart::Literal(literal) => literal.flags.quote_str(),
|
||||
ast::FStringPart::FString(f) => f.flags.quote_str(),
|
||||
})
|
||||
.next()
|
||||
.map(|f| f.flags.quote_str())
|
||||
.unwrap_or("\"");
|
||||
|
||||
for f in f_string.value.f_strings() {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
for part in &f_string.value {
|
||||
match part {
|
||||
ast::FStringPart::Literal(literal) => {
|
||||
let literal_text = literal.as_str();
|
||||
if literal_text.contains('%') {
|
||||
return;
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
format_string.push_str(literal_text);
|
||||
}
|
||||
ast::FStringPart::FString(f) => {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
assertion_line: 50
|
||||
---
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:6:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:7:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:8:10
|
||||
|
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
assertion_line: 71
|
||||
---
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:6:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
3 | variablename = "value"
|
||||
4 |
|
||||
5 | log = logging.getLogger(__name__)
|
||||
- log.info(f"a" f"b {variablename}")
|
||||
6 + log.info("ab %s", variablename)
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:7:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
4 |
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
- log.info("a " f"b {variablename}")
|
||||
7 + log.info("a b %s", variablename)
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:8:10
|
||||
|
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
- log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
8 + log.info("prefix middle %s suffix", variablename)
|
||||
@@ -43,6 +43,7 @@ where
|
||||
T: Ranged,
|
||||
{
|
||||
let mut diagnostic = checker.report_diagnostic(DeprecatedCElementTree, node.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
let contents = checker.locator().slice(node);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
contents.replacen("cElementTree", "ElementTree", 1),
|
||||
|
||||
@@ -265,6 +265,7 @@ pub(crate) fn deprecated_mock_attribute(checker: &Checker, attribute: &ast::Expr
|
||||
},
|
||||
attribute.value.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
"mock".to_string(),
|
||||
attribute.value.range(),
|
||||
@@ -313,6 +314,7 @@ pub(crate) fn deprecated_mock_import(checker: &Checker, stmt: &Stmt) {
|
||||
},
|
||||
name.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
if let Some(content) = content.as_ref() {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
content.clone(),
|
||||
@@ -351,6 +353,7 @@ pub(crate) fn deprecated_mock_import(checker: &Checker, stmt: &Stmt) {
|
||||
},
|
||||
stmt.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
if let Some(indent) = indentation(checker.source(), stmt) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
format_import_from(stmt, indent, checker.locator(), checker.stylist())
|
||||
|
||||
@@ -98,6 +98,7 @@ pub(crate) fn deprecated_unittest_alias(checker: &Checker, expr: &Expr) {
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("self.{target}"),
|
||||
expr.range(),
|
||||
|
||||
@@ -68,6 +68,7 @@ pub(crate) fn replace_universal_newlines(checker: &Checker, call: &ast::ExprCall
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(ReplaceUniversalNewlines, arg.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
|
||||
if call.arguments.find_keyword("text").is_some() {
|
||||
diagnostic.try_set_fix(|| {
|
||||
|
||||
@@ -57,6 +57,7 @@ pub(crate) fn typing_text_str_alias(checker: &Checker, expr: &Expr) {
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["typing", "Text"]))
|
||||
{
|
||||
let mut diagnostic = checker.report_diagnostic(TypingTextStrAlias, expr.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol(
|
||||
"str",
|
||||
|
||||
@@ -110,10 +110,7 @@ pub(crate) fn explicit_f_string_type_conversion(checker: &Checker, f_string: &as
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(ExplicitFStringTypeConversion, expression.range());
|
||||
|
||||
// Don't support fixing f-string with debug text.
|
||||
// Don't report diagnostic for f-string with debug text.
|
||||
if element
|
||||
.as_interpolation()
|
||||
.is_some_and(|interpolation| interpolation.debug_text.is_some())
|
||||
@@ -121,6 +118,9 @@ pub(crate) fn explicit_f_string_type_conversion(checker: &Checker, f_string: &as
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(ExplicitFStringTypeConversion, expression.range());
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
convert_call_to_conversion_flag(checker, conversion, f_string, index, arg)
|
||||
});
|
||||
|
||||
@@ -293,18 +293,6 @@ help: Replace with conversion flag
|
||||
48 | f"{repr(1)=}"
|
||||
49 |
|
||||
|
||||
RUF010 Use explicit conversion flag
|
||||
--> RUF010.py:48:4
|
||||
|
|
||||
46 | f"{builtins.repr(1)}"
|
||||
47 |
|
||||
48 | f"{repr(1)=}"
|
||||
| ^^^^^^^
|
||||
49 |
|
||||
50 | f"{repr(lambda: 1)}"
|
||||
|
|
||||
help: Replace with conversion flag
|
||||
|
||||
RUF010 [*] Use explicit conversion flag
|
||||
--> RUF010.py:50:4
|
||||
|
|
||||
@@ -383,6 +371,7 @@ help: Replace with conversion flag
|
||||
56 + f"{(x for x in [])!s}"
|
||||
57 |
|
||||
58 | f"{str((x for x in []))}"
|
||||
59 |
|
||||
|
||||
RUF010 [*] Use explicit conversion flag
|
||||
--> RUF010.py:58:4
|
||||
@@ -391,6 +380,8 @@ RUF010 [*] Use explicit conversion flag
|
||||
57 |
|
||||
58 | f"{str((x for x in []))}"
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
59 |
|
||||
60 | # Debug text cases - should not trigger RUF010
|
||||
|
|
||||
help: Replace with conversion flag
|
||||
55 |
|
||||
@@ -398,3 +389,6 @@ help: Replace with conversion flag
|
||||
57 |
|
||||
- f"{str((x for x in []))}"
|
||||
58 + f"{(x for x in [])!s}"
|
||||
59 |
|
||||
60 | # Debug text cases - should not trigger RUF010
|
||||
61 | f"{str(1)=}"
|
||||
|
||||
@@ -67,8 +67,8 @@ impl PythonVersion {
|
||||
}
|
||||
|
||||
pub const fn latest_ty() -> Self {
|
||||
// Make sure to update the default value for `EnvironmentOptions::python_version` when bumping this version.
|
||||
Self::PY313
|
||||
// Make sure to update the default value for `EnvironmentOptions::python_version` when bumping this version.
|
||||
Self::PY314
|
||||
}
|
||||
|
||||
pub const fn as_tuple(self) -> (u8, u8) {
|
||||
|
||||
2
crates/ty/docs/cli.md
generated
2
crates/ty/docs/cli.md
generated
@@ -76,7 +76,7 @@ over all configuration files.</p>
|
||||
<p>This is used to specialize the type of <code>sys.platform</code> and will affect the visibility of platform-specific functions and attributes. If the value is set to <code>all</code>, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.</p>
|
||||
</dd><dt id="ty-check--python-version"><a href="#ty-check--python-version"><code>--python-version</code></a>, <code>--target-version</code> <i>version</i></dt><dd><p>Python version to assume when resolving types.</p>
|
||||
<p>The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.</p>
|
||||
<p>If a version is not specified on the command line or in a configuration file, ty will try the following techniques in order of preference to determine a value: 1. Check for the <code>project.requires-python</code> setting in a <code>pyproject.toml</code> file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the latest stable Python version supported by ty (currently Python 3.13)</p>
|
||||
<p>If a version is not specified on the command line or in a configuration file, ty will try the following techniques in order of preference to determine a value: 1. Check for the <code>project.requires-python</code> setting in a <code>pyproject.toml</code> file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the latest stable Python version supported by ty (see <code>ty check --help</code> output)</p>
|
||||
<p>Possible values:</p>
|
||||
<ul>
|
||||
<li><code>3.7</code></li>
|
||||
|
||||
4
crates/ty/docs/configuration.md
generated
4
crates/ty/docs/configuration.md
generated
@@ -133,9 +133,9 @@ For some language features, ty can also understand conditionals based on compari
|
||||
with `sys.version_info`. These are commonly found in typeshed, for example,
|
||||
to reflect the differing contents of the standard library across Python versions.
|
||||
|
||||
**Default value**: `"3.13"`
|
||||
**Default value**: `"3.14"`
|
||||
|
||||
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | <major>.<minor>`
|
||||
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
|
||||
|
||||
191
crates/ty/docs/rules.md
generated
191
crates/ty/docs/rules.md
generated
@@ -36,7 +36,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L114)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L115)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L158)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L159)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -88,7 +88,7 @@ f(int) # error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L184)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L185)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -117,7 +117,7 @@ a = 1
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L209)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L210)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -147,7 +147,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L235)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -177,7 +177,7 @@ class B(A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L300)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L301)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -202,7 +202,7 @@ class B(A, A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L321)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L322)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L524)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L525)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -334,7 +334,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L548)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L549)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L353)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L354)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -445,7 +445,7 @@ an atypical memory layout.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L593)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L594)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L633)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L634)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -496,7 +496,7 @@ a: int = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1688)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1745)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -562,7 +562,7 @@ asyncio.run(main())
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L685)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L686)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -584,7 +584,7 @@ class A(42): ... # error: [invalid-base]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L736)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L737)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -609,7 +609,7 @@ with 1:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L757)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L758)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -636,7 +636,7 @@ a: str
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L780)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L781)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -678,7 +678,7 @@ except ZeroDivisionError:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L816)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L817)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L568)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L569)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L842)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L843)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -771,7 +771,7 @@ def f(t: TypeVar("U")): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L891)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L892)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -803,7 +803,7 @@ class B(metaclass=f): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L498)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L499)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -833,7 +833,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L918)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L919)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -881,7 +881,7 @@ def foo(x: int) -> int: ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L961)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1018)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -905,7 +905,7 @@ def f(a: int = ''): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L435)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L436)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -937,7 +937,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L981)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1038)
|
||||
</small>
|
||||
|
||||
Checks for `raise` statements that raise non-exceptions or use invalid
|
||||
@@ -984,7 +984,7 @@ def g():
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L614)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L615)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1007,7 +1007,7 @@ def func() -> int:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1024)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1081)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1061,7 +1061,7 @@ TODO #14889
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L870)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1086,7 +1086,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1063)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1120)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1114,7 +1114,7 @@ TYPE_CHECKING = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1087)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1144)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1142,7 +1142,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1139)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1196)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1174,7 +1174,7 @@ f(10) # Error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1111)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1168)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1206,7 +1206,7 @@ class C:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1167)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1224)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1239,7 +1239,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1196)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1253)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1262,7 +1262,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1787)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1844)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1293,7 +1293,7 @@ alice["age"] # KeyError
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1215)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1320,7 +1320,7 @@ func("string") # error: [no-matching-overload]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1238)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1295)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1342,7 +1342,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1256)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1366,7 +1366,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1307)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1364)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1391,7 +1391,7 @@ f(1, x=2) # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1542)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1599)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1445,7 +1445,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1664)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1721)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1473,7 +1473,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1398)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1455)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1500,7 +1500,7 @@ class B(A): ... # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1443)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1500)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1525,7 +1525,7 @@ f("foo") # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1421)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1478)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1551,7 +1551,7 @@ def _(x: int):
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1464)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1521)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1595,7 +1595,7 @@ class A:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1521)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1578)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1620,7 +1620,7 @@ f(x=1, y=2) # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1563)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1620)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1646,7 +1646,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1585)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1642)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1669,7 +1669,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1604)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1661)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1692,7 +1692,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1276)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1333)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1727,7 +1727,7 @@ b1 < b2 < b1 # exception raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1623)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1680)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1753,7 +1753,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[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#L1645)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1702)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1776,7 +1776,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L463)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L464)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1815,7 +1815,7 @@ class SubProto(BaseProto, Protocol):
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L280)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1868,7 +1868,7 @@ a = 20 / 0 # type: ignore
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1328)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1385)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1894,7 +1894,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L132)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L133)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1924,7 +1924,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1350)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1407)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1954,7 +1954,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[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#L1716)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1773)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1979,7 +1979,7 @@ cast(int, f()) # Redundant
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[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#L1503)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1560)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2030,7 +2030,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1737)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1794)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2084,7 +2084,7 @@ def g():
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2116,12 +2116,73 @@ class D(C): ... # error: [unsupported-base]
|
||||
|
||||
[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
|
||||
## `useless-overload-body`
|
||||
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L962)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for various `@overload`-decorated functions that have non-stub bodies.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Functions decorated with `@overload` are ignored at runtime; they are overridden
|
||||
by the implementation function that follows the series of overloads. While it is
|
||||
not illegal to provide a body for an `@overload`-decorated function, it may indicate
|
||||
a misunderstanding of how the `@overload` decorator works.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def foo(x: int) -> int:
|
||||
return x + 1 # will never be executed
|
||||
|
||||
@overload
|
||||
def foo(x: str) -> str:
|
||||
return "Oh no, got a string" # will never be executed
|
||||
|
||||
def foo(x: int | str) -> int | str:
|
||||
raise Exception("unexpected type encountered")
|
||||
```
|
||||
|
||||
Use instead:
|
||||
|
||||
```py
|
||||
from typing import assert_never, overload
|
||||
|
||||
@overload
|
||||
def foo(x: int) -> int: ...
|
||||
|
||||
@overload
|
||||
def foo(x: str) -> str: ...
|
||||
|
||||
def foo(x: int | str) -> int | str:
|
||||
if isinstance(x, int):
|
||||
return x + 1
|
||||
elif isinstance(x, str):
|
||||
return "Oh no, got a string"
|
||||
else:
|
||||
assert_never(x)
|
||||
```
|
||||
|
||||
**References**
|
||||
|
||||
- [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload)
|
||||
|
||||
## `division-by-zero`
|
||||
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[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#L261)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L262)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2143,7 +2204,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[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#L1376)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
@@ -85,7 +85,7 @@ pub(crate) struct CheckCommand {
|
||||
/// and use the minimum version from the specified range
|
||||
/// 2. Check for an activated or configured Python environment
|
||||
/// and attempt to infer the Python version of that environment
|
||||
/// 3. Fall back to the latest stable Python version supported by ty (currently Python 3.13)
|
||||
/// 3. Fall back to the latest stable Python version supported by ty (see `ty check --help` output)
|
||||
#[arg(long, value_name = "VERSION", alias = "target-version")]
|
||||
pub(crate) python_version: Option<PythonVersion>,
|
||||
|
||||
|
||||
32
crates/ty_completion_eval/Cargo.toml
Normal file
32
crates/ty_completion_eval/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "ty_completion_eval"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true, features = ["os"] }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
ty_ide = { workspace = true }
|
||||
ty_project = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
bstr = { workspace = true }
|
||||
clap = { workspace = true, features = ["wrap_help", "string", "env"] }
|
||||
csv = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
143
crates/ty_completion_eval/README.md
Normal file
143
crates/ty_completion_eval/README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
This directory contains a framework for evaluating completion suggestions
|
||||
returned by the ty LSP.
|
||||
|
||||
# Running an evaluation
|
||||
|
||||
To run a full evaluation, run the `ty_completion_eval` crate with the
|
||||
`all` command from the root of this repository:
|
||||
|
||||
```console
|
||||
cargo run --release --package ty_completion_eval -- all
|
||||
```
|
||||
|
||||
The output should look like this:
|
||||
|
||||
```text
|
||||
Finished `release` profile [optimized] target(s) in 0.09s
|
||||
Running `target/release/ty_completion_eval all`
|
||||
mean reciprocal rank: 0.20409790112917506
|
||||
MRR exceeds threshold of 0.001
|
||||
```
|
||||
|
||||
If you want to look at the results of each individual evaluation task,
|
||||
you can ask the evaluation to write CSV data that contains the rank of
|
||||
the expected answer in each completion request:
|
||||
|
||||
```console
|
||||
cargo r -r -p ty_completion_eval -- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
||||
```
|
||||
|
||||
To debug a _specific_ task and look at the actual results, use the `show-one`
|
||||
command:
|
||||
|
||||
```console
|
||||
cargo r -q -p ty_completion_eval show-one higher-level-symbols-preferred --index 1
|
||||
```
|
||||
|
||||
(The `--index` flag is only needed if there are multiple `<CURSOR>` directives in the same file.)
|
||||
|
||||
Has output that should look like this:
|
||||
|
||||
```text
|
||||
ZQZQZQ_SOMETHING_IMPORTANT (*, 1/31)
|
||||
__annotations__
|
||||
__class__
|
||||
__delattr__
|
||||
__dict__
|
||||
__dir__
|
||||
__doc__
|
||||
__eq__
|
||||
__file__
|
||||
__format__
|
||||
__getattr__
|
||||
__getattribute__
|
||||
__getstate__
|
||||
__hash__
|
||||
__init__
|
||||
__init_subclass__
|
||||
__loader__
|
||||
__module__
|
||||
__name__
|
||||
__ne__
|
||||
__new__
|
||||
__package__
|
||||
__path__
|
||||
__reduce__
|
||||
__reduce_ex__
|
||||
__repr__
|
||||
__setattr__
|
||||
__sizeof__
|
||||
__spec__
|
||||
__str__
|
||||
__subclasshook__
|
||||
-----
|
||||
found 31 completions
|
||||
```
|
||||
|
||||
The expected answer is marked with a `*`. The higher the rank, the better. In this example, the
|
||||
rank is perfect. Note that the expected answer may not always appear in the completion results!
|
||||
(Which is considered the worst possible outcome by this evaluation framework.)
|
||||
|
||||
# Evaluation model
|
||||
|
||||
This evaluation is based on [mean reciprocal rank] (MRR). That is, it assumes
|
||||
that for every evaluation task (i.e., a single completion request) there is
|
||||
precisely one correct answer. The higher the correct answer appears in each
|
||||
completion request, the better. The mean reciprocal rank is computed as the
|
||||
average of `1/rank` across all evaluation tasks. The higher the mean reciprocal
|
||||
rank, the better.
|
||||
|
||||
The evaluation starts by preparing its truth data, which is contained in the `./truth` directory.
|
||||
Within `./truth` is a list of Python projects. Every project contains one or more `<CURSOR>`
|
||||
directives. Each `<CURSOR>` directive corresponds to an instruction to initiate a completion
|
||||
request at that position. For example:
|
||||
|
||||
```python
|
||||
class Foo:
|
||||
def frobnicate(self): pass
|
||||
|
||||
foo = Foo()
|
||||
foo.frob<CURSOR: frobnicate>
|
||||
```
|
||||
|
||||
The above example says that completions should be requested immediately after `foo.frob`
|
||||
_and_ that the expected answer is `frobnicate`.
|
||||
|
||||
When testing auto-import, one should also include the module in the expected answer.
|
||||
For example:
|
||||
|
||||
```python
|
||||
RegexFl<CURSOR: re.RegexFlag>
|
||||
```
|
||||
|
||||
Settings for completion requests can be configured via a `completion.toml` file within
|
||||
each Python project directory.
|
||||
|
||||
When an evaluation is run, the truth data is copied to a temporary directory.
|
||||
`uv sync` is then run within each directory to prepare it.
|
||||
|
||||
# Continuous Integration
|
||||
|
||||
At time of writing (2025-10-07), an evaluation is run in CI. CI will fail if the MRR is
|
||||
below a set threshold. When this occurs, it means that the evaluation's results have likely
|
||||
gotten worse in some measurable way. Ideally, the way to fix this would be to fix whatever
|
||||
regression occurred in ranking. One can follow the steps above to run an evaluation and
|
||||
emit the individual task results in CSV format. This difference between this CSV data and
|
||||
whatever is committed at `./crates/ty_completion_eval/completion-evaluation-tasks.csv` should
|
||||
point to where the regression occurs.
|
||||
|
||||
If the change is not a regression or is otherwise expected, then the MRR threshold can be
|
||||
lowered. This requires changing how `ty_completion_eval` is executed within CI.
|
||||
|
||||
CI will also fail if the individual task results have changed.
|
||||
To make CI pass, you can just re-run the evaluation locally and commit the results:
|
||||
|
||||
```console
|
||||
cargo r -r -p ty_completion_eval -- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
||||
```
|
||||
|
||||
CI fails in this case because it would be best to scrutinize the differences here.
|
||||
It's possible that the ranking has improved in some measurable way, for example.
|
||||
(Think of this as if it were a snapshot test.)
|
||||
|
||||
[mean reciprocal rank]: https://en.wikipedia.org/wiki/Mean_reciprocal_rank
|
||||
17
crates/ty_completion_eval/completion-evaluation-tasks.csv
Normal file
17
crates/ty_completion_eval/completion-evaluation-tasks.csv
Normal file
@@ -0,0 +1,17 @@
|
||||
name,file,index,rank
|
||||
higher-level-symbols-preferred,main.py,0,
|
||||
higher-level-symbols-preferred,main.py,1,1
|
||||
import-deprioritizes-dunder,main.py,0,195
|
||||
import-deprioritizes-sunder,main.py,0,195
|
||||
internal-typeshed-hidden,main.py,0,43
|
||||
numpy-array,main.py,0,
|
||||
numpy-array,main.py,1,32
|
||||
object-attr-instance-methods,main.py,0,7
|
||||
object-attr-instance-methods,main.py,1,1
|
||||
raise-uses-base-exception,main.py,0,42
|
||||
scope-existing-over-new-import,main.py,0,495
|
||||
scope-prioritize-closer,main.py,0,152
|
||||
scope-simple-long-identifier,main.py,0,140
|
||||
ty-extensions-lower-stdlib,main.py,0,142
|
||||
type-var-typing-over-ast,main.py,0,65
|
||||
type-var-typing-over-ast,main.py,1,353
|
||||
|
618
crates/ty_completion_eval/src/main.rs
Normal file
618
crates/ty_completion_eval/src/main.rs
Normal file
@@ -0,0 +1,618 @@
|
||||
/*!
|
||||
A simple command line tool for running a completion evaluation.
|
||||
|
||||
See `crates/ty_completion_eval/README.md` for examples and more docs.
|
||||
*/
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::ExitCode;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap::Parser;
|
||||
use regex::bytes::Regex;
|
||||
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use ty_ide::Completion;
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_python_semantic::ModuleName;
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
#[command(
|
||||
author,
|
||||
name = "ty_completion_eval",
|
||||
about = "Run a information retrieval evaluation on ty-powered completions."
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
enum Command {
|
||||
/// Run an evaluation on all tasks.
|
||||
All(AllCommand),
|
||||
/// Show the completions for a single task.
|
||||
///
|
||||
/// This is useful for debugging one single completion task. For
|
||||
/// example, let's say you make a change to a ranking heuristic and
|
||||
/// everything looks good except for a few tasks where the rank for
|
||||
/// the expected answer regressed. Just use this command to run a
|
||||
/// specific task and you'll get the actual completions for that
|
||||
/// task printed to stdout.
|
||||
///
|
||||
/// If the expected answer is found in the completion list, then
|
||||
/// it is marked with an `*` along with its rank.
|
||||
ShowOne(ShowOneCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
struct AllCommand {
|
||||
/// The mean reciprocal rank threshold that the evaluation must
|
||||
/// meet or exceed in order for the evaluation to pass.
|
||||
#[arg(
|
||||
long,
|
||||
help = "The mean reciprocal rank threshold.",
|
||||
value_name = "FLOAT",
|
||||
default_value_t = 0.001
|
||||
)]
|
||||
threshold: f64,
|
||||
/// If given, a CSV file of the results for each individual task
|
||||
/// is written to the path given.
|
||||
#[arg(
|
||||
long,
|
||||
help = "When provided, write individual task results in CSV format.",
|
||||
value_name = "FILE"
|
||||
)]
|
||||
tasks: Option<String>,
|
||||
/// Whether to keep the temporary evaluation directory around
|
||||
/// after finishing or not. Keeping it around is useful for
|
||||
/// debugging when something has gone wrong.
|
||||
#[arg(
|
||||
long,
|
||||
help = "Whether to keep the temporary evaluation directory around or not."
|
||||
)]
|
||||
keep_tmp_dir: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
struct ShowOneCommand {
|
||||
/// The name of one or more completion tasks to run in isolation.
|
||||
///
|
||||
/// The name corresponds to the name of a directory in
|
||||
/// `./crates/ty_completion_eval/truth/`.
|
||||
#[arg(help = "The task name to run.", value_name = "TASK_NAME")]
|
||||
task_name: String,
|
||||
/// The name of the file, relative to the root of the
|
||||
/// Python project, that contains one or more completion
|
||||
/// tasks to run in isolation.
|
||||
#[arg(long, help = "The file name to run.", value_name = "FILE_NAME")]
|
||||
file_name: Option<String>,
|
||||
/// The index of the cursor directive within `file_name`
|
||||
/// to select.
|
||||
#[arg(
|
||||
long,
|
||||
help = "The index of the cursor directive to run.",
|
||||
value_name = "INDEX"
|
||||
)]
|
||||
index: Option<usize>,
|
||||
/// Whether to keep the temporary evaluation directory around
|
||||
/// after finishing or not. Keeping it around is useful for
|
||||
/// debugging when something has gone wrong.
|
||||
#[arg(
|
||||
long,
|
||||
help = "Whether to keep the temporary evaluation directory around or not."
|
||||
)]
|
||||
keep_tmp_dir: bool,
|
||||
}
|
||||
|
||||
impl ShowOneCommand {
|
||||
fn matches_source_task(&self, task_source: &TaskSource) -> bool {
|
||||
self.task_name == task_source.name
|
||||
}
|
||||
|
||||
fn matches_task(&self, task: &Task) -> bool {
|
||||
self.task_name == task.name
|
||||
&& self
|
||||
.file_name
|
||||
.as_ref()
|
||||
.is_some_and(|name| name == task.cursor_name())
|
||||
&& self.index.is_some_and(|index| index == task.cursor.index)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<ExitCode> {
|
||||
let args = Cli::parse();
|
||||
|
||||
// The base path to which all CLI arguments are relative to.
|
||||
let cwd = {
|
||||
let cwd = std::env::current_dir().context("Failed to get the current working directory")?;
|
||||
SystemPathBuf::from_path_buf(cwd).map_err(|path| {
|
||||
anyhow!(
|
||||
"The current working directory `{}` contains non-Unicode characters. \
|
||||
ty only supports Unicode paths.",
|
||||
path.display()
|
||||
)
|
||||
})?
|
||||
};
|
||||
// Where we store our truth data.
|
||||
let truth = cwd.join("crates").join("ty_completion_eval").join("truth");
|
||||
anyhow::ensure!(
|
||||
truth.as_std_path().exists(),
|
||||
"{truth} does not exist: ty's completion evaluation must be run from the root \
|
||||
of the ruff repository",
|
||||
truth = truth.as_std_path().display(),
|
||||
);
|
||||
|
||||
// The temporary directory at which we copy our truth
|
||||
// data to. We do this because we can't use the truth
|
||||
// data as-is with its `<CURSOR>` annotations (and perhaps
|
||||
// any other future annotations we add).
|
||||
let mut tmp_eval_dir = tempfile::Builder::new()
|
||||
.prefix("ty-completion-eval-")
|
||||
.tempdir()
|
||||
.context("Failed to create temporary directory")?;
|
||||
let tmp_eval_path = SystemPath::from_std_path(tmp_eval_dir.path())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Temporary directory path is not valid UTF-8: {}",
|
||||
tmp_eval_dir.path().display()
|
||||
)
|
||||
})?
|
||||
.to_path_buf();
|
||||
|
||||
let sources = TaskSource::all(&truth)?;
|
||||
match args.command {
|
||||
Command::ShowOne(ref cmd) => {
|
||||
tmp_eval_dir.disable_cleanup(cmd.keep_tmp_dir);
|
||||
|
||||
let Some(source) = sources
|
||||
.iter()
|
||||
.find(|source| cmd.matches_source_task(source))
|
||||
else {
|
||||
anyhow::bail!("could not find task named `{}`", cmd.task_name);
|
||||
};
|
||||
let tasks = source.to_tasks(&tmp_eval_path)?;
|
||||
let matching: Vec<&Task> = tasks.iter().filter(|task| cmd.matches_task(task)).collect();
|
||||
anyhow::ensure!(
|
||||
!matching.is_empty(),
|
||||
"could not find any tasks matching the given criteria",
|
||||
);
|
||||
anyhow::ensure!(
|
||||
matching.len() < 2,
|
||||
"found more than one task matching the given criteria",
|
||||
);
|
||||
let task = &matching[0];
|
||||
let completions = task.completions()?;
|
||||
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
for (i, c) in completions.iter().enumerate() {
|
||||
write!(stdout, "{}", c.name.as_str())?;
|
||||
if let Some(module_name) = c.module_name {
|
||||
write!(stdout, " (module: {module_name})")?;
|
||||
}
|
||||
if task.cursor.answer.matches(c) {
|
||||
write!(stdout, " (*, {}/{})", i + 1, completions.len())?;
|
||||
}
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
writeln!(stdout, "-----")?;
|
||||
writeln!(stdout, "found {} completions", completions.len())?;
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
Command::All(AllCommand {
|
||||
threshold,
|
||||
tasks,
|
||||
keep_tmp_dir,
|
||||
}) => {
|
||||
tmp_eval_dir.disable_cleanup(keep_tmp_dir);
|
||||
|
||||
let mut precision_sum = 0.0;
|
||||
let mut task_count = 0.0f64;
|
||||
let mut results_wtr = None;
|
||||
if let Some(ref tasks) = tasks {
|
||||
let mut wtr = csv::Writer::from_path(SystemPath::new(tasks))?;
|
||||
wtr.serialize(("name", "file", "index", "rank"))?;
|
||||
results_wtr = Some(wtr);
|
||||
}
|
||||
for source in &sources {
|
||||
for task in source.to_tasks(&tmp_eval_path)? {
|
||||
task_count += 1.0;
|
||||
|
||||
let completions = task.completions()?;
|
||||
let rank = task.rank(&completions)?;
|
||||
precision_sum += rank.map(|rank| 1.0 / f64::from(rank)).unwrap_or(0.0);
|
||||
if let Some(ref mut wtr) = results_wtr {
|
||||
wtr.serialize((&task.name, &task.cursor_name(), task.cursor.index, rank))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mrr = precision_sum / task_count;
|
||||
if let Some(ref mut wtr) = results_wtr {
|
||||
wtr.flush()?;
|
||||
}
|
||||
|
||||
let mut out = std::io::stdout().lock();
|
||||
writeln!(out, "mean reciprocal rank: {mrr:.4}")?;
|
||||
if mrr < threshold {
|
||||
writeln!(
|
||||
out,
|
||||
"Failure: MRR does not exceed minimum threshold of {threshold}"
|
||||
)?;
|
||||
Ok(ExitCode::FAILURE)
|
||||
} else {
|
||||
writeln!(out, "Success: MRR exceeds minimum threshold of {threshold}")?;
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single completion task.
|
||||
///
|
||||
/// The task is oriented in such a way that we have a single "cursor"
|
||||
/// position in a Python project. This allows us to ask for completions
|
||||
/// at that position.
|
||||
struct Task {
|
||||
db: ProjectDatabase,
|
||||
dir: SystemPathBuf,
|
||||
name: String,
|
||||
cursor: Cursor,
|
||||
settings: ty_ide::CompletionSettings,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
/// Create a new task for the Python project at `project_path`.
|
||||
///
|
||||
/// `truth` should correspond to the completion configuration and the
|
||||
/// expected answer for completions at the given `cursor` position.
|
||||
fn new(
|
||||
project_path: &SystemPath,
|
||||
truth: &CompletionTruth,
|
||||
cursor: Cursor,
|
||||
) -> anyhow::Result<Task> {
|
||||
let name = project_path.file_name().ok_or_else(|| {
|
||||
anyhow::anyhow!("project directory `{project_path}` does not contain a base name")
|
||||
})?;
|
||||
|
||||
let system = OsSystem::new(project_path);
|
||||
let mut project_metadata = ProjectMetadata::discover(project_path, &system)?;
|
||||
project_metadata.apply_configuration_files(&system)?;
|
||||
let db = ProjectDatabase::new(project_metadata, system)?;
|
||||
Ok(Task {
|
||||
db,
|
||||
dir: project_path.to_path_buf(),
|
||||
name: name.to_string(),
|
||||
cursor,
|
||||
settings: (&truth.settings).into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the rank of the expected answer in the completions
|
||||
/// given.
|
||||
///
|
||||
/// The rank is the position (one indexed) at which the expected
|
||||
/// answer appears in the slice given, or `None` if the answer
|
||||
/// isn't found at all. A position of zero is maximally correct. A
|
||||
/// missing position is maximally wrong. Anything in the middle is
|
||||
/// a grey area with a lower rank being better.
|
||||
///
|
||||
/// Because the rank is one indexed, if this returns a rank, then
|
||||
/// it is guaranteed to be non-zero.
|
||||
fn rank(&self, completions: &[Completion<'_>]) -> anyhow::Result<Option<u32>> {
|
||||
completions
|
||||
.iter()
|
||||
.position(|completion| self.cursor.answer.matches(completion))
|
||||
.map(|rank| u32::try_from(rank + 1).context("rank of completion is too big"))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Return completions for this task.
|
||||
fn completions(&self) -> anyhow::Result<Vec<Completion<'_>>> {
|
||||
let file = system_path_to_file(&self.db, &self.cursor.path)
|
||||
.with_context(|| format!("failed to get database file for `{}`", self.cursor.path))?;
|
||||
let offset = ruff_text_size::TextSize::try_from(self.cursor.offset).with_context(|| {
|
||||
format!(
|
||||
"failed to convert `<CURSOR>` file offset `{}` to 32-bit integer",
|
||||
self.cursor.offset
|
||||
)
|
||||
})?;
|
||||
let completions = ty_ide::completion(&self.db, &self.settings, file, offset);
|
||||
Ok(completions)
|
||||
}
|
||||
|
||||
/// Returns the file name, relative to this project's root
|
||||
/// directory, that contains the cursor directive that we
|
||||
/// are evaluating.
|
||||
fn cursor_name(&self) -> &str {
|
||||
self.cursor
|
||||
.path
|
||||
.strip_prefix(&self.dir)
|
||||
.expect("task directory is a parent of cursor")
|
||||
.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Task {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("Test")
|
||||
.field("db", &"<ProjectDatabase>")
|
||||
.field("dir", &self.dir)
|
||||
.field("name", &self.name)
|
||||
.field("cursor", &self.cursor)
|
||||
.field("settings", &self.settings)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Truth data for a single completion evaluation test.
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct CompletionTruth {
|
||||
#[serde(default)]
|
||||
settings: CompletionSettings,
|
||||
}
|
||||
|
||||
/// Settings to forward to our completion routine.
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct CompletionSettings {
|
||||
#[serde(default)]
|
||||
auto_import: bool,
|
||||
}
|
||||
|
||||
impl From<&CompletionSettings> for ty_ide::CompletionSettings {
|
||||
fn from(x: &CompletionSettings) -> ty_ide::CompletionSettings {
|
||||
ty_ide::CompletionSettings {
|
||||
auto_import: x.auto_import,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The "source" of a task, as found in ty's git repository.
|
||||
#[derive(Debug)]
|
||||
struct TaskSource {
|
||||
/// The directory containing this task.
|
||||
dir: SystemPathBuf,
|
||||
/// The name of this task (the basename of `dir`).
|
||||
name: String,
|
||||
/// The "truth" data for this task along with any
|
||||
/// settings. This is pulled from `{dir}/completion.toml`.
|
||||
truth: CompletionTruth,
|
||||
}
|
||||
|
||||
impl TaskSource {
|
||||
fn all(src_dir: &SystemPath) -> anyhow::Result<Vec<TaskSource>> {
|
||||
let mut sources = vec![];
|
||||
let read_dir = src_dir
|
||||
.as_std_path()
|
||||
.read_dir()
|
||||
.with_context(|| format!("failed to read directory entries in `{src_dir}`"))?;
|
||||
for result in read_dir {
|
||||
let dent = result
|
||||
.with_context(|| format!("failed to get directory entry from `{src_dir}`"))?;
|
||||
let path = dent.path();
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dir = SystemPath::from_std_path(&path).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"truth source directory `{path}` contains invalid UTF-8",
|
||||
path = path.display()
|
||||
)
|
||||
})?;
|
||||
sources.push(TaskSource::new(dir)?);
|
||||
}
|
||||
// Sort our sources so that we always run in the same order.
|
||||
// And also so that the CSV output is deterministic across
|
||||
// all platforms.
|
||||
sources.sort_by(|source1, source2| source1.name.cmp(&source2.name));
|
||||
Ok(sources)
|
||||
}
|
||||
|
||||
fn new(dir: &SystemPath) -> anyhow::Result<TaskSource> {
|
||||
let name = dir.file_name().ok_or_else(|| {
|
||||
anyhow::anyhow!("truth source directory `{dir}` does not contain a base name")
|
||||
})?;
|
||||
|
||||
let truth_path = dir.join("completion.toml");
|
||||
let truth_data = std::fs::read(truth_path.as_std_path())
|
||||
.with_context(|| format!("failed to read truth data at `{truth_path}`"))?;
|
||||
let truth = toml::from_slice(&truth_data).with_context(|| {
|
||||
format!("failed to parse TOML completion truth data from `{truth_path}`")
|
||||
})?;
|
||||
|
||||
Ok(TaskSource {
|
||||
dir: dir.to_path_buf(),
|
||||
name: name.to_string(),
|
||||
truth,
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert this "source" task (from the Ruff repository) into
|
||||
/// one or more evaluation tasks within a single Python project.
|
||||
/// Exactly one task is created for each cursor directive found in
|
||||
/// this source task.
|
||||
///
|
||||
/// This includes running `uv sync` to set up a full virtual
|
||||
/// environment.
|
||||
fn to_tasks(&self, parent_dst_dir: &SystemPath) -> anyhow::Result<Vec<Task>> {
|
||||
let dir = parent_dst_dir.join(&self.name);
|
||||
let cursors = copy_project(&self.dir, &dir)?;
|
||||
let uv_sync_output = std::process::Command::new("uv")
|
||||
.arg("sync")
|
||||
.current_dir(dir.as_std_path())
|
||||
.output()
|
||||
.with_context(|| format!("failed to run `uv sync` in `{dir}`"))?;
|
||||
if !uv_sync_output.status.success() {
|
||||
let code = uv_sync_output
|
||||
.status
|
||||
.code()
|
||||
.map(|code| code.to_string())
|
||||
.unwrap_or_else(|| "UNKNOWN".to_string());
|
||||
let stderr = bstr::BStr::new(&uv_sync_output.stderr);
|
||||
anyhow::bail!("`uv sync` failed to run with exit code `{code}`, stderr: {stderr}")
|
||||
}
|
||||
cursors
|
||||
.into_iter()
|
||||
.map(|cursor| Task::new(&dir, &self.truth, cursor))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A single cursor directive within a single Python project.
|
||||
///
|
||||
/// Each cursor directive looks like:
|
||||
/// `<CURSOR [expected-module.]expected-symbol>`.
|
||||
///
|
||||
/// That is, each cursor directive corresponds to a single completion
|
||||
/// request, and each request is a single evaluation task.
|
||||
#[derive(Clone, Debug)]
|
||||
struct Cursor {
|
||||
/// The path to the file containing this directive.
|
||||
path: SystemPathBuf,
|
||||
/// The index (starting at 0) of this cursor directive
|
||||
/// within `path`.
|
||||
index: usize,
|
||||
/// The byte offset at which this cursor was located
|
||||
/// within `path`.
|
||||
offset: usize,
|
||||
/// The expected symbol (and optionally module) for this
|
||||
/// completion request.
|
||||
answer: CompletionAnswer,
|
||||
}
|
||||
|
||||
/// The answer for a single completion request.
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct CompletionAnswer {
|
||||
symbol: String,
|
||||
module: Option<String>,
|
||||
}
|
||||
|
||||
impl CompletionAnswer {
|
||||
/// Returns true when this answer matches the completion given.
|
||||
fn matches(&self, completion: &Completion) -> bool {
|
||||
self.symbol == completion.name.as_str()
|
||||
&& self.module.as_deref() == completion.module_name.map(ModuleName::as_str)
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy the Python project from `src_dir` to `dst_dir`.
|
||||
///
|
||||
/// This also looks for occurrences of cursor directives among the
|
||||
/// project files and returns them. The original cursor directives are
|
||||
/// deleted.
|
||||
///
|
||||
/// Hidden files or directories are skipped.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any underlying I/O errors are bubbled up. Also, if no cursor
|
||||
/// directives are found, then an error is returned. This guarantees
|
||||
/// that the `Vec<Cursor>` is always non-empty.
|
||||
fn copy_project(src_dir: &SystemPath, dst_dir: &SystemPath) -> anyhow::Result<Vec<Cursor>> {
|
||||
std::fs::create_dir_all(dst_dir).with_context(|| dst_dir.to_string())?;
|
||||
|
||||
let mut cursors = vec![];
|
||||
for result in walkdir::WalkDir::new(src_dir.as_std_path()) {
|
||||
let dent =
|
||||
result.with_context(|| format!("failed to get directory entry from {src_dir}"))?;
|
||||
if dent
|
||||
.file_name()
|
||||
.to_str()
|
||||
.is_some_and(|name| name.starts_with('.'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let src = SystemPath::from_std_path(dent.path()).ok_or_else(|| {
|
||||
anyhow::anyhow!("path `{}` is not valid UTF-8", dent.path().display())
|
||||
})?;
|
||||
let name = src
|
||||
.strip_prefix(src_dir)
|
||||
.expect("descendent of `src_dir` must start with `src`");
|
||||
// let name = src
|
||||
// .file_name()
|
||||
// .ok_or_else(|| anyhow::anyhow!("path `{src}` is missing a basename"))?;
|
||||
let dst = dst_dir.join(name);
|
||||
if dent.file_type().is_dir() {
|
||||
std::fs::create_dir_all(dst.as_std_path())
|
||||
.with_context(|| format!("failed to create directory `{dst}`"))?;
|
||||
} else {
|
||||
cursors.extend(copy_file(src, &dst)?);
|
||||
}
|
||||
}
|
||||
anyhow::ensure!(
|
||||
!cursors.is_empty(),
|
||||
"could not find any `<CURSOR>` directives in any of the files in `{src_dir}`",
|
||||
);
|
||||
Ok(cursors)
|
||||
}
|
||||
|
||||
/// Copies `src` to `dst` while looking for cursor directives.
|
||||
///
|
||||
/// Each cursor directive looks like:
|
||||
/// `<CURSOR [expected-module.]expected-symbol>`.
|
||||
///
|
||||
/// When occurrences of cursor directives are found, then they are
|
||||
/// replaced with the empty string. The position of each occurrence is
|
||||
/// recorded, which points to the correct place in a document where all
|
||||
/// cursor directives are omitted.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// When an underlying I/O error occurs.
|
||||
fn copy_file(src: &SystemPath, dst: &SystemPath) -> anyhow::Result<Vec<Cursor>> {
|
||||
static RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
// Our module/symbol identifier regex here is certainly more
|
||||
// permissive than necessary, but I think that should be fine
|
||||
// for this silly little syntax. ---AG
|
||||
Regex::new(r"<CURSOR:\s*(?:(?<module>[\S--.]+)\.)?(?<symbol>[\S--.]+)>").unwrap()
|
||||
});
|
||||
|
||||
let src_data =
|
||||
std::fs::read(src).with_context(|| format!("failed to read `{src}` for copying"))?;
|
||||
let mut cursors = vec![];
|
||||
// The new data, without cursor directives.
|
||||
let mut new = Vec::with_capacity(src_data.len());
|
||||
// An index into `src_data` corresponding to either the start of
|
||||
// the data or the end of the previous cursor directive that we
|
||||
// found.
|
||||
let mut prev_match_end = 0;
|
||||
// The total bytes removed so far by replacing cursor directives
|
||||
// with empty strings.
|
||||
let mut bytes_removed = 0;
|
||||
for (index, caps) in RE.captures_iter(&src_data).enumerate() {
|
||||
let overall = caps.get(0).expect("zeroth group is always available");
|
||||
new.extend_from_slice(&src_data[prev_match_end..overall.start()]);
|
||||
prev_match_end = overall.end();
|
||||
let offset = overall.start() - bytes_removed;
|
||||
bytes_removed += overall.len();
|
||||
|
||||
let symbol = str::from_utf8(&caps["symbol"])
|
||||
.context("expected symbol in cursor directive in `{src}` is not valid UTF-8")?
|
||||
.to_string();
|
||||
let module = caps
|
||||
.name("module")
|
||||
.map(|module| {
|
||||
str::from_utf8(module.as_bytes())
|
||||
.context("expected module in cursor directive in `{src}` is not valid UTF-8")
|
||||
})
|
||||
.transpose()?
|
||||
.map(ToString::to_string);
|
||||
let answer = CompletionAnswer { symbol, module };
|
||||
cursors.push(Cursor {
|
||||
path: dst.to_path_buf(),
|
||||
index,
|
||||
offset,
|
||||
answer,
|
||||
});
|
||||
}
|
||||
new.extend_from_slice(&src_data[prev_match_end..]);
|
||||
std::fs::write(dst, &new)
|
||||
.with_context(|| format!("failed to write contents of `{src}` to `{dst}`"))?;
|
||||
Ok(cursors)
|
||||
}
|
||||
11
crates/ty_completion_eval/truth/README.md
Normal file
11
crates/ty_completion_eval/truth/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
This directory contains truth data for ty's completion evaluation.
|
||||
|
||||
# Adding new truth data
|
||||
|
||||
To add new truth data, you can either add a new `<CURSOR>` directive to an
|
||||
existing Python project in this directory or create a new Python project. To
|
||||
create a new directory, just `cp -a existing new` and modify it as needed. Then:
|
||||
|
||||
1. Check `completion.toml` for relevant settings.
|
||||
2. Run `uv.lock` after updating `pyproject.toml` (if necessary) to ensure the
|
||||
dependency versions are locked.
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = true
|
||||
@@ -0,0 +1,9 @@
|
||||
# This is similar to the `numpy-array` test case,
|
||||
# where the completions returned don't contain
|
||||
# the expected symbol at all.
|
||||
ZQZQZQ_<CURSOR: sub1.ZQZQZQ_SOMETHING_IMPORTANT>
|
||||
|
||||
import sub1
|
||||
# This works though, so ty sees the symbol where
|
||||
# as our auto-import symbol finder does not.
|
||||
sub1.ZQZQZQ_<CURSOR: ZQZQZQ_SOMETHING_IMPORTANT>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
@@ -0,0 +1 @@
|
||||
from .sub2 import ZQZQZQ_SOMETHING_IMPORTANT
|
||||
@@ -0,0 +1 @@
|
||||
ZQZQZQ_SOMETHING_IMPORTANT = 1
|
||||
8
crates/ty_completion_eval/truth/higher-level-symbols-preferred/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/higher-level-symbols-preferred/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = false
|
||||
@@ -0,0 +1,4 @@
|
||||
# This checks that we prioritize modules without
|
||||
# preceding double underscores over modules with
|
||||
# preceding double underscores.
|
||||
import zqzq<CURSOR: zqzqzq>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/import-deprioritizes-dunder/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/import-deprioritizes-dunder/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = false
|
||||
@@ -0,0 +1,4 @@
|
||||
# This checks that we prioritize modules without
|
||||
# preceding underscores over modules with
|
||||
# preceding underscores.
|
||||
import zqzq<CURSOR: zqzqzq>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/import-deprioritizes-sunder/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/import-deprioritizes-sunder/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = true
|
||||
@@ -0,0 +1,9 @@
|
||||
# This is a case where a symbol from an internal module appears
|
||||
# before the desired symbol from `typing`.
|
||||
#
|
||||
# We use a slightly different example than the one reported in
|
||||
# the issue to capture the deficiency via ranking. That is, in
|
||||
# astral-sh/ty#1274, the (current) top suggestion is the correct one.
|
||||
#
|
||||
# ref: https://github.com/astral-sh/ty/issues/1274#issuecomment-3345923575
|
||||
NoneTy<CURSOR: types.NoneType>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/internal-typeshed-hidden/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/internal-typeshed-hidden/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = true
|
||||
16
crates/ty_completion_eval/truth/numpy-array/main.py
Normal file
16
crates/ty_completion_eval/truth/numpy-array/main.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# This one is tricky because `array` is an exported
|
||||
# symbol in a whole bunch of numpy internal modules.
|
||||
#
|
||||
# At time of writing (2025-10-07), the right completion
|
||||
# doesn't actually show up at all in the suggestions
|
||||
# returned. In fact, nothing from the top-level `numpy`
|
||||
# module shows up.
|
||||
arra<CURSOR: numpy.array>
|
||||
|
||||
import numpy as np
|
||||
# In contrast to above, this *does* include the correct
|
||||
# completion. So there is likely some kind of bug in our
|
||||
# symbol discovery code for auto-import that isn't present
|
||||
# when using ty to discover symbols (which is likely far
|
||||
# too expensive to use across all dependencies).
|
||||
np.arra<CURSOR: array>
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"numpy>=2.3.3",
|
||||
]
|
||||
66
crates/ty_completion_eval/truth/numpy-array/uv.lock
generated
Normal file
66
crates/ty_completion_eval/truth/numpy-array/uv.lock
generated
Normal file
@@ -0,0 +1,66 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "numpy", specifier = ">=2.3.3" }]
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = false
|
||||
@@ -0,0 +1,16 @@
|
||||
class Quux:
|
||||
def __init__(self): pass
|
||||
def lion(self): pass
|
||||
def tiger(self): pass
|
||||
def bear(self): pass
|
||||
def chicken(self): pass
|
||||
def turkey(self): pass
|
||||
def wasp(self): pass
|
||||
def rabbit(self): pass
|
||||
def squirrel(self): pass
|
||||
|
||||
quux = Quux()
|
||||
quux.tur<CURSOR: turkey>
|
||||
|
||||
quux = Quux()
|
||||
quux.be<CURSOR: bear>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/object-attr-instance-methods/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/object-attr-instance-methods/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = false
|
||||
@@ -0,0 +1,2 @@
|
||||
# ref: https://github.com/astral-sh/ty/issues/1262
|
||||
raise NotImplement<CURSOR: NotImplementedError>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/raise-uses-base-exception/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/raise-uses-base-exception/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = true
|
||||
@@ -0,0 +1,3 @@
|
||||
# ref: https://github.com/astral-sh/ty/issues/1274#issuecomment-3345942698
|
||||
from typing import Iterator
|
||||
Iter<CURSOR: Iterator>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/scope-existing-over-new-import/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/scope-existing-over-new-import/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = false
|
||||
@@ -0,0 +1,5 @@
|
||||
zqzqzq_global_identifier = 1
|
||||
|
||||
def foo():
|
||||
zqzqzq_local_identifier = 1
|
||||
zqzqzq_<CURSOR: zqzqzq_local_identifier>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/scope-prioritize-closer/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/scope-prioritize-closer/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = false
|
||||
@@ -0,0 +1,2 @@
|
||||
simple_long_identifier = 1
|
||||
simple<CURSOR: simple_long_identifier>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/scope-simple-long-identifier/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/scope-simple-long-identifier/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = true
|
||||
@@ -0,0 +1,2 @@
|
||||
# ref: https://github.com/astral-sh/ty/issues/1274#issuecomment-3345879257
|
||||
reveal<CURSOR: typing.reveal_type>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/ty-extensions-lower-stdlib/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/ty-extensions-lower-stdlib/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
auto-import = true
|
||||
@@ -0,0 +1,12 @@
|
||||
# This one demands that `TypeVa` complete to `typing.TypeVar`
|
||||
# even though there is also an `ast.TypeVar`. Getting this one
|
||||
# right seems tricky, and probably requires module-specific
|
||||
# heuristics.
|
||||
#
|
||||
# ref: https://github.com/astral-sh/ty/issues/1274#issuecomment-3345884227
|
||||
TypeVa<CURSOR: typing.TypeVar>
|
||||
|
||||
# This is a similar case of `ctypes.cast` being preferred over
|
||||
# `typing.cast`. Maybe `typing` should just get a slightly higher
|
||||
# weight than most other stdlib modules?
|
||||
cas<CURSOR: typing.cast>
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
8
crates/ty_completion_eval/truth/type-var-typing-over-ast/uv.lock
generated
Normal file
8
crates/ty_completion_eval/truth/type-var-typing-over-ast/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -1732,6 +1732,7 @@ C.<CURSOR>
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
meta_attr :: int
|
||||
mro :: bound method <class 'C'>.mro() -> list[type]
|
||||
__annotate__ :: @Todo | None
|
||||
__annotations__ :: dict[str, Any]
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
@@ -1797,7 +1798,7 @@ Meta.<CURSOR>
|
||||
// whether we're in release mode or not. These differences
|
||||
// aren't really relevant for completion tests AFAIK, so
|
||||
// just redact them. ---AG
|
||||
filters => [(r"(?m)\s*__(annotations|new)__.+$", "")]},
|
||||
filters => [(r"(?m)\s*__(annotations|new|annotate)__.+$", "")]},
|
||||
{
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
meta_attr :: property
|
||||
@@ -1908,6 +1909,7 @@ Quux.<CURSOR>
|
||||
some_method :: def some_method(self) -> int
|
||||
some_property :: property
|
||||
some_static_method :: def some_static_method(self) -> int
|
||||
__annotate__ :: @Todo | None
|
||||
__annotations__ :: dict[str, Any]
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
@@ -1970,7 +1972,7 @@ Answer.<CURSOR>
|
||||
insta::with_settings!({
|
||||
// See above: filter out some members which contain @Todo types that are
|
||||
// rendered differently in release mode.
|
||||
filters => [(r"(?m)\s*__(call|reduce_ex)__.+$", "")]},
|
||||
filters => [(r"(?m)\s*__(call|reduce_ex|annotate|signature)__.+$", "")]},
|
||||
{
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
NO :: Literal[Answer.NO]
|
||||
@@ -2020,7 +2022,6 @@ Answer.<CURSOR>
|
||||
__reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT@__reversed__]
|
||||
__ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType
|
||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||
__signature__ :: bound method <class 'Answer'>.__signature__() -> str
|
||||
__sizeof__ :: def __sizeof__(self) -> int
|
||||
__str__ :: def __str__(self) -> str
|
||||
__subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
|
||||
|
||||
@@ -15,8 +15,10 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::HasDefinition;
|
||||
use ty_python_semantic::ImportAliasResolution;
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::types::definitions_for_keyword_argument;
|
||||
use ty_python_semantic::types::{Type, call_signature_details};
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
call_signature_details, definitions_for_keyword_argument,
|
||||
};
|
||||
use ty_python_semantic::{
|
||||
HasType, SemanticModel, definitions_for_imported_symbol, definitions_for_name,
|
||||
};
|
||||
|
||||
@@ -308,26 +308,8 @@ mod tests {
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing_extensions import TypeAliasType
|
||||
3 |
|
||||
4 | Alias = TypeAliasType("Alias", tuple[int, int])
|
||||
| ^^^^^
|
||||
5 |
|
||||
6 | Alias
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:1
|
||||
|
|
||||
4 | Alias = TypeAliasType("Alias", tuple[int, int])
|
||||
5 |
|
||||
6 | Alias
|
||||
| ^^^^^
|
||||
|
|
||||
"#);
|
||||
// TODO: This should jump to the definition of `Alias` above.
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6,7 +6,8 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::types::{Type, inlay_hint_function_argument_details};
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::inlay_hint_function_argument_details;
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -13,9 +13,8 @@ use ruff_python_ast::{
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
use std::ops::Deref;
|
||||
use ty_python_semantic::{
|
||||
HasType, SemanticModel,
|
||||
semantic_index::definition::DefinitionKind,
|
||||
types::{Type, definition_kind_for_name},
|
||||
HasType, SemanticModel, semantic_index::definition::DefinitionKind, types::Type,
|
||||
types::ide_support::definition_kind_for_name,
|
||||
};
|
||||
|
||||
// This module walks the AST and collects a set of "semantic tokens" for a file
|
||||
|
||||
@@ -17,7 +17,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::{
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
CallSignatureDetails, call_signature_details, find_active_signature_from_details,
|
||||
};
|
||||
|
||||
|
||||
@@ -520,8 +520,8 @@ pub struct EnvironmentOptions {
|
||||
/// to reflect the differing contents of the standard library across Python versions.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[option(
|
||||
default = r#""3.13""#,
|
||||
value_type = r#""3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | <major>.<minor>"#,
|
||||
default = r#""3.14""#,
|
||||
value_type = r#""3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>"#,
|
||||
example = r#"
|
||||
python-version = "3.12"
|
||||
"#
|
||||
|
||||
@@ -301,6 +301,30 @@ reveal_type(Container("a")) # revealed: Container[str]
|
||||
reveal_type(Container(b"a")) # revealed: Container[bytes]
|
||||
```
|
||||
|
||||
## Implicit self for classes with a default value for their generic parameter
|
||||
|
||||
```py
|
||||
from typing import Self, TypeVar, Generic
|
||||
|
||||
class Container[T = bytes]:
|
||||
def method(self) -> Self:
|
||||
return self
|
||||
|
||||
def _(c: Container[str], d: Container):
|
||||
reveal_type(c.method()) # revealed: Container[str]
|
||||
reveal_type(d.method()) # revealed: Container[bytes]
|
||||
|
||||
T = TypeVar("T", default=bytes)
|
||||
|
||||
class LegacyContainer(Generic[T]):
|
||||
def method(self) -> Self:
|
||||
return self
|
||||
|
||||
def _(c: LegacyContainer[str], d: LegacyContainer):
|
||||
reveal_type(c.method()) # revealed: LegacyContainer[str]
|
||||
reveal_type(d.method()) # revealed: LegacyContainer[bytes]
|
||||
```
|
||||
|
||||
## Invalid Usage
|
||||
|
||||
`Self` cannot be used in the signature of a function or variable.
|
||||
|
||||
@@ -130,13 +130,9 @@ type IntList = list[int]
|
||||
m: IntList = [1, 2, 3]
|
||||
reveal_type(m) # revealed: list[int]
|
||||
|
||||
# TODO: this should type-check and avoid literal promotion
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
||||
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
|
||||
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
# TODO: this should type-check and avoid literal promotion
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
|
||||
o: list[typing.LiteralString] = ["a", "b", "c"]
|
||||
reveal_type(o) # revealed: list[LiteralString]
|
||||
|
||||
@@ -160,6 +156,81 @@ a: list[str] = [1, 2, 3]
|
||||
b: set[int] = {1, 2, "3"}
|
||||
```
|
||||
|
||||
## Literal annnotations are respected
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
from typing_extensions import Literal, LiteralString
|
||||
|
||||
a: list[Literal[1]] = [1]
|
||||
reveal_type(a) # revealed: list[Literal[1]]
|
||||
|
||||
b: list[Literal[True]] = [True]
|
||||
reveal_type(b) # revealed: list[Literal[True]]
|
||||
|
||||
c: list[Literal["a"]] = ["a"]
|
||||
reveal_type(c) # revealed: list[Literal["a"]]
|
||||
|
||||
d: list[LiteralString] = ["a", "b", "c"]
|
||||
reveal_type(d) # revealed: list[LiteralString]
|
||||
|
||||
e: list[list[Literal[1]]] = [[1]]
|
||||
reveal_type(e) # revealed: list[list[Literal[1]]]
|
||||
|
||||
class Color(Enum):
|
||||
RED = "red"
|
||||
|
||||
f: dict[list[Literal[1]], list[Literal[Color.RED]]] = {[1]: [Color.RED, Color.RED]}
|
||||
reveal_type(f) # revealed: dict[list[Literal[1]], list[Literal[Color.RED]]]
|
||||
|
||||
class X[T]:
|
||||
def __init__(self, value: T): ...
|
||||
|
||||
g: X[Literal[1]] = X(1)
|
||||
reveal_type(g) # revealed: X[Literal[1]]
|
||||
|
||||
h: X[int] = X(1)
|
||||
reveal_type(h) # revealed: X[int]
|
||||
|
||||
i: dict[list[X[Literal[1]]], set[Literal[b"a"]]] = {[X(1)]: {b"a"}}
|
||||
reveal_type(i) # revealed: dict[list[X[Literal[1]]], set[Literal[b"a"]]]
|
||||
|
||||
j: list[Literal[1, 2, 3]] = [1, 2, 3]
|
||||
reveal_type(j) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
k: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
|
||||
reveal_type(k) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
type Y[T] = list[T]
|
||||
|
||||
l: Y[Y[Literal[1]]] = [[1]]
|
||||
reveal_type(l) # revealed: list[list[Literal[1]]]
|
||||
|
||||
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
|
||||
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
|
||||
|
||||
n: list[tuple[int, str, int]] = [(1, "2", 3), (4, "5", 6)]
|
||||
reveal_type(n) # revealed: list[tuple[int, str, int]]
|
||||
|
||||
o: list[tuple[Literal[1], ...]] = [(1, 1, 1)]
|
||||
reveal_type(o) # revealed: list[tuple[Literal[1], ...]]
|
||||
|
||||
p: list[tuple[int, ...]] = [(1, 1, 1)]
|
||||
reveal_type(p) # revealed: list[tuple[int, ...]]
|
||||
|
||||
# literal promotion occurs based on assignability, an exact match is not required
|
||||
q: list[int | Literal[1]] = [1]
|
||||
reveal_type(q) # revealed: list[int]
|
||||
|
||||
r: list[Literal[1, 2, 3, 4]] = [1, 2]
|
||||
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
|
||||
```
|
||||
|
||||
## PEP-604 annotations are supported
|
||||
|
||||
```py
|
||||
|
||||
@@ -820,22 +820,30 @@ reveal_type(C().c) # revealed: int
|
||||
|
||||
### Inheritance of class/instance attributes
|
||||
|
||||
#### Instance variable defined in a base class
|
||||
|
||||
```py
|
||||
class Base:
|
||||
declared_in_body: int | None = 1
|
||||
attribute: int | None = 1
|
||||
|
||||
base_class_attribute_1: str | None
|
||||
base_class_attribute_2: str | None
|
||||
base_class_attribute_3: str | None
|
||||
redeclared_with_same_type: str | None
|
||||
redeclared_with_narrower_type: str | None
|
||||
redeclared_with_wider_type: str | None
|
||||
|
||||
overwritten_in_subclass_body: str
|
||||
overwritten_in_subclass_method: str
|
||||
|
||||
undeclared = "base"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.defined_in_init: str | None = "value in base"
|
||||
self.pure_attribute: str | None = "value in base"
|
||||
|
||||
self.pure_overwritten_in_subclass_body: str = "value in base"
|
||||
self.pure_overwritten_in_subclass_method: str = "value in base"
|
||||
|
||||
self.pure_undeclared = "base"
|
||||
|
||||
class Intermediate(Base):
|
||||
# Redeclaring base class attributes with the *same *type is fine:
|
||||
base_class_attribute_1: str | None = None
|
||||
redeclared_with_same_type: str | None = None
|
||||
|
||||
# Redeclaring them with a *narrower type* is unsound, because modifications
|
||||
# through a `Base` reference could violate that constraint.
|
||||
@@ -847,22 +855,70 @@ class Intermediate(Base):
|
||||
# enabled by default can still be discussed.
|
||||
#
|
||||
# TODO: This should be an error
|
||||
base_class_attribute_2: str
|
||||
redeclared_with_narrower_type: str
|
||||
|
||||
# Redeclaring attributes with a *wider type* directly violates LSP.
|
||||
#
|
||||
# In this case, both mypy and pyright report an error.
|
||||
#
|
||||
# TODO: This should be an error
|
||||
base_class_attribute_3: str | int | None
|
||||
redeclared_with_wider_type: str | int | None
|
||||
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
overwritten_in_subclass_body = None
|
||||
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
pure_overwritten_in_subclass_body = None
|
||||
|
||||
undeclared = "intermediate"
|
||||
|
||||
def set_attributes(self) -> None:
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
self.overwritten_in_subclass_method = None
|
||||
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
self.pure_overwritten_in_subclass_method = None
|
||||
|
||||
self.pure_undeclared = "intermediate"
|
||||
|
||||
class Derived(Intermediate): ...
|
||||
|
||||
reveal_type(Derived.declared_in_body) # revealed: int | None
|
||||
reveal_type(Derived.attribute) # revealed: int | None
|
||||
reveal_type(Derived().attribute) # revealed: int | None
|
||||
|
||||
reveal_type(Derived().declared_in_body) # revealed: int | None
|
||||
reveal_type(Derived.redeclared_with_same_type) # revealed: str | None
|
||||
reveal_type(Derived().redeclared_with_same_type) # revealed: str | None
|
||||
|
||||
reveal_type(Derived().defined_in_init) # revealed: str | None
|
||||
# TODO: It would probably be more consistent if these were `str | None`
|
||||
reveal_type(Derived.redeclared_with_narrower_type) # revealed: str
|
||||
reveal_type(Derived().redeclared_with_narrower_type) # revealed: str
|
||||
|
||||
# TODO: It would probably be more consistent if these were `str | None`
|
||||
reveal_type(Derived.redeclared_with_wider_type) # revealed: str | int | None
|
||||
reveal_type(Derived().redeclared_with_wider_type) # revealed: str | int | None
|
||||
|
||||
# TODO: Both of these should be `str`
|
||||
reveal_type(Derived.overwritten_in_subclass_body) # revealed: Unknown | None
|
||||
reveal_type(Derived().overwritten_in_subclass_body) # revealed: Unknown | None | str
|
||||
|
||||
# TODO: Both of these should be `str`
|
||||
reveal_type(Derived.overwritten_in_subclass_method) # revealed: str
|
||||
reveal_type(Derived().overwritten_in_subclass_method) # revealed: str | Unknown | None
|
||||
|
||||
reveal_type(Derived().pure_attribute) # revealed: str | None
|
||||
|
||||
# TODO: This should be `str`
|
||||
reveal_type(Derived().pure_overwritten_in_subclass_body) # revealed: Unknown | None | str
|
||||
|
||||
# TODO: This should be `str`
|
||||
reveal_type(Derived().pure_overwritten_in_subclass_method) # revealed: Unknown | None
|
||||
|
||||
# TODO: Both of these should be `Unknown | Literal["intermediate", "base"]`
|
||||
reveal_type(Derived.undeclared) # revealed: Unknown | Literal["intermediate"]
|
||||
reveal_type(Derived().undeclared) # revealed: Unknown | Literal["intermediate"]
|
||||
|
||||
# TODO: This should be `Unknown | Literal["intermediate", "base"]`
|
||||
reveal_type(Derived().pure_undeclared) # revealed: Unknown | Literal["intermediate"]
|
||||
```
|
||||
|
||||
## Accessing attributes on class objects
|
||||
|
||||
@@ -1210,11 +1210,7 @@ from typing_extensions import LiteralString
|
||||
|
||||
def f(a: Foo, b: list[str], c: list[LiteralString], e):
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
# TODO: we should select the second overload here and reveal `str`
|
||||
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
|
||||
reveal_type(a.join(b)) # revealed: LiteralString
|
||||
|
||||
reveal_type(a.join(b)) # revealed: str
|
||||
reveal_type(a.join(c)) # revealed: LiteralString
|
||||
|
||||
# since both overloads match and they have return types that are not equivalent,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user