Compare commits
7 Commits
gankra/scr
...
ibraheem/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
531ca7e47a | ||
|
|
caed80df5e | ||
|
|
5d07d58d59 | ||
|
|
19c5cd8018 | ||
|
|
12e72041c6 | ||
|
|
be78ea3851 | ||
|
|
623210232e |
8
.github/renovate.json5
vendored
8
.github/renovate.json5
vendored
@@ -75,6 +75,14 @@
|
||||
matchManagers: ["cargo"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
// `mkdocs-material` requires a manual update to keep the version in sync
|
||||
// with `mkdocs-material-insider`.
|
||||
// See: https://squidfunk.github.io/mkdocs-material/insiders/upgrade/
|
||||
matchManagers: ["pip_requirements"],
|
||||
matchPackageNames: ["mkdocs-material"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
groupName: "pre-commit dependencies",
|
||||
matchManagers: ["pre-commit"],
|
||||
|
||||
19
.github/workflows/ci.yaml
vendored
19
.github/workflows/ci.yaml
vendored
@@ -24,8 +24,6 @@ env:
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.14"
|
||||
NEXTEST_PROFILE: ci
|
||||
# Enable mdtests that require external dependencies
|
||||
MDTEST_EXTERNAL: "1"
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
@@ -781,6 +779,8 @@ jobs:
|
||||
name: "mkdocs"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
@@ -788,6 +788,11 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
@@ -795,7 +800,11 @@ jobs:
|
||||
with:
|
||||
python-version: 3.13
|
||||
activate-environment: true
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt
|
||||
- name: "Install dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: uv pip install -r docs/requirements.txt
|
||||
- name: "Update README File"
|
||||
run: python scripts/transform_readme.py --target mkdocs
|
||||
@@ -803,8 +812,12 @@ jobs:
|
||||
run: python scripts/generate_mkdocs.py
|
||||
- name: "Check docs formatting"
|
||||
run: python scripts/check_docs_formatted.py
|
||||
- name: "Build Insiders docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
- name: "Build docs"
|
||||
run: mkdocs build --strict -f mkdocs.yml
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
check-formatter-instability-and-black-similarity:
|
||||
name: "formatter instabilities and black similarity"
|
||||
|
||||
20
.github/workflows/publish-docs.yml
vendored
20
.github/workflows/publish-docs.yml
vendored
@@ -20,6 +20,8 @@ on:
|
||||
jobs:
|
||||
mkdocs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
@@ -57,12 +59,23 @@ jobs:
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: pip install -r docs/requirements-insiders.txt
|
||||
|
||||
- name: "Install dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: pip install -r docs/requirements.txt
|
||||
|
||||
- name: "Copy README File"
|
||||
@@ -70,8 +83,13 @@ jobs:
|
||||
python scripts/transform_readme.py --target mkdocs
|
||||
python scripts/generate_mkdocs.py
|
||||
|
||||
- name: "Build Insiders docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
|
||||
- name: "Build docs"
|
||||
run: mkdocs build --strict -f mkdocs.yml
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
- name: "Clone docs repo"
|
||||
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
||||
|
||||
6
.github/workflows/publish-pypi.yml
vendored
6
.github/workflows/publish-pypi.yml
vendored
@@ -18,8 +18,7 @@ jobs:
|
||||
environment:
|
||||
name: release
|
||||
permissions:
|
||||
# For PyPI's trusted publishing.
|
||||
id-token: write
|
||||
id-token: write # For PyPI's trusted publishing + PEP 740 attestations
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
@@ -28,5 +27,8 @@ jobs:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
merge-multiple: true
|
||||
- uses: astral-sh/attest-action@2c727738cea36d6c97dd85eb133ea0e0e8fe754b # v0.0.4
|
||||
with:
|
||||
paths: wheels/*
|
||||
- name: Publish to PyPi
|
||||
run: uv publish -v wheels/*
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,34 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.8
|
||||
|
||||
Released on 2025-12-04.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bugbear`\] Catch `yield` expressions within other statements (`B901`) ([#21200](https://github.com/astral-sh/ruff/pull/21200))
|
||||
- \[`flake8-use-pathlib`\] Mark fixes unsafe for return type changes (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#21440](https://github.com/astral-sh/ruff/pull/21440))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix syntax error false positives for `await` outside functions ([#21763](https://github.com/astral-sh/ruff/pull/21763))
|
||||
- \[`flake8-simplify`\] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (`SIM222`, `SIM223`) ([#21479](https://github.com/astral-sh/ruff/pull/21479))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Suggest using `--output-file` option in GitLab integration ([#21706](https://github.com/astral-sh/ruff/pull/21706))
|
||||
|
||||
### Other changes
|
||||
|
||||
- [syntax-error] Default type parameter followed by non-default type parameter ([#21657](https://github.com/astral-sh/ruff/pull/21657))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@kieran-ryan](https://github.com/kieran-ryan)
|
||||
- [@11happy](https://github.com/11happy)
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
|
||||
## 0.14.7
|
||||
|
||||
Released on 2025-11-28.
|
||||
|
||||
@@ -331,6 +331,13 @@ you addressed them.
|
||||
|
||||
## MkDocs
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> The documentation uses Material for MkDocs Insiders, which is closed-source software.
|
||||
> This means only members of the Astral organization can preview the documentation exactly as it
|
||||
> will appear in production.
|
||||
> Outside contributors can still preview the documentation, but there will be some differences. Consult [the Material for MkDocs documentation](https://squidfunk.github.io/mkdocs-material/insiders/benefits/#features) for which features are exclusively available in the insiders version.
|
||||
|
||||
To preview any changes to the documentation locally:
|
||||
|
||||
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
|
||||
@@ -344,7 +351,11 @@ To preview any changes to the documentation locally:
|
||||
1. Run the development server with:
|
||||
|
||||
```shell
|
||||
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.yml
|
||||
# For contributors.
|
||||
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.public.yml
|
||||
|
||||
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
|
||||
uvx --with-requirements docs/requirements-insiders.txt -- mkdocs serve -f mkdocs.insiders.yml
|
||||
```
|
||||
|
||||
The documentation should then be available locally at
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -2859,7 +2859,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.8"
|
||||
version = "0.14.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3117,14 +3117,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.8"
|
||||
version = "0.14.7"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"clap",
|
||||
"colored 3.0.0",
|
||||
"compact_str",
|
||||
"fern",
|
||||
"glob",
|
||||
"globset",
|
||||
@@ -3473,7 +3472,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.8"
|
||||
version = "0.14.7"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -4557,7 +4556,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"dunce",
|
||||
"insta",
|
||||
"memchr",
|
||||
"path-slash",
|
||||
|
||||
@@ -272,12 +272,6 @@ large_stack_arrays = "allow"
|
||||
lto = "fat"
|
||||
codegen-units = 16
|
||||
|
||||
# Profile to build a minimally sized binary for ruff/ty
|
||||
[profile.minimal-size]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
|
||||
# Some crates don't change as much but benefit more from
|
||||
# more expensive optimization passes, so we selectively
|
||||
# decrease codegen-units in some cases.
|
||||
|
||||
@@ -147,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.14.8/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.8/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.7/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.7/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -181,7 +181,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.8
|
||||
rev: v0.14.7
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.8"
|
||||
version = "0.14.7"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -166,8 +166,28 @@ impl Diagnostic {
|
||||
/// Returns the primary message for this diagnostic.
|
||||
///
|
||||
/// A diagnostic always has a message, but it may be empty.
|
||||
///
|
||||
/// NOTE: At present, this routine will return the first primary
|
||||
/// annotation's message as the primary message when the main diagnostic
|
||||
/// message is empty. This is meant to facilitate an incremental migration
|
||||
/// in ty over to the new diagnostic data model. (The old data model
|
||||
/// didn't distinguish between messages on the entire diagnostic and
|
||||
/// messages attached to a particular span.)
|
||||
pub fn primary_message(&self) -> &str {
|
||||
self.inner.message.as_str()
|
||||
if !self.inner.message.as_str().is_empty() {
|
||||
return self.inner.message.as_str();
|
||||
}
|
||||
// FIXME: As a special case, while we're migrating ty
|
||||
// to the new diagnostic data model, we'll look for a primary
|
||||
// message from the primary annotation. This is because most
|
||||
// ty diagnostics are created with an empty diagnostic
|
||||
// message and instead attach the message to the annotation.
|
||||
// Fixing this will require touching basically every diagnostic
|
||||
// in ty, so we do it this way for now to match the old
|
||||
// semantics. ---AG
|
||||
self.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Introspects this diagnostic and returns what kind of "primary" message
|
||||
@@ -179,6 +199,18 @@ impl Diagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
@@ -192,10 +224,11 @@ impl Diagnostic {
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +693,18 @@ impl SubDiagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
@@ -669,10 +714,11 @@ impl SubDiagnostic {
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -842,10 +888,6 @@ impl Annotation {
|
||||
pub fn hide_snippet(&mut self, yes: bool) {
|
||||
self.hide_snippet = yes;
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.is_primary
|
||||
}
|
||||
}
|
||||
|
||||
/// Tags that can be associated with an annotation.
|
||||
@@ -1466,10 +1508,28 @@ pub enum DiagnosticFormat {
|
||||
pub enum ConciseMessage<'a> {
|
||||
/// A diagnostic contains a non-empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
MainDiagnostic(&'a str),
|
||||
/// A diagnostic contains an empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "old" data model.
|
||||
PrimaryAnnotation(&'a str),
|
||||
/// A diagnostic contains a non-empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
Both { main: &'a str, annotation: &'a str },
|
||||
/// A diagnostic contains an empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This indicates that the diagnostic is probably using the old
|
||||
/// model.
|
||||
Empty,
|
||||
/// A custom concise message has been provided.
|
||||
Custom(&'a str),
|
||||
}
|
||||
@@ -1480,9 +1540,13 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
ConciseMessage::MainDiagnostic(main) => {
|
||||
write!(f, "{main}")
|
||||
}
|
||||
ConciseMessage::PrimaryAnnotation(annotation) => {
|
||||
write!(f, "{annotation}")
|
||||
}
|
||||
ConciseMessage::Both { main, annotation } => {
|
||||
write!(f, "{main}: {annotation}")
|
||||
}
|
||||
ConciseMessage::Empty => Ok(()),
|
||||
ConciseMessage::Custom(message) => {
|
||||
write!(f, "{message}")
|
||||
}
|
||||
|
||||
@@ -21,11 +21,7 @@ use crate::source::source_text;
|
||||
/// reflected in the changed AST offsets.
|
||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
|
||||
/// for determining if a query result is unchanged.
|
||||
///
|
||||
/// The LRU capacity of 200 was picked without any empirical evidence that it's optimal,
|
||||
/// instead it's a wild guess that it should be unlikely that incremental changes involve
|
||||
/// more than 200 modules. Parsed ASTs within the same revision are never evicted by Salsa.
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size, lru=200)]
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
@@ -96,9 +92,14 @@ impl ParsedModule {
|
||||
self.inner.store(None);
|
||||
}
|
||||
|
||||
/// Returns the file to which this module belongs.
|
||||
pub fn file(&self) -> File {
|
||||
self.file
|
||||
/// Returns the pointer address of this [`ParsedModule`].
|
||||
///
|
||||
/// The pointer uniquely identifies the module within the current Salsa revision,
|
||||
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
|
||||
pub fn addr(&self) -> usize {
|
||||
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
|
||||
// `Arc` within the `ArcSwap` may change.
|
||||
Arc::as_ptr(&self.inner).addr()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -667,13 +667,6 @@ impl Deref for SystemPathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for SystemPathBuf {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_std_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<SystemPath>> FromIterator<P> for SystemPathBuf {
|
||||
fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
|
||||
let mut buf = SystemPathBuf::new();
|
||||
|
||||
@@ -49,7 +49,7 @@ impl ModuleImports {
|
||||
// Resolve the imports.
|
||||
let mut resolved_imports = ModuleImports::default();
|
||||
for import in imports {
|
||||
for resolved in Resolver::new(db, path).resolve(import) {
|
||||
for resolved in Resolver::new(db).resolve(import) {
|
||||
if let Some(path) = resolved.as_system_path() {
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
use ruff_db::files::{File, FilePath, system_path_to_file};
|
||||
use ruff_db::system::SystemPath;
|
||||
use ty_python_semantic::{
|
||||
ModuleName, resolve_module, resolve_module_confident, resolve_real_module,
|
||||
resolve_real_module_confident,
|
||||
};
|
||||
use ruff_db::files::FilePath;
|
||||
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
|
||||
|
||||
use crate::ModuleDb;
|
||||
use crate::collector::CollectedImport;
|
||||
@@ -11,15 +7,12 @@ use crate::collector::CollectedImport;
|
||||
/// Collect all imports for a given Python file.
|
||||
pub(crate) struct Resolver<'a> {
|
||||
db: &'a ModuleDb,
|
||||
file: Option<File>,
|
||||
}
|
||||
|
||||
impl<'a> Resolver<'a> {
|
||||
/// Initialize a [`Resolver`] with a given [`ModuleDb`].
|
||||
pub(crate) fn new(db: &'a ModuleDb, path: &SystemPath) -> Self {
|
||||
// If we know the importing file we can potentially resolve more imports
|
||||
let file = system_path_to_file(db, path).ok();
|
||||
Self { db, file }
|
||||
pub(crate) fn new(db: &'a ModuleDb) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
/// Resolve the [`CollectedImport`] into a [`FilePath`].
|
||||
@@ -77,21 +70,13 @@ impl<'a> Resolver<'a> {
|
||||
|
||||
/// Resolves a module name to a module.
|
||||
pub(crate) fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = if let Some(file) = self.file {
|
||||
resolve_module(self.db, file, module_name)?
|
||||
} else {
|
||||
resolve_module_confident(self.db, module_name)?
|
||||
};
|
||||
let module = resolve_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
|
||||
/// Resolves a module name to a module (stubs not allowed).
|
||||
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = if let Some(file) = self.file {
|
||||
resolve_real_module(self.db, file, module_name)?
|
||||
} else {
|
||||
resolve_real_module_confident(self.db, module_name)?
|
||||
};
|
||||
let module = resolve_real_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.8"
|
||||
version = "0.14.7"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -35,7 +35,6 @@ anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "string"], optional = true }
|
||||
colored = { workspace = true }
|
||||
compact_str = { workspace = true }
|
||||
fern = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
|
||||
@@ -28,11 +28,9 @@ yaml.load("{}", SafeLoader)
|
||||
yaml.load("{}", yaml.SafeLoader)
|
||||
yaml.load("{}", CSafeLoader)
|
||||
yaml.load("{}", yaml.CSafeLoader)
|
||||
yaml.load("{}", yaml.cyaml.CSafeLoader)
|
||||
yaml.load("{}", NewSafeLoader)
|
||||
yaml.load("{}", Loader=SafeLoader)
|
||||
yaml.load("{}", Loader=yaml.SafeLoader)
|
||||
yaml.load("{}", Loader=CSafeLoader)
|
||||
yaml.load("{}", Loader=yaml.CSafeLoader)
|
||||
yaml.load("{}", Loader=yaml.cyaml.CSafeLoader)
|
||||
yaml.load("{}", Loader=NewSafeLoader)
|
||||
|
||||
@@ -52,16 +52,16 @@ def not_broken5():
|
||||
yield inner()
|
||||
|
||||
|
||||
def broken3():
|
||||
def not_broken6():
|
||||
return (yield from [])
|
||||
|
||||
|
||||
def broken4():
|
||||
def not_broken7():
|
||||
x = yield from []
|
||||
return x
|
||||
|
||||
|
||||
def broken5():
|
||||
def not_broken8():
|
||||
x = None
|
||||
|
||||
def inner(ex):
|
||||
@@ -76,13 +76,3 @@ class NotBroken9(object):
|
||||
def __await__(self):
|
||||
yield from function()
|
||||
return 42
|
||||
|
||||
|
||||
async def broken6():
|
||||
yield 1
|
||||
return foo()
|
||||
|
||||
|
||||
async def broken7():
|
||||
yield 1
|
||||
return [1, 2, 3]
|
||||
|
||||
@@ -17,24 +17,3 @@ def _():
|
||||
|
||||
# Valid yield scope
|
||||
yield 3
|
||||
|
||||
|
||||
# await is valid in any generator, sync or async
|
||||
(await cor async for cor in f()) # ok
|
||||
(await cor for cor in f()) # ok
|
||||
|
||||
# but not in comprehensions
|
||||
[await cor async for cor in f()] # F704
|
||||
{await cor async for cor in f()} # F704
|
||||
{await cor: 1 async for cor in f()} # F704
|
||||
[await cor for cor in f()] # F704
|
||||
{await cor for cor in f()} # F704
|
||||
{await cor: 1 for cor in f()} # F704
|
||||
|
||||
# or in the iterator of an async generator, which is evaluated in the parent
|
||||
# scope
|
||||
(cor async for cor in await f()) # F704
|
||||
(await cor async for cor in [await c for c in f()]) # F704
|
||||
|
||||
# this is also okay because the comprehension is within the generator scope
|
||||
([await c for c in cor] async for cor in f()) # ok
|
||||
|
||||
@@ -3,5 +3,3 @@ def func():
|
||||
|
||||
# Top-level await
|
||||
await 1
|
||||
|
||||
([await c for c in cor] async for cor in func()) # ok
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
async def gen():
|
||||
yield 1
|
||||
return 42
|
||||
|
||||
def gen(): # B901 but not a syntax error - not an async generator
|
||||
yield 1
|
||||
return 42
|
||||
|
||||
async def gen(): # ok - no value in return
|
||||
yield 1
|
||||
return
|
||||
|
||||
async def gen():
|
||||
yield 1
|
||||
return foo()
|
||||
|
||||
async def gen():
|
||||
yield 1
|
||||
return [1, 2, 3]
|
||||
|
||||
async def gen():
|
||||
if True:
|
||||
yield 1
|
||||
return 10
|
||||
@@ -69,7 +69,6 @@ use crate::noqa::NoqaMapping;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::preview::is_undefined_export_in_dunder_init_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_bugbear::rules::ReturnInGenerator;
|
||||
use crate::rules::pyflakes::rules::{
|
||||
LateFutureImport, MultipleStarredExpressions, ReturnOutsideFunction,
|
||||
UndefinedLocalWithNestedImportStarUsage, YieldOutsideFunction,
|
||||
@@ -730,12 +729,6 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
self.report_diagnostic(NonlocalWithoutBinding { name }, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReturnInGenerator => {
|
||||
// B901
|
||||
if self.is_rule_enabled(Rule::ReturnInGenerator) {
|
||||
self.report_diagnostic(ReturnInGenerator, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
||||
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
||||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
||||
@@ -754,7 +747,6 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
|
||||
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
|
||||
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_) => {
|
||||
self.semantic_errors.borrow_mut().push(error);
|
||||
}
|
||||
@@ -788,10 +780,6 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) => return false,
|
||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||
ScopeKind::Generator {
|
||||
kind: GeneratorKind::Generator,
|
||||
..
|
||||
} => return true,
|
||||
ScopeKind::Generator { .. }
|
||||
| ScopeKind::Module
|
||||
| ScopeKind::Type
|
||||
@@ -841,19 +829,14 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
self.source_type.is_ipynb()
|
||||
}
|
||||
|
||||
fn in_generator_context(&self) -> bool {
|
||||
for scope in self.semantic.current_scopes() {
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Generator {
|
||||
kind: GeneratorKind::Generator,
|
||||
..
|
||||
}
|
||||
) {
|
||||
return true;
|
||||
fn in_generator_scope(&self) -> bool {
|
||||
matches!(
|
||||
&self.semantic.current_scope().kind,
|
||||
ScopeKind::Generator {
|
||||
kind: GeneratorKind::Generator,
|
||||
..
|
||||
}
|
||||
}
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
fn in_loop_context(&self) -> bool {
|
||||
|
||||
@@ -46,7 +46,6 @@ pub mod rule_selector;
|
||||
pub mod rules;
|
||||
pub mod settings;
|
||||
pub mod source_kind;
|
||||
pub mod suppression;
|
||||
mod text_helpers;
|
||||
pub mod upstream_categories;
|
||||
mod violation;
|
||||
|
||||
@@ -1043,7 +1043,6 @@ mod tests {
|
||||
Rule::YieldFromInAsyncFunction,
|
||||
Path::new("yield_from_in_async_function.py")
|
||||
)]
|
||||
#[test_case(Rule::ReturnInGenerator, Path::new("return_in_generator.py"))]
|
||||
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = path.to_string_lossy().to_string();
|
||||
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
|
||||
|
||||
@@ -75,7 +75,6 @@ pub(crate) fn unsafe_yaml_load(checker: &Checker, call: &ast::ExprCall) {
|
||||
qualified_name.segments(),
|
||||
["yaml", "SafeLoader" | "CSafeLoader"]
|
||||
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
|
||||
| ["yaml", "cyaml", "CSafeLoader"]
|
||||
)
|
||||
})
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt};
|
||||
use ruff_python_ast::statement_visitor;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt, StmtFunctionDef};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
@@ -95,11 +96,6 @@ pub(crate) fn return_in_generator(checker: &Checker, function_def: &StmtFunction
|
||||
return;
|
||||
}
|
||||
|
||||
// Async functions are flagged by the `ReturnInGenerator` semantic syntax error.
|
||||
if function_def.is_async {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut visitor = ReturnInGeneratorVisitor::default();
|
||||
visitor.visit_body(&function_def.body);
|
||||
|
||||
@@ -116,9 +112,15 @@ struct ReturnInGeneratorVisitor {
|
||||
has_yield: bool,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for ReturnInGeneratorVisitor {
|
||||
impl StatementVisitor<'_> for ReturnInGeneratorVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::Expr(ast::StmtExpr { value, .. }) => match **value {
|
||||
Expr::Yield(_) | Expr::YieldFrom(_) => {
|
||||
self.has_yield = true;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Stmt::FunctionDef(_) => {
|
||||
// Do not recurse into nested functions; they're evaluated separately.
|
||||
}
|
||||
@@ -128,19 +130,8 @@ impl Visitor<'_> for ReturnInGeneratorVisitor {
|
||||
node_index: _,
|
||||
}) => {
|
||||
self.return_ = Some(*range);
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
_ => walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
match expr {
|
||||
Expr::Lambda(_) => {}
|
||||
Expr::Yield(_) | Expr::YieldFrom(_) => {
|
||||
self.has_yield = true;
|
||||
}
|
||||
_ => walk_expr(self, expr),
|
||||
_ => statement_visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,46 +21,3 @@ B901 Using `yield` and `return {value}` in a generator function can lead to conf
|
||||
37 |
|
||||
38 | yield from not_broken()
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> B901.py:56:5
|
||||
|
|
||||
55 | def broken3():
|
||||
56 | return (yield from [])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> B901.py:61:5
|
||||
|
|
||||
59 | def broken4():
|
||||
60 | x = yield from []
|
||||
61 | return x
|
||||
| ^^^^^^^^
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> B901.py:72:5
|
||||
|
|
||||
71 | inner((yield from []))
|
||||
72 | return x
|
||||
| ^^^^^^^^
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> B901.py:83:5
|
||||
|
|
||||
81 | async def broken6():
|
||||
82 | yield 1
|
||||
83 | return foo()
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> B901.py:88:5
|
||||
|
|
||||
86 | async def broken7():
|
||||
87 | yield 1
|
||||
88 | return [1, 2, 3]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -37,88 +37,3 @@ F704 `await` statement outside of a function
|
||||
12 |
|
||||
13 | def _():
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:27:2
|
||||
|
|
||||
26 | # but not in comprehensions
|
||||
27 | [await cor async for cor in f()] # F704
|
||||
| ^^^^^^^^^
|
||||
28 | {await cor async for cor in f()} # F704
|
||||
29 | {await cor: 1 async for cor in f()} # F704
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:28:2
|
||||
|
|
||||
26 | # but not in comprehensions
|
||||
27 | [await cor async for cor in f()] # F704
|
||||
28 | {await cor async for cor in f()} # F704
|
||||
| ^^^^^^^^^
|
||||
29 | {await cor: 1 async for cor in f()} # F704
|
||||
30 | [await cor for cor in f()] # F704
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:29:2
|
||||
|
|
||||
27 | [await cor async for cor in f()] # F704
|
||||
28 | {await cor async for cor in f()} # F704
|
||||
29 | {await cor: 1 async for cor in f()} # F704
|
||||
| ^^^^^^^^^
|
||||
30 | [await cor for cor in f()] # F704
|
||||
31 | {await cor for cor in f()} # F704
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:30:2
|
||||
|
|
||||
28 | {await cor async for cor in f()} # F704
|
||||
29 | {await cor: 1 async for cor in f()} # F704
|
||||
30 | [await cor for cor in f()] # F704
|
||||
| ^^^^^^^^^
|
||||
31 | {await cor for cor in f()} # F704
|
||||
32 | {await cor: 1 for cor in f()} # F704
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:31:2
|
||||
|
|
||||
29 | {await cor: 1 async for cor in f()} # F704
|
||||
30 | [await cor for cor in f()] # F704
|
||||
31 | {await cor for cor in f()} # F704
|
||||
| ^^^^^^^^^
|
||||
32 | {await cor: 1 for cor in f()} # F704
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:32:2
|
||||
|
|
||||
30 | [await cor for cor in f()] # F704
|
||||
31 | {await cor for cor in f()} # F704
|
||||
32 | {await cor: 1 for cor in f()} # F704
|
||||
| ^^^^^^^^^
|
||||
33 |
|
||||
34 | # or in the iterator of an async generator, which is evaluated in the parent
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:36:23
|
||||
|
|
||||
34 | # or in the iterator of an async generator, which is evaluated in the parent
|
||||
35 | # scope
|
||||
36 | (cor async for cor in await f()) # F704
|
||||
| ^^^^^^^^^
|
||||
37 | (await cor async for cor in [await c for c in f()]) # F704
|
||||
|
|
||||
|
||||
F704 `await` statement outside of a function
|
||||
--> F704.py:37:30
|
||||
|
|
||||
35 | # scope
|
||||
36 | (cor async for cor in await f()) # F704
|
||||
37 | (await cor async for cor in [await c for c in f()]) # F704
|
||||
| ^^^^^^^
|
||||
38 |
|
||||
39 | # this is also okay because the comprehension is within the generator scope
|
||||
|
|
||||
|
||||
@@ -17,6 +17,4 @@ PLE1142 `await` should be used within an async function
|
||||
4 | # Top-level await
|
||||
5 | await 1
|
||||
| ^^^^^^^
|
||||
6 |
|
||||
7 | ([await c for c in cor] async for cor in func()) # ok
|
||||
|
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> resources/test/fixtures/syntax_errors/return_in_generator.py:3:5
|
||||
|
|
||||
1 | async def gen():
|
||||
2 | yield 1
|
||||
3 | return 42
|
||||
| ^^^^^^^^^
|
||||
4 |
|
||||
5 | def gen(): # B901 but not a syntax error - not an async generator
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> resources/test/fixtures/syntax_errors/return_in_generator.py:7:5
|
||||
|
|
||||
5 | def gen(): # B901 but not a syntax error - not an async generator
|
||||
6 | yield 1
|
||||
7 | return 42
|
||||
| ^^^^^^^^^
|
||||
8 |
|
||||
9 | async def gen(): # ok - no value in return
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> resources/test/fixtures/syntax_errors/return_in_generator.py:15:5
|
||||
|
|
||||
13 | async def gen():
|
||||
14 | yield 1
|
||||
15 | return foo()
|
||||
| ^^^^^^^^^^^^
|
||||
16 |
|
||||
17 | async def gen():
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> resources/test/fixtures/syntax_errors/return_in_generator.py:19:5
|
||||
|
|
||||
17 | async def gen():
|
||||
18 | yield 1
|
||||
19 | return [1, 2, 3]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
20 |
|
||||
21 | async def gen():
|
||||
|
|
||||
|
||||
B901 Using `yield` and `return {value}` in a generator function can lead to confusing behavior
|
||||
--> resources/test/fixtures/syntax_errors/return_in_generator.py:24:5
|
||||
|
|
||||
22 | if True:
|
||||
23 | yield 1
|
||||
24 | return 10
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,17 @@
|
||||
use std::cell::RefCell;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use get_size2::{GetSize, StandardTracker};
|
||||
use ordermap::{OrderMap, OrderSet};
|
||||
|
||||
thread_local! {
|
||||
pub static TRACKER: RefCell<Option<StandardTracker>>= const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
struct TrackerGuard(Option<StandardTracker>);
|
||||
|
||||
impl Drop for TrackerGuard {
|
||||
fn drop(&mut self) {
|
||||
TRACKER.set(self.0.take());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach_tracker<R>(tracker: StandardTracker, f: impl FnOnce() -> R) -> R {
|
||||
let prev = TRACKER.replace(Some(tracker));
|
||||
let _guard = TrackerGuard(prev);
|
||||
f()
|
||||
}
|
||||
|
||||
fn with_tracker<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(Option<&mut StandardTracker>) -> R,
|
||||
{
|
||||
TRACKER.with(|tracker| {
|
||||
let mut tracker = tracker.borrow_mut();
|
||||
f(tracker.as_mut())
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the memory usage of the provided object, using a global tracker to avoid
|
||||
/// double-counting shared objects.
|
||||
pub fn heap_size<T: GetSize>(value: &T) -> usize {
|
||||
with_tracker(|tracker| {
|
||||
if let Some(tracker) = tracker {
|
||||
value.get_heap_size_with_tracker(tracker).0
|
||||
} else {
|
||||
value.get_heap_size()
|
||||
}
|
||||
})
|
||||
static TRACKER: LazyLock<Mutex<StandardTracker>> =
|
||||
LazyLock::new(|| Mutex::new(StandardTracker::new()));
|
||||
|
||||
value
|
||||
.get_heap_size_with_tracker(&mut *TRACKER.lock().unwrap())
|
||||
.0
|
||||
}
|
||||
|
||||
/// An implementation of [`GetSize::get_heap_size`] for [`OrderSet`].
|
||||
|
||||
@@ -11,8 +11,6 @@ use crate::ExprRef;
|
||||
/// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of
|
||||
/// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should
|
||||
/// generally prefer [`parenthesized_range`].
|
||||
///
|
||||
/// Prefer [`crate::token::parentheses_iterator`] if you have access to [`crate::token::Tokens`].
|
||||
pub fn parentheses_iterator<'a>(
|
||||
expr: ExprRef<'a>,
|
||||
parent: Option<AnyNodeRef>,
|
||||
@@ -59,8 +57,6 @@ pub fn parentheses_iterator<'a>(
|
||||
|
||||
/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
|
||||
/// parenthesized; or `None`, if the expression is not parenthesized.
|
||||
///
|
||||
/// Prefer [`crate::token::parenthesized_range`] if you have access to [`crate::token::Tokens`].
|
||||
pub fn parenthesized_range(
|
||||
expr: ExprRef,
|
||||
parent: AnyNodeRef,
|
||||
|
||||
@@ -12,11 +12,11 @@ static FINDER: LazyLock<Finder> = LazyLock::new(|| Finder::new(b"# /// script"))
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ScriptTag {
|
||||
/// The content of the script before the metadata block.
|
||||
pub prelude: String,
|
||||
prelude: String,
|
||||
/// The metadata block.
|
||||
pub metadata: String,
|
||||
metadata: String,
|
||||
/// The content of the script after the metadata block.
|
||||
pub postlude: String,
|
||||
postlude: String,
|
||||
}
|
||||
|
||||
impl ScriptTag {
|
||||
|
||||
@@ -16,10 +16,8 @@ use crate::str_prefix::{
|
||||
use crate::{AnyStringFlags, BoolOp, Operator, StringFlags, UnaryOp};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
mod parentheses;
|
||||
mod tokens;
|
||||
|
||||
pub use parentheses::{parentheses_iterator, parenthesized_range};
|
||||
pub use tokens::{TokenAt, TokenIterWithContext, Tokens};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
|
||||
use super::{TokenKind, Tokens};
|
||||
use crate::{AnyNodeRef, ExprRef};
|
||||
|
||||
/// Returns an iterator over the ranges of the optional parentheses surrounding an expression.
|
||||
///
|
||||
/// E.g. for `((f()))` with `f()` as expression, the iterator returns the ranges (1, 6) and (0, 7).
|
||||
///
|
||||
/// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of
|
||||
/// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should
|
||||
/// generally prefer [`parenthesized_range`].
|
||||
pub fn parentheses_iterator<'a>(
|
||||
expr: ExprRef<'a>,
|
||||
parent: Option<AnyNodeRef>,
|
||||
tokens: &'a Tokens,
|
||||
) -> impl Iterator<Item = TextRange> + 'a {
|
||||
let after_tokens = if let Some(parent) = parent {
|
||||
// If the parent is a node that brings its own parentheses, exclude the closing parenthesis
|
||||
// from our search range. Otherwise, we risk matching on calls, like `func(x)`, for which
|
||||
// the open and close parentheses are part of the `Arguments` node.
|
||||
let exclusive_parent_end = if parent.is_arguments() {
|
||||
parent.end() - ")".text_len()
|
||||
} else {
|
||||
parent.end()
|
||||
};
|
||||
|
||||
tokens.in_range(TextRange::new(expr.end(), exclusive_parent_end))
|
||||
} else {
|
||||
tokens.after(expr.end())
|
||||
};
|
||||
|
||||
let right_parens = after_tokens
|
||||
.iter()
|
||||
.filter(|token| !token.kind().is_trivia())
|
||||
.take_while(move |token| token.kind() == TokenKind::Rpar);
|
||||
|
||||
let left_parens = tokens
|
||||
.before(expr.start())
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|token| !token.kind().is_trivia())
|
||||
.take_while(|token| token.kind() == TokenKind::Lpar);
|
||||
|
||||
right_parens
|
||||
.zip(left_parens)
|
||||
.map(|(right, left)| TextRange::new(left.start(), right.end()))
|
||||
}
|
||||
|
||||
/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
|
||||
/// parenthesized; or `None`, if the expression is not parenthesized.
|
||||
pub fn parenthesized_range(
|
||||
expr: ExprRef,
|
||||
parent: AnyNodeRef,
|
||||
tokens: &Tokens,
|
||||
) -> Option<TextRange> {
|
||||
parentheses_iterator(expr, Some(parent), tokens).last()
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
//! Tests for [`ruff_python_ast::tokens::parentheses_iterator`] and
|
||||
//! [`ruff_python_ast::tokens::parenthesized_range`].
|
||||
|
||||
use ruff_python_ast::{
|
||||
self as ast, Expr,
|
||||
token::{parentheses_iterator, parenthesized_range},
|
||||
};
|
||||
use ruff_python_parser::parse_module;
|
||||
|
||||
#[test]
|
||||
fn test_no_parentheses() {
|
||||
let source = "x = 2 + 2";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_parentheses() {
|
||||
let source = "x = (2 + 2)";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
|
||||
let range = result.expect("should find parentheses");
|
||||
assert_eq!(&source[range], "(2 + 2)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_double_parentheses() {
|
||||
let source = "x = ((2 + 2))";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
|
||||
let range = result.expect("should find parentheses");
|
||||
assert_eq!(&source[range], "((2 + 2))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parentheses_with_whitespace() {
|
||||
let source = "x = ( 2 + 2 )";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
|
||||
let range = result.expect("should find parentheses");
|
||||
assert_eq!(&source[range], "( 2 + 2 )");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parentheses_with_comments() {
|
||||
let source = "x = ( # comment\n 2 + 2\n)";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
|
||||
let range = result.expect("should find parentheses");
|
||||
assert_eq!(&source[range], "( # comment\n 2 + 2\n)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parenthesized_range_multiple() {
|
||||
let source = "x = (((2 + 2)))";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
|
||||
let range = result.expect("should find parentheses");
|
||||
assert_eq!(&source[range], "(((2 + 2)))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parentheses_iterator_multiple() {
|
||||
let source = "x = (((2 + 2)))";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let ranges: Vec<_> =
|
||||
parentheses_iterator(assign.value.as_ref().into(), Some(stmt.into()), tokens).collect();
|
||||
assert_eq!(ranges.len(), 3);
|
||||
assert_eq!(&source[ranges[0]], "(2 + 2)");
|
||||
assert_eq!(&source[ranges[1]], "((2 + 2))");
|
||||
assert_eq!(&source[ranges[2]], "(((2 + 2)))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_arguments_not_counted() {
|
||||
let source = "f(x)";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Expr(expr_stmt) = stmt else {
|
||||
panic!("expected `Expr` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let Expr::Call(call) = expr_stmt.value.as_ref() else {
|
||||
panic!("expected Call expression, got {:?}", expr_stmt.value);
|
||||
};
|
||||
|
||||
let arg = call
|
||||
.arguments
|
||||
.args
|
||||
.first()
|
||||
.expect("call should have an argument");
|
||||
let result = parenthesized_range(arg.into(), (&call.arguments).into(), tokens);
|
||||
// The parentheses belong to the call, not the argument
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_with_parenthesized_argument() {
|
||||
let source = "f((x))";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Expr(expr_stmt) = stmt else {
|
||||
panic!("expected Expr statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let Expr::Call(call) = expr_stmt.value.as_ref() else {
|
||||
panic!("expected `Call` expression, got {:?}", expr_stmt.value);
|
||||
};
|
||||
|
||||
let arg = call
|
||||
.arguments
|
||||
.args
|
||||
.first()
|
||||
.expect("call should have an argument");
|
||||
let result = parenthesized_range(arg.into(), (&call.arguments).into(), tokens);
|
||||
|
||||
let range = result.expect("should find parentheses around argument");
|
||||
assert_eq!(&source[range], "(x)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiline_with_parentheses() {
|
||||
let source = "x = (\n 2 + 2 + 2\n)";
|
||||
let parsed = parse_module(source).expect("should parse valid python");
|
||||
let tokens = parsed.tokens();
|
||||
let module = parsed.syntax();
|
||||
|
||||
let stmt = module.body.first().expect("module should have a statement");
|
||||
let ast::Stmt::Assign(assign) = stmt else {
|
||||
panic!("expected `Assign` statement, got {stmt:?}");
|
||||
};
|
||||
|
||||
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
|
||||
let range = result.expect("should find parentheses");
|
||||
assert_eq!(&source[range], "(\n 2 + 2 + 2\n)");
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
class C[T = int, U]: ...
|
||||
class C[T1, T2 = int, T3, T4]: ...
|
||||
type Alias[T = int, U] = ...
|
||||
@@ -3,13 +3,12 @@
|
||||
//! This checker is not responsible for traversing the AST itself. Instead, its
|
||||
//! [`SemanticSyntaxChecker::visit_stmt`] and [`SemanticSyntaxChecker::visit_expr`] methods should
|
||||
//! be called in a parent `Visitor`'s `visit_stmt` and `visit_expr` methods, respectively.
|
||||
|
||||
use ruff_python_ast::{
|
||||
self as ast, Expr, ExprContext, IrrefutablePatternKind, Pattern, PythonVersion, Stmt, StmtExpr,
|
||||
StmtFunctionDef, StmtImportFrom,
|
||||
StmtImportFrom,
|
||||
comparable::ComparableExpr,
|
||||
helpers,
|
||||
visitor::{Visitor, walk_expr, walk_stmt},
|
||||
visitor::{Visitor, walk_expr},
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
@@ -145,16 +144,11 @@ impl SemanticSyntaxChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::ClassDef(ast::StmtClassDef {
|
||||
type_params: Some(type_params),
|
||||
..
|
||||
})
|
||||
| Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
type_params: Some(type_params),
|
||||
..
|
||||
}) => {
|
||||
Self::duplicate_type_parameter_name(type_params, ctx);
|
||||
Self::type_parameter_default_order(type_params, ctx);
|
||||
Stmt::ClassDef(ast::StmtClassDef { type_params, .. })
|
||||
| Stmt::TypeAlias(ast::StmtTypeAlias { type_params, .. }) => {
|
||||
if let Some(type_params) = type_params {
|
||||
Self::duplicate_type_parameter_name(type_params, ctx);
|
||||
}
|
||||
}
|
||||
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
|
||||
if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() {
|
||||
@@ -617,39 +611,6 @@ impl SemanticSyntaxChecker {
|
||||
}
|
||||
}
|
||||
|
||||
fn type_parameter_default_order<Ctx: SemanticSyntaxContext>(
|
||||
type_params: &ast::TypeParams,
|
||||
ctx: &Ctx,
|
||||
) {
|
||||
let mut seen_default = false;
|
||||
for type_param in type_params.iter() {
|
||||
let has_default = match type_param {
|
||||
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { default, .. })
|
||||
| ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { default, .. })
|
||||
| ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { default, .. }) => {
|
||||
default.is_some()
|
||||
}
|
||||
};
|
||||
|
||||
if seen_default && !has_default {
|
||||
// test_err type_parameter_default_order
|
||||
// class C[T = int, U]: ...
|
||||
// class C[T1, T2 = int, T3, T4]: ...
|
||||
// type Alias[T = int, U] = ...
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::TypeParameterDefaultOrder(
|
||||
type_param.name().id.to_string(),
|
||||
),
|
||||
type_param.range(),
|
||||
);
|
||||
}
|
||||
if has_default {
|
||||
seen_default = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn duplicate_parameter_name<Ctx: SemanticSyntaxContext>(
|
||||
parameters: &ast::Parameters,
|
||||
ctx: &Ctx,
|
||||
@@ -740,21 +701,7 @@ impl SemanticSyntaxChecker {
|
||||
self.seen_futures_boundary = true;
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(StmtFunctionDef { is_async, body, .. }) => {
|
||||
if *is_async {
|
||||
let mut visitor = ReturnVisitor::default();
|
||||
visitor.visit_body(body);
|
||||
|
||||
if visitor.has_yield {
|
||||
if let Some(return_range) = visitor.return_range {
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::ReturnInGenerator,
|
||||
return_range,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(_) => {
|
||||
self.seen_futures_boundary = true;
|
||||
}
|
||||
_ => {
|
||||
@@ -949,7 +896,7 @@ impl SemanticSyntaxChecker {
|
||||
// This check is required in addition to avoiding calling this function in `visit_expr`
|
||||
// because the generator scope applies to nested parts of the `Expr::Generator` that are
|
||||
// visited separately.
|
||||
if ctx.in_generator_context() {
|
||||
if ctx.in_generator_scope() {
|
||||
return;
|
||||
}
|
||||
Self::add_error(
|
||||
@@ -1119,12 +1066,6 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::DuplicateTypeParameter => {
|
||||
f.write_str("duplicate type parameter")
|
||||
}
|
||||
SemanticSyntaxErrorKind::TypeParameterDefaultOrder(name) => {
|
||||
write!(
|
||||
f,
|
||||
"non default type parameter `{name}` follows default type parameter"
|
||||
)
|
||||
}
|
||||
SemanticSyntaxErrorKind::MultipleCaseAssignment(name) => {
|
||||
write!(f, "multiple assignments to name `{name}` in pattern")
|
||||
}
|
||||
@@ -1228,9 +1169,6 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::NonlocalWithoutBinding(name) => {
|
||||
write!(f, "no binding for nonlocal `{name}` found")
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReturnInGenerator => {
|
||||
write!(f, "`return` with value in async generator")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1634,12 +1572,6 @@ pub enum SemanticSyntaxErrorKind {
|
||||
|
||||
/// Represents a nonlocal statement for a name that has no binding in an enclosing scope.
|
||||
NonlocalWithoutBinding(String),
|
||||
|
||||
/// Represents a default type parameter followed by a non-default type parameter.
|
||||
TypeParameterDefaultOrder(String),
|
||||
|
||||
/// Represents a `return` statement with a value in an asynchronous generator.
|
||||
ReturnInGenerator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
@@ -1756,40 +1688,6 @@ impl Visitor<'_> for ReboundComprehensionVisitor<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ReturnVisitor {
|
||||
return_range: Option<TextRange>,
|
||||
has_yield: bool,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for ReturnVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
// Do not recurse into nested functions; they're evaluated separately.
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {}
|
||||
Stmt::Return(ast::StmtReturn {
|
||||
value: Some(_),
|
||||
range,
|
||||
..
|
||||
}) => {
|
||||
self.return_range = Some(*range);
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
_ => walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
match expr {
|
||||
Expr::Lambda(_) => {}
|
||||
Expr::Yield(_) | Expr::YieldFrom(_) => {
|
||||
self.has_yield = true;
|
||||
}
|
||||
_ => walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MatchPatternVisitor<'a, Ctx> {
|
||||
names: FxHashSet<&'a ast::name::Name>,
|
||||
ctx: &'a Ctx,
|
||||
@@ -2198,11 +2096,11 @@ pub trait SemanticSyntaxContext {
|
||||
/// Returns `true` if the visitor is in a function scope.
|
||||
fn in_function_scope(&self) -> bool;
|
||||
|
||||
/// Returns `true` if the visitor is within a generator scope.
|
||||
/// Returns `true` if the visitor is in a generator scope.
|
||||
///
|
||||
/// Note that this refers to an `Expr::Generator` precisely, not to comprehensions more
|
||||
/// generally.
|
||||
fn in_generator_context(&self) -> bool;
|
||||
fn in_generator_scope(&self) -> bool;
|
||||
|
||||
/// Returns `true` if the source file is a Jupyter notebook.
|
||||
fn in_notebook(&self) -> bool;
|
||||
|
||||
@@ -573,7 +573,7 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
|
||||
true
|
||||
}
|
||||
|
||||
fn in_generator_context(&self) -> bool {
|
||||
fn in_generator_scope(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -375,12 +375,3 @@ Module(
|
||||
4 | type X[**P = x := int] = int
|
||||
5 | type X[**P = *int] = int
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
2 | type X[**P = yield x] = int
|
||||
3 | type X[**P = yield from x] = int
|
||||
4 | type X[**P = x := int] = int
|
||||
| ^^^ Syntax Error: non default type parameter `int` follows default type parameter
|
||||
5 | type X[**P = *int] = int
|
||||
|
|
||||
|
||||
@@ -459,12 +459,3 @@ Module(
|
||||
5 | type X[T = x := int] = int
|
||||
6 | type X[T: int = *int] = int
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
3 | type X[T = (yield x)] = int
|
||||
4 | type X[T = yield from x] = int
|
||||
5 | type X[T = x := int] = int
|
||||
| ^^^ Syntax Error: non default type parameter `int` follows default type parameter
|
||||
6 | type X[T: int = *int] = int
|
||||
|
|
||||
|
||||
@@ -384,11 +384,3 @@ Module(
|
||||
| ^^^^^^^^^^^^ Syntax Error: yield expression cannot be used within a TypeVarTuple default
|
||||
5 | type X[*Ts = x := int] = int
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
3 | type X[*Ts = yield x] = int
|
||||
4 | type X[*Ts = yield from x] = int
|
||||
5 | type X[*Ts = x := int] = int
|
||||
| ^^^ Syntax Error: non default type parameter `int` follows default type parameter
|
||||
|
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/type_parameter_default_order.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..89,
|
||||
body: [
|
||||
ClassDef(
|
||||
StmtClassDef {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..24,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("C"),
|
||||
range: 6..7,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
type_params: Some(
|
||||
TypeParams {
|
||||
range: 7..19,
|
||||
node_index: NodeIndex(None),
|
||||
type_params: [
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 8..15,
|
||||
name: Identifier {
|
||||
id: Name("T"),
|
||||
range: 8..9,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: Some(
|
||||
Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 12..15,
|
||||
id: Name("int"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 17..18,
|
||||
name: Identifier {
|
||||
id: Name("U"),
|
||||
range: 17..18,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: None,
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
arguments: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 21..24,
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 21..24,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
ClassDef(
|
||||
StmtClassDef {
|
||||
node_index: NodeIndex(None),
|
||||
range: 25..59,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("C"),
|
||||
range: 31..32,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
type_params: Some(
|
||||
TypeParams {
|
||||
range: 32..54,
|
||||
node_index: NodeIndex(None),
|
||||
type_params: [
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 33..35,
|
||||
name: Identifier {
|
||||
id: Name("T1"),
|
||||
range: 33..35,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: None,
|
||||
},
|
||||
),
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 37..45,
|
||||
name: Identifier {
|
||||
id: Name("T2"),
|
||||
range: 37..39,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: Some(
|
||||
Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 42..45,
|
||||
id: Name("int"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 47..49,
|
||||
name: Identifier {
|
||||
id: Name("T3"),
|
||||
range: 47..49,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: None,
|
||||
},
|
||||
),
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 51..53,
|
||||
name: Identifier {
|
||||
id: Name("T4"),
|
||||
range: 51..53,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: None,
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
arguments: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 56..59,
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 56..59,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
TypeAlias(
|
||||
StmtTypeAlias {
|
||||
node_index: NodeIndex(None),
|
||||
range: 60..88,
|
||||
name: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 65..70,
|
||||
id: Name("Alias"),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
type_params: Some(
|
||||
TypeParams {
|
||||
range: 70..82,
|
||||
node_index: NodeIndex(None),
|
||||
type_params: [
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 71..78,
|
||||
name: Identifier {
|
||||
id: Name("T"),
|
||||
range: 71..72,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: Some(
|
||||
Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 75..78,
|
||||
id: Name("int"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
TypeVar(
|
||||
TypeParamTypeVar {
|
||||
node_index: NodeIndex(None),
|
||||
range: 80..81,
|
||||
name: Identifier {
|
||||
id: Name("U"),
|
||||
range: 80..81,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
bound: None,
|
||||
default: None,
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
value: EllipsisLiteral(
|
||||
ExprEllipsisLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 85..88,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Semantic Syntax Errors
|
||||
|
||||
|
|
||||
1 | class C[T = int, U]: ...
|
||||
| ^ Syntax Error: non default type parameter `U` follows default type parameter
|
||||
2 | class C[T1, T2 = int, T3, T4]: ...
|
||||
3 | type Alias[T = int, U] = ...
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | class C[T = int, U]: ...
|
||||
2 | class C[T1, T2 = int, T3, T4]: ...
|
||||
| ^^ Syntax Error: non default type parameter `T3` follows default type parameter
|
||||
3 | type Alias[T = int, U] = ...
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | class C[T = int, U]: ...
|
||||
2 | class C[T1, T2 = int, T3, T4]: ...
|
||||
| ^^ Syntax Error: non default type parameter `T4` follows default type parameter
|
||||
3 | type Alias[T = int, U] = ...
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | class C[T = int, U]: ...
|
||||
2 | class C[T1, T2 = int, T3, T4]: ...
|
||||
3 | type Alias[T = int, U] = ...
|
||||
| ^ Syntax Error: non default type parameter `U` follows default type parameter
|
||||
|
|
||||
@@ -33,29 +33,26 @@ impl LineIndex {
|
||||
line_starts.push(TextSize::default());
|
||||
|
||||
let bytes = text.as_bytes();
|
||||
let mut utf8 = false;
|
||||
|
||||
assert!(u32::try_from(bytes.len()).is_ok());
|
||||
|
||||
for i in memchr::memchr2_iter(b'\n', b'\r', bytes) {
|
||||
// Skip `\r` in `\r\n` sequences (only count the `\n`).
|
||||
if bytes[i] == b'\r' && bytes.get(i + 1) == Some(&b'\n') {
|
||||
continue;
|
||||
for (i, byte) in bytes.iter().enumerate() {
|
||||
utf8 |= !byte.is_ascii();
|
||||
|
||||
match byte {
|
||||
// Only track one line break for `\r\n`.
|
||||
b'\r' if bytes.get(i + 1) == Some(&b'\n') => continue,
|
||||
b'\n' | b'\r' => {
|
||||
// SAFETY: Assertion above guarantees `i <= u32::MAX`
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
line_starts.push(TextSize::from(i as u32) + TextSize::from(1));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// SAFETY: Assertion above guarantees `i <= u32::MAX`
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
line_starts.push(TextSize::from(i as u32) + TextSize::from(1));
|
||||
}
|
||||
|
||||
// Determine whether the source text is ASCII.
|
||||
//
|
||||
// Empirically, this simple loop is auto-vectorized by LLVM and benchmarks faster than both
|
||||
// `str::is_ascii()` and hand-written SIMD.
|
||||
let mut has_non_ascii = false;
|
||||
for byte in bytes {
|
||||
has_non_ascii |= !byte.is_ascii();
|
||||
}
|
||||
|
||||
let kind = if has_non_ascii {
|
||||
let kind = if utf8 {
|
||||
IndexKind::Utf8
|
||||
} else {
|
||||
IndexKind::Ascii
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.8"
|
||||
version = "0.14.7"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
148
crates/ty/docs/rules.md
generated
148
crates/ty/docs/rules.md
generated
@@ -39,7 +39,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L179" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L181" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ f(int) # error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L205" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L207" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ a = 1
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L230" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L232" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class B(A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L282" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L284" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ type B = A
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L345" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L366" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L590" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -387,7 +387,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L614" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L616" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L396" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L398" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -502,7 +502,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L668" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L670" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L708" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L710" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -557,7 +557,7 @@ a: int = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1998" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2000" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -591,7 +591,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L730" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L732" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -627,7 +627,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L760" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L762" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -651,7 +651,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L813" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -678,7 +678,7 @@ with 1:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L832" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L834" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -707,7 +707,7 @@ a: str
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L855" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L857" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -751,7 +751,7 @@ except ZeroDivisionError:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1668" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1670" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -793,7 +793,7 @@ class D(A):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L891" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L893" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -826,7 +826,7 @@ class C[U](Generic[T]): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L635" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L637" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -865,7 +865,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L917" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L919" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -900,7 +900,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1014" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1016" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -934,7 +934,7 @@ class B(metaclass=f): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2126" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2128" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1041,7 +1041,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L544" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1095,7 +1095,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L990" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L992" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1125,7 +1125,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1041" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1175,7 +1175,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1140" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1142" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1201,7 +1201,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L947" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1232,7 +1232,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L478" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L480" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1266,7 +1266,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1160" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1162" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1315,7 +1315,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L689" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L691" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1340,7 +1340,7 @@ def func() -> int:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1203" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1205" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1398,7 +1398,7 @@ TODO #14889
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L969" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L971" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1425,7 +1425,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1435" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1437" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1472,7 +1472,7 @@ Bar[int] # error: too few arguments
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1242" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1244" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1502,7 +1502,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1266" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1268" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1532,7 +1532,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1318" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1320" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1566,7 +1566,7 @@ f(10) # Error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1600,7 +1600,7 @@ class C:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1346" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1348" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1635,7 +1635,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1375" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1377" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1660,7 +1660,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2099" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2101" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1693,7 +1693,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1722,7 +1722,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1417" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1419" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1746,7 +1746,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1476" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1478" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1772,7 +1772,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1641" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1643" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1805,7 +1805,7 @@ class B(A):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1527" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1529" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1832,7 +1832,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1852" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1854" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1890,7 +1890,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1974" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1976" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1920,7 +1920,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1618" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1620" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1949,7 +1949,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1786" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1788" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1983,7 +1983,7 @@ class F(NamedTuple):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1726" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1728" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2010,7 +2010,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1704" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1706" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2038,7 +2038,7 @@ def _(x: int):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1747" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2084,7 +2084,7 @@ class A:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1831" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1833" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2111,7 +2111,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1873" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1875" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2139,7 +2139,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1895" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1897" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2164,7 +2164,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1914" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1916" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2189,7 +2189,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1496" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1498" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2226,7 +2226,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1933" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1935" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2254,7 +2254,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1955" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1957" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2279,7 +2279,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L507" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L509" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2320,7 +2320,7 @@ class SubProto(BaseProto, Protocol):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L322" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L324" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2408,7 +2408,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1548" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1550" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2436,7 +2436,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L153" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L155" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2468,7 +2468,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1570" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1572" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2500,7 +2500,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2026" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2028" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2527,7 +2527,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1813" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1815" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2551,7 +2551,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2047" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2049" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2609,7 +2609,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L778" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L780" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2648,7 +2648,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1084" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2711,7 +2711,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L304" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L306" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2735,7 +2735,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1596" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1598" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -90,22 +90,6 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
})?
|
||||
};
|
||||
|
||||
let system = OsSystem::new(&cwd);
|
||||
|
||||
// If we see a single path, check if it's a PEP-723 script
|
||||
let mut script_project = None;
|
||||
if let [path] = &*args.paths {
|
||||
match ProjectMetadata::discover_script(path, &system) {
|
||||
Ok(project) => {
|
||||
script_project = Some(project);
|
||||
}
|
||||
Err(ty_project::ProjectMetadataError::NotAScript(_)) => {
|
||||
// This is fine
|
||||
}
|
||||
Err(e) => tracing::info!("Issue reading script at `{path}`: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
let project_path = args
|
||||
.project
|
||||
.as_ref()
|
||||
@@ -127,6 +111,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
.map(|path| SystemPath::absolute(path, &cwd))
|
||||
.collect();
|
||||
|
||||
let system = OsSystem::new(&cwd);
|
||||
let watch = args.watch;
|
||||
let exit_zero = args.exit_zero;
|
||||
let config_file = args
|
||||
@@ -136,13 +121,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
|
||||
let mut project_metadata = match &config_file {
|
||||
Some(config_file) => ProjectMetadata::from_config_file(config_file.clone(), &system)?,
|
||||
None => {
|
||||
if let Some(project) = script_project {
|
||||
project
|
||||
} else {
|
||||
ProjectMetadata::discover(&project_path, &system)?
|
||||
}
|
||||
}
|
||||
None => ProjectMetadata::discover(&project_path, &system)?,
|
||||
};
|
||||
|
||||
project_metadata.apply_configuration_files(&system)?;
|
||||
|
||||
@@ -43,7 +43,7 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
3 | python-version = "3.11"
|
||||
| ^^^^^^ Python version configuration
|
||||
| ^^^^^^ Python 3.11 assumed due to this configuration setting
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
@@ -143,7 +143,7 @@ fn config_file_annotation_showing_where_python_version_set_typing_error() -> any
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -159,14 +159,14 @@ fn config_file_annotation_showing_where_python_version_set_typing_error() -> any
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
3 | python-version = "3.8"
|
||||
| ^^^^^ Python version configuration
|
||||
| ^^^^^ Python 3.8 assumed due to this configuration setting
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
"###);
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r###"
|
||||
success: false
|
||||
@@ -772,7 +772,7 @@ fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Resu
|
||||
("test.py", "aiter"),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -787,7 +787,7 @@ fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Resu
|
||||
--> venv/pyvenv.cfg:2:11
|
||||
|
|
||||
2 | version = 3.8
|
||||
| ^^^ Virtual environment metadata
|
||||
| ^^^ Python version inferred from virtual environment metadata file
|
||||
3 | home = foo/bar/bin
|
||||
|
|
||||
info: No Python version was specified on the command line or in a configuration file
|
||||
@@ -796,7 +796,7 @@ fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Resu
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -831,7 +831,7 @@ fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> {
|
||||
("test.py", "aiter"),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -846,7 +846,7 @@ fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> {
|
||||
--> venv/pyvenv.cfg:4:23
|
||||
|
|
||||
4 | version = 3.8
|
||||
| ^^^ Virtual environment metadata
|
||||
| ^^^ Python version inferred from virtual environment metadata file
|
||||
|
|
||||
info: No Python version was specified on the command line or in a configuration file
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
@@ -854,7 +854,7 @@ fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> {
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -898,7 +898,7 @@ fn config_file_annotation_showing_where_python_version_set_syntax_error() -> any
|
||||
|
|
||||
2 | [project]
|
||||
3 | requires-python = ">=3.8"
|
||||
| ^^^^^^^ Python version configuration
|
||||
| ^^^^^^^ Python 3.8 assumed due to this configuration setting
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -1206,7 +1206,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | [environment]
|
||||
3 | python-version = "3.10"
|
||||
| ^^^^^^ Python version configuration
|
||||
| ^^^^^^ Python 3.10 assumed due to this configuration setting
|
||||
4 | python-platform = "linux"
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
@@ -1225,7 +1225,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | [environment]
|
||||
3 | python-version = "3.10"
|
||||
| ^^^^^^ Python version configuration
|
||||
| ^^^^^^ Python 3.10 assumed due to this configuration setting
|
||||
4 | python-platform = "linux"
|
||||
|
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -15,7 +15,7 @@ use ty_project::metadata::pyproject::{PyProject, Tool};
|
||||
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||
use ty_project::watch::{ChangeEvent, ProjectWatcher, directory_watcher};
|
||||
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||
use ty_python_semantic::{Module, ModuleName, PythonPlatform, resolve_module_confident};
|
||||
use ty_python_semantic::{Module, ModuleName, PythonPlatform, resolve_module};
|
||||
|
||||
struct TestCase {
|
||||
db: ProjectDatabase,
|
||||
@@ -232,8 +232,7 @@ impl TestCase {
|
||||
}
|
||||
|
||||
fn module<'c>(&'c self, name: &str) -> Module<'c> {
|
||||
resolve_module_confident(self.db(), &ModuleName::new(name).unwrap())
|
||||
.expect("module to be present")
|
||||
resolve_module(self.db(), &ModuleName::new(name).unwrap()).expect("module to be present")
|
||||
}
|
||||
|
||||
fn sorted_submodule_names(&self, parent_module_name: &str) -> Vec<String> {
|
||||
@@ -812,8 +811,7 @@ fn directory_moved_to_project() -> anyhow::Result<()> {
|
||||
.with_context(|| "Failed to create __init__.py")?;
|
||||
std::fs::write(a_original_path.as_std_path(), "").with_context(|| "Failed to create a.py")?;
|
||||
|
||||
let sub_a_module =
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap());
|
||||
let sub_a_module = resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap());
|
||||
|
||||
assert_eq!(sub_a_module, None);
|
||||
case.assert_indexed_project_files([bar]);
|
||||
@@ -834,9 +832,7 @@ fn directory_moved_to_project() -> anyhow::Result<()> {
|
||||
.expect("a.py to exist");
|
||||
|
||||
// `import sub.a` should now resolve
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some());
|
||||
|
||||
case.assert_indexed_project_files([bar, init_file, a_file]);
|
||||
|
||||
@@ -852,9 +848,7 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
|
||||
])?;
|
||||
let bar = case.system_file(case.project_path("bar.py")).unwrap();
|
||||
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some());
|
||||
|
||||
let sub_path = case.project_path("sub");
|
||||
let init_file = case
|
||||
@@ -876,9 +870,7 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
|
||||
case.apply_changes(changes, None);
|
||||
|
||||
// `import sub.a` should no longer resolve
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_none()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_none());
|
||||
|
||||
assert!(!init_file.exists(case.db()));
|
||||
assert!(!a_file.exists(case.db()));
|
||||
@@ -898,12 +890,8 @@ fn directory_renamed() -> anyhow::Result<()> {
|
||||
|
||||
let bar = case.system_file(case.project_path("bar.py")).unwrap();
|
||||
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some()
|
||||
);
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("foo.baz").unwrap()).is_none()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some());
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("foo.baz").unwrap()).is_none());
|
||||
|
||||
let sub_path = case.project_path("sub");
|
||||
let sub_init = case
|
||||
@@ -927,13 +915,9 @@ fn directory_renamed() -> anyhow::Result<()> {
|
||||
case.apply_changes(changes, None);
|
||||
|
||||
// `import sub.a` should no longer resolve
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_none()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_none());
|
||||
// `import foo.baz` should now resolve
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("foo.baz").unwrap()).is_some()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("foo.baz").unwrap()).is_some());
|
||||
|
||||
// The old paths are no longer tracked
|
||||
assert!(!sub_init.exists(case.db()));
|
||||
@@ -966,9 +950,7 @@ fn directory_deleted() -> anyhow::Result<()> {
|
||||
|
||||
let bar = case.system_file(case.project_path("bar.py")).unwrap();
|
||||
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_some());
|
||||
|
||||
let sub_path = case.project_path("sub");
|
||||
|
||||
@@ -988,9 +970,7 @@ fn directory_deleted() -> anyhow::Result<()> {
|
||||
case.apply_changes(changes, None);
|
||||
|
||||
// `import sub.a` should no longer resolve
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_none()
|
||||
);
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("sub.a").unwrap()).is_none());
|
||||
|
||||
assert!(!init_file.exists(case.db()));
|
||||
assert!(!a_file.exists(case.db()));
|
||||
@@ -1019,7 +999,7 @@ fn search_path() -> anyhow::Result<()> {
|
||||
let site_packages = case.root_path().join("site_packages");
|
||||
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("a").unwrap()),
|
||||
resolve_module(case.db(), &ModuleName::new("a").unwrap()),
|
||||
None
|
||||
);
|
||||
|
||||
@@ -1029,7 +1009,7 @@ fn search_path() -> anyhow::Result<()> {
|
||||
|
||||
case.apply_changes(changes, None);
|
||||
|
||||
assert!(resolve_module_confident(case.db(), &ModuleName::new_static("a").unwrap()).is_some());
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("a").unwrap()).is_some());
|
||||
case.assert_indexed_project_files([case.system_file(case.project_path("bar.py")).unwrap()]);
|
||||
|
||||
Ok(())
|
||||
@@ -1042,7 +1022,7 @@ fn add_search_path() -> anyhow::Result<()> {
|
||||
let site_packages = case.project_path("site_packages");
|
||||
std::fs::create_dir_all(site_packages.as_std_path())?;
|
||||
|
||||
assert!(resolve_module_confident(case.db(), &ModuleName::new_static("a").unwrap()).is_none());
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("a").unwrap()).is_none());
|
||||
|
||||
// Register site-packages as a search path.
|
||||
case.update_options(Options {
|
||||
@@ -1060,7 +1040,7 @@ fn add_search_path() -> anyhow::Result<()> {
|
||||
|
||||
case.apply_changes(changes, None);
|
||||
|
||||
assert!(resolve_module_confident(case.db(), &ModuleName::new_static("a").unwrap()).is_some());
|
||||
assert!(resolve_module(case.db(), &ModuleName::new_static("a").unwrap()).is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1192,7 +1172,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
|
||||
|
||||
// Unset the custom typeshed directory.
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("os").unwrap()),
|
||||
resolve_module(case.db(), &ModuleName::new("os").unwrap()),
|
||||
None
|
||||
);
|
||||
|
||||
@@ -1207,7 +1187,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
|
||||
|
||||
case.apply_changes(changes, None);
|
||||
|
||||
assert!(resolve_module_confident(case.db(), &ModuleName::new("os").unwrap()).is_some());
|
||||
assert!(resolve_module(case.db(), &ModuleName::new("os").unwrap()).is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1430,7 +1410,7 @@ mod unix {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let baz = resolve_module_confident(case.db(), &ModuleName::new_static("bar.baz").unwrap())
|
||||
let baz = resolve_module(case.db(), &ModuleName::new_static("bar.baz").unwrap())
|
||||
.expect("Expected bar.baz to exist in site-packages.");
|
||||
let baz_project = case.project_path("bar/baz.py");
|
||||
let baz_file = baz.file(case.db()).unwrap();
|
||||
@@ -1506,7 +1486,7 @@ mod unix {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let baz = resolve_module_confident(case.db(), &ModuleName::new_static("bar.baz").unwrap())
|
||||
let baz = resolve_module(case.db(), &ModuleName::new_static("bar.baz").unwrap())
|
||||
.expect("Expected bar.baz to exist in site-packages.");
|
||||
let baz_file = baz.file(case.db()).unwrap();
|
||||
let bar_baz = case.project_path("bar/baz.py");
|
||||
@@ -1611,7 +1591,7 @@ mod unix {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let baz = resolve_module_confident(case.db(), &ModuleName::new_static("bar.baz").unwrap())
|
||||
let baz = resolve_module(case.db(), &ModuleName::new_static("bar.baz").unwrap())
|
||||
.expect("Expected bar.baz to exist in site-packages.");
|
||||
let baz_site_packages_path =
|
||||
case.project_path(".venv/lib/python3.12/site-packages/bar/baz.py");
|
||||
@@ -1874,11 +1854,11 @@ fn rename_files_casing_only() -> anyhow::Result<()> {
|
||||
let mut case = setup([("lib.py", "class Foo: ...")])?;
|
||||
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("lib").unwrap()).is_some(),
|
||||
resolve_module(case.db(), &ModuleName::new("lib").unwrap()).is_some(),
|
||||
"Expected `lib` module to exist."
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("Lib").unwrap()),
|
||||
resolve_module(case.db(), &ModuleName::new("Lib").unwrap()),
|
||||
None,
|
||||
"Expected `Lib` module not to exist"
|
||||
);
|
||||
@@ -1911,13 +1891,13 @@ fn rename_files_casing_only() -> anyhow::Result<()> {
|
||||
|
||||
// Resolving `lib` should now fail but `Lib` should now succeed
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("lib").unwrap()),
|
||||
resolve_module(case.db(), &ModuleName::new("lib").unwrap()),
|
||||
None,
|
||||
"Expected `lib` module to no longer exist."
|
||||
);
|
||||
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("Lib").unwrap()).is_some(),
|
||||
resolve_module(case.db(), &ModuleName::new("Lib").unwrap()).is_some(),
|
||||
"Expected `Lib` module to exist"
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
name,file,index,rank
|
||||
auto-import-includes-modules,main.py,0,1
|
||||
auto-import-includes-modules,main.py,1,7
|
||||
auto-import-includes-modules,main.py,2,1
|
||||
auto-import-skips-current-module,main.py,0,1
|
||||
fstring-completions,main.py,0,1
|
||||
higher-level-symbols-preferred,main.py,0,
|
||||
@@ -14,9 +11,9 @@ import-deprioritizes-type_check_only,main.py,2,1
|
||||
import-deprioritizes-type_check_only,main.py,3,2
|
||||
import-deprioritizes-type_check_only,main.py,4,3
|
||||
import-keyword-completion,main.py,0,1
|
||||
internal-typeshed-hidden,main.py,0,2
|
||||
internal-typeshed-hidden,main.py,0,4
|
||||
none-completion,main.py,0,2
|
||||
numpy-array,main.py,0,159
|
||||
numpy-array,main.py,0,
|
||||
numpy-array,main.py,1,1
|
||||
object-attr-instance-methods,main.py,0,1
|
||||
object-attr-instance-methods,main.py,1,1
|
||||
@@ -26,6 +23,6 @@ scope-existing-over-new-import,main.py,0,1
|
||||
scope-prioritize-closer,main.py,0,2
|
||||
scope-simple-long-identifier,main.py,0,1
|
||||
tstring-completions,main.py,0,1
|
||||
ty-extensions-lower-stdlib,main.py,0,9
|
||||
ty-extensions-lower-stdlib,main.py,0,8
|
||||
type-var-typing-over-ast,main.py,0,3
|
||||
type-var-typing-over-ast,main.py,1,251
|
||||
type-var-typing-over-ast,main.py,1,275
|
||||
|
||||
|
@@ -506,21 +506,9 @@ struct CompletionAnswer {
|
||||
impl CompletionAnswer {
|
||||
/// Returns true when this answer matches the completion given.
|
||||
fn matches(&self, completion: &Completion) -> bool {
|
||||
if let Some(ref qualified) = completion.qualified {
|
||||
if qualified.as_str() == self.qualified() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self.symbol == completion.name.as_str()
|
||||
&& self.module.as_deref() == completion.module_name.map(ModuleName::as_str)
|
||||
}
|
||||
|
||||
fn qualified(&self) -> String {
|
||||
self.module
|
||||
.as_ref()
|
||||
.map(|module| format!("{module}.{}", self.symbol))
|
||||
.unwrap_or_else(|| self.symbol.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy the Python project from `src_dir` to `dst_dir`.
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[settings]
|
||||
auto-import = true
|
||||
@@ -1,3 +0,0 @@
|
||||
multiprocess<CURSOR: multiprocessing>
|
||||
collect<CURSOR: collections>
|
||||
collabc<CURSOR: collections.abc>
|
||||
@@ -1,5 +0,0 @@
|
||||
[project]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
@@ -1,8 +0,0 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "test"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
@@ -2,10 +2,7 @@ use ruff_db::files::File;
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::{Module, ModuleName, all_modules, resolve_real_shadowable_module};
|
||||
|
||||
use crate::{
|
||||
SymbolKind,
|
||||
symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only},
|
||||
};
|
||||
use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only};
|
||||
|
||||
/// Get all symbols matching the query string.
|
||||
///
|
||||
@@ -23,7 +20,7 @@ pub fn all_symbols<'db>(
|
||||
|
||||
let typing_extensions = ModuleName::new("typing_extensions").unwrap();
|
||||
let is_typing_extensions_available = importing_from.is_stub(db)
|
||||
|| resolve_real_shadowable_module(db, importing_from, &typing_extensions).is_some();
|
||||
|| resolve_real_shadowable_module(db, &typing_extensions).is_some();
|
||||
|
||||
let results = std::sync::Mutex::new(Vec::new());
|
||||
{
|
||||
@@ -39,39 +36,18 @@ pub fn all_symbols<'db>(
|
||||
let Some(file) = module.file(&*db) else {
|
||||
continue;
|
||||
};
|
||||
// By convention, modules starting with an underscore
|
||||
// are generally considered unexported. However, we
|
||||
// should consider first party modules fair game.
|
||||
//
|
||||
// Note that we apply this recursively. e.g.,
|
||||
// `numpy._core.multiarray` is considered private
|
||||
// because it's a child of `_core`.
|
||||
if module.name(&*db).components().any(|c| c.starts_with('_'))
|
||||
&& module
|
||||
.search_path(&*db)
|
||||
.is_none_or(|sp| !sp.is_first_party())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// TODO: also make it available in `TYPE_CHECKING` blocks
|
||||
// (we'd need https://github.com/astral-sh/ty/issues/1553 to do this well)
|
||||
if !is_typing_extensions_available && module.name(&*db) == &typing_extensions {
|
||||
continue;
|
||||
}
|
||||
s.spawn(move |_| {
|
||||
if query.is_match_symbol_name(module.name(&*db)) {
|
||||
results.lock().unwrap().push(AllSymbolInfo {
|
||||
symbol: None,
|
||||
module,
|
||||
file,
|
||||
});
|
||||
}
|
||||
for (_, symbol) in symbols_for_file_global_only(&*db, file).search(query) {
|
||||
// It seems like we could do better here than
|
||||
// locking `results` for every single symbol,
|
||||
// but this works pretty well as it is.
|
||||
results.lock().unwrap().push(AllSymbolInfo {
|
||||
symbol: Some(symbol.to_owned()),
|
||||
symbol: symbol.to_owned(),
|
||||
module,
|
||||
file,
|
||||
});
|
||||
@@ -83,16 +59,8 @@ pub fn all_symbols<'db>(
|
||||
|
||||
let mut results = results.into_inner().unwrap();
|
||||
results.sort_by(|s1, s2| {
|
||||
let key1 = (
|
||||
s1.name_in_file()
|
||||
.unwrap_or_else(|| s1.module().name(db).as_str()),
|
||||
s1.file.path(db).as_str(),
|
||||
);
|
||||
let key2 = (
|
||||
s2.name_in_file()
|
||||
.unwrap_or_else(|| s2.module().name(db).as_str()),
|
||||
s2.file.path(db).as_str(),
|
||||
);
|
||||
let key1 = (&s1.symbol.name, s1.file.path(db).as_str());
|
||||
let key2 = (&s2.symbol.name, s2.file.path(db).as_str());
|
||||
key1.cmp(&key2)
|
||||
});
|
||||
results
|
||||
@@ -103,53 +71,14 @@ pub fn all_symbols<'db>(
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AllSymbolInfo<'db> {
|
||||
/// The symbol information.
|
||||
///
|
||||
/// When absent, this implies the symbol is the module itself.
|
||||
symbol: Option<SymbolInfo<'static>>,
|
||||
pub symbol: SymbolInfo<'static>,
|
||||
/// The module containing the symbol.
|
||||
module: Module<'db>,
|
||||
pub module: Module<'db>,
|
||||
/// The file containing the symbol.
|
||||
///
|
||||
/// This `File` is guaranteed to be the same
|
||||
/// as the `File` underlying `module`.
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl<'db> AllSymbolInfo<'db> {
|
||||
/// Returns the name of this symbol as it exists in a file.
|
||||
///
|
||||
/// When absent, there is no concrete symbol in a module
|
||||
/// somewhere. Instead, this represents importing a module.
|
||||
/// In this case, if the caller needs a symbol name, they
|
||||
/// should use `AllSymbolInfo::module().name()`.
|
||||
pub fn name_in_file(&self) -> Option<&str> {
|
||||
self.symbol.as_ref().map(|symbol| &*symbol.name)
|
||||
}
|
||||
|
||||
/// Returns the "kind" of this symbol.
|
||||
///
|
||||
/// The kind of a symbol in the context of auto-import is
|
||||
/// determined on a best effort basis. It may be imprecise
|
||||
/// in some cases, e.g., reporting a module as a variable.
|
||||
pub fn kind(&self) -> SymbolKind {
|
||||
self.symbol
|
||||
.as_ref()
|
||||
.map(|symbol| symbol.kind)
|
||||
.unwrap_or(SymbolKind::Module)
|
||||
}
|
||||
|
||||
/// Returns the module this symbol is exported from.
|
||||
pub fn module(&self) -> Module<'db> {
|
||||
self.module
|
||||
}
|
||||
|
||||
/// Returns the `File` corresponding to the module.
|
||||
///
|
||||
/// This is always equivalent to
|
||||
/// `AllSymbolInfo::module().file().unwrap()`.
|
||||
pub fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
pub file: File,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -233,31 +162,25 @@ ABCDEFGHIJKLMNOP = 'https://api.example.com'
|
||||
return "No symbols found".to_string();
|
||||
}
|
||||
|
||||
self.render_diagnostics(symbols.into_iter().map(|symbol_info| AllSymbolDiagnostic {
|
||||
db: &self.db,
|
||||
symbol_info,
|
||||
}))
|
||||
self.render_diagnostics(symbols.into_iter().map(AllSymbolDiagnostic::new))
|
||||
}
|
||||
}
|
||||
|
||||
struct AllSymbolDiagnostic<'db> {
|
||||
db: &'db dyn Db,
|
||||
symbol_info: AllSymbolInfo<'db>,
|
||||
}
|
||||
|
||||
impl<'db> AllSymbolDiagnostic<'db> {
|
||||
fn new(symbol_info: AllSymbolInfo<'db>) -> Self {
|
||||
Self { symbol_info }
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDiagnostic for AllSymbolDiagnostic<'_> {
|
||||
fn into_diagnostic(self) -> Diagnostic {
|
||||
let symbol_kind_str = self.symbol_info.kind().to_string();
|
||||
let symbol_kind_str = self.symbol_info.symbol.kind.to_string();
|
||||
|
||||
let info_text = format!(
|
||||
"{} {}",
|
||||
symbol_kind_str,
|
||||
self.symbol_info.name_in_file().unwrap_or_else(|| self
|
||||
.symbol_info
|
||||
.module()
|
||||
.name(self.db)
|
||||
.as_str())
|
||||
);
|
||||
let info_text = format!("{} {}", symbol_kind_str, self.symbol_info.symbol.name);
|
||||
|
||||
let sub = SubDiagnostic::new(SubDiagnosticSeverity::Info, info_text);
|
||||
|
||||
@@ -266,12 +189,9 @@ ABCDEFGHIJKLMNOP = 'https://api.example.com'
|
||||
Severity::Info,
|
||||
"AllSymbolInfo".to_string(),
|
||||
);
|
||||
|
||||
let mut span = Span::from(self.symbol_info.file());
|
||||
if let Some(ref symbol) = self.symbol_info.symbol {
|
||||
span = span.with_range(symbol.name_range);
|
||||
}
|
||||
main.annotate(Annotation::primary(span));
|
||||
main.annotate(Annotation::primary(
|
||||
Span::from(self.symbol_info.file).with_range(self.symbol_info.symbol.name_range),
|
||||
));
|
||||
main.sub(sub);
|
||||
|
||||
main
|
||||
|
||||
@@ -74,7 +74,7 @@ impl<'db> Completions<'db> {
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
Some(ImportEdit {
|
||||
label: format!("import {}", item.qualified?),
|
||||
label: format!("import {}.{}", item.module_name?, item.name),
|
||||
edit: item.import?,
|
||||
})
|
||||
})
|
||||
@@ -160,10 +160,6 @@ impl<'db> Extend<Completion<'db>> for Completions<'db> {
|
||||
pub struct Completion<'db> {
|
||||
/// The label shown to the user for this suggestion.
|
||||
pub name: Name,
|
||||
/// The fully qualified name, when available.
|
||||
///
|
||||
/// This is only set when `module_name` is available.
|
||||
pub qualified: Option<Name>,
|
||||
/// The text that should be inserted at the cursor
|
||||
/// when the completion is selected.
|
||||
///
|
||||
@@ -229,7 +225,6 @@ impl<'db> Completion<'db> {
|
||||
let is_type_check_only = semantic.is_type_check_only(db);
|
||||
Completion {
|
||||
name: semantic.name,
|
||||
qualified: None,
|
||||
insert: None,
|
||||
ty: semantic.ty,
|
||||
kind: None,
|
||||
@@ -311,7 +306,6 @@ impl<'db> Completion<'db> {
|
||||
fn keyword(name: &str) -> Self {
|
||||
Completion {
|
||||
name: name.into(),
|
||||
qualified: None,
|
||||
insert: None,
|
||||
ty: None,
|
||||
kind: Some(CompletionKind::Keyword),
|
||||
@@ -327,7 +321,6 @@ impl<'db> Completion<'db> {
|
||||
fn value_keyword(name: &str, ty: Type<'db>) -> Completion<'db> {
|
||||
Completion {
|
||||
name: name.into(),
|
||||
qualified: None,
|
||||
insert: None,
|
||||
ty: Some(ty),
|
||||
kind: Some(CompletionKind::Keyword),
|
||||
@@ -544,22 +537,12 @@ fn add_unimported_completions<'db>(
|
||||
let members = importer.members_in_scope_at(scoped.node, scoped.node.start());
|
||||
|
||||
for symbol in all_symbols(db, file, &completions.query) {
|
||||
if symbol.file() == file || symbol.module().is_known(db, KnownModule::Builtins) {
|
||||
if symbol.module.file(db) == Some(file) || symbol.module.is_known(db, KnownModule::Builtins)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let module_name = symbol.module().name(db);
|
||||
let (name, qualified, request) = symbol
|
||||
.name_in_file()
|
||||
.map(|name| {
|
||||
let qualified = format!("{module_name}.{name}");
|
||||
(name, qualified, create_import_request(module_name, name))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let name = module_name.as_str();
|
||||
let qualified = name.to_string();
|
||||
(name, qualified, ImportRequest::module(name))
|
||||
});
|
||||
let request = create_import_request(symbol.module.name(db), &symbol.symbol.name);
|
||||
// FIXME: `all_symbols` doesn't account for wildcard imports.
|
||||
// Since we're looking at every module, this is probably
|
||||
// "fine," but it might mean that we import a symbol from the
|
||||
@@ -568,12 +551,11 @@ fn add_unimported_completions<'db>(
|
||||
// N.B. We use `add` here because `all_symbols` already
|
||||
// takes our query into account.
|
||||
completions.force_add(Completion {
|
||||
name: ast::name::Name::new(name),
|
||||
qualified: Some(ast::name::Name::new(qualified)),
|
||||
name: ast::name::Name::new(&symbol.symbol.name),
|
||||
insert: Some(import_action.symbol_text().into()),
|
||||
ty: None,
|
||||
kind: symbol.kind().to_completion_kind(),
|
||||
module_name: Some(module_name),
|
||||
kind: symbol.symbol.kind.to_completion_kind(),
|
||||
module_name: Some(symbol.module.name(db)),
|
||||
import: import_action.import().cloned(),
|
||||
builtin: false,
|
||||
// TODO: `is_type_check_only` requires inferring the type of the symbol
|
||||
@@ -4368,7 +4350,7 @@ from os.<CURSOR>
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @r"
|
||||
Kadabra :: Literal[1] :: <no import required>
|
||||
Kadabra :: Literal[1] :: Current module
|
||||
AbraKadabra :: Unavailable :: package
|
||||
");
|
||||
}
|
||||
@@ -5552,7 +5534,7 @@ def foo(param: s<CURSOR>)
|
||||
// Even though long_namea is alphabetically before long_nameb,
|
||||
// long_nameb is currently imported and should be preferred.
|
||||
assert_snapshot!(snapshot, @r"
|
||||
long_nameb :: Literal[1] :: <no import required>
|
||||
long_nameb :: Literal[1] :: Current module
|
||||
long_namea :: Unavailable :: foo
|
||||
");
|
||||
}
|
||||
@@ -5822,7 +5804,7 @@ from .imp<CURSOR>
|
||||
#[test]
|
||||
fn typing_extensions_excluded_from_import() {
|
||||
let builder = completion_test_builder("from typing<CURSOR>").module_names();
|
||||
assert_snapshot!(builder.build().snapshot(), @"typing :: <no import required>");
|
||||
assert_snapshot!(builder.build().snapshot(), @"typing :: Current module");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5830,7 +5812,13 @@ from .imp<CURSOR>
|
||||
let builder = completion_test_builder("deprecated<CURSOR>")
|
||||
.auto_import()
|
||||
.module_names();
|
||||
assert_snapshot!(builder.build().snapshot(), @"deprecated :: warnings");
|
||||
assert_snapshot!(builder.build().snapshot(), @r"
|
||||
Deprecated :: importlib.metadata
|
||||
DeprecatedList :: importlib.metadata
|
||||
DeprecatedNonAbstract :: importlib.metadata
|
||||
DeprecatedTuple :: importlib.metadata
|
||||
deprecated :: warnings
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5841,8 +5829,8 @@ from .imp<CURSOR>
|
||||
.completion_test_builder()
|
||||
.module_names();
|
||||
assert_snapshot!(builder.build().snapshot(), @r"
|
||||
typing :: <no import required>
|
||||
typing_extensions :: <no import required>
|
||||
typing :: Current module
|
||||
typing_extensions :: Current module
|
||||
");
|
||||
}
|
||||
|
||||
@@ -5855,6 +5843,10 @@ from .imp<CURSOR>
|
||||
.auto_import()
|
||||
.module_names();
|
||||
assert_snapshot!(builder.build().snapshot(), @r"
|
||||
Deprecated :: importlib.metadata
|
||||
DeprecatedList :: importlib.metadata
|
||||
DeprecatedNonAbstract :: importlib.metadata
|
||||
DeprecatedTuple :: importlib.metadata
|
||||
deprecated :: typing_extensions
|
||||
deprecated :: warnings
|
||||
");
|
||||
@@ -5867,8 +5859,8 @@ from .imp<CURSOR>
|
||||
.completion_test_builder()
|
||||
.module_names();
|
||||
assert_snapshot!(builder.build().snapshot(), @r"
|
||||
typing :: <no import required>
|
||||
typing_extensions :: <no import required>
|
||||
typing :: Current module
|
||||
typing_extensions :: Current module
|
||||
");
|
||||
}
|
||||
|
||||
@@ -5880,284 +5872,15 @@ from .imp<CURSOR>
|
||||
.auto_import()
|
||||
.module_names();
|
||||
assert_snapshot!(builder.build().snapshot(), @r"
|
||||
Deprecated :: importlib.metadata
|
||||
DeprecatedList :: importlib.metadata
|
||||
DeprecatedNonAbstract :: importlib.metadata
|
||||
DeprecatedTuple :: importlib.metadata
|
||||
deprecated :: typing_extensions
|
||||
deprecated :: warnings
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reexport_simple_import_noauto() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
import foo
|
||||
foo.ZQ<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source("foo.py", r#"from bar import ZQZQ"#)
|
||||
.source("bar.py", r#"ZQZQ = 1"#)
|
||||
.completion_test_builder()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @"ZQZQ :: <no import required>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reexport_simple_import_auto() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
ZQ<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source("foo.py", r#"from bar import ZQZQ"#)
|
||||
.source("bar.py", r#"ZQZQ = 1"#)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
// We're specifically looking for `ZQZQ` in `bar`
|
||||
// here but *not* in `foo`. Namely, in `foo`,
|
||||
// `ZQZQ` is a "regular" import that is not by
|
||||
// convention considered a re-export.
|
||||
assert_snapshot!(snapshot, @"ZQZQ :: bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reexport_redundant_convention_import_noauto() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
import foo
|
||||
foo.ZQ<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source("foo.py", r#"from bar import ZQZQ as ZQZQ"#)
|
||||
.source("bar.py", r#"ZQZQ = 1"#)
|
||||
.completion_test_builder()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @"ZQZQ :: <no import required>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reexport_redundant_convention_import_auto() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
ZQ<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source("foo.py", r#"from bar import ZQZQ as ZQZQ"#)
|
||||
.source("bar.py", r#"ZQZQ = 1"#)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @r"
|
||||
ZQZQ :: bar
|
||||
ZQZQ :: foo
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_respects_all() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
ZQ<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"bar.py",
|
||||
r#"
|
||||
ZQZQ1 = 1
|
||||
ZQZQ2 = 1
|
||||
__all__ = ['ZQZQ1']
|
||||
"#,
|
||||
)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
// We specifically do not want `ZQZQ2` here, since
|
||||
// it is not part of `__all__`.
|
||||
assert_snapshot!(snapshot, @r"
|
||||
ZQZQ1 :: bar
|
||||
");
|
||||
}
|
||||
|
||||
// This test confirms current behavior (as of 2025-12-04), but
|
||||
// it's not consistent with auto-import. That is, it doesn't
|
||||
// strictly respect `__all__` on `bar`, but perhaps it should.
|
||||
//
|
||||
// See: https://github.com/astral-sh/ty/issues/1757
|
||||
#[test]
|
||||
fn object_attr_ignores_all() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
import bar
|
||||
bar.ZQ<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"bar.py",
|
||||
r#"
|
||||
ZQZQ1 = 1
|
||||
ZQZQ2 = 1
|
||||
__all__ = ['ZQZQ1']
|
||||
"#,
|
||||
)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
// We specifically do not want `ZQZQ2` here, since
|
||||
// it is not part of `__all__`.
|
||||
assert_snapshot!(snapshot, @r"
|
||||
ZQZQ1 :: <no import required>
|
||||
ZQZQ2 :: <no import required>
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_ignores_modules_with_leading_underscore() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
Quitter<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
// There is a `Quitter` in `_sitebuiltins` in the standard
|
||||
// library. But this is skipped by auto-import because it's
|
||||
// 1) not first party and 2) starts with an `_`.
|
||||
assert_snapshot!(snapshot, @"<No completions found>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_includes_modules_with_leading_underscore_in_first_party() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
ZQ<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"bar.py",
|
||||
r#"
|
||||
ZQZQ1 = 1
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"_foo.py",
|
||||
r#"
|
||||
ZQZQ1 = 1
|
||||
"#,
|
||||
)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.module_names()
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @r"
|
||||
ZQZQ1 :: _foo
|
||||
ZQZQ1 :: bar
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_includes_stdlib_modules_as_suggestions() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
multiprocess<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @r"
|
||||
multiprocessing
|
||||
multiprocessing.connection
|
||||
multiprocessing.context
|
||||
multiprocessing.dummy
|
||||
multiprocessing.dummy.connection
|
||||
multiprocessing.forkserver
|
||||
multiprocessing.heap
|
||||
multiprocessing.managers
|
||||
multiprocessing.pool
|
||||
multiprocessing.popen_fork
|
||||
multiprocessing.popen_forkserver
|
||||
multiprocessing.popen_spawn_posix
|
||||
multiprocessing.popen_spawn_win32
|
||||
multiprocessing.process
|
||||
multiprocessing.queues
|
||||
multiprocessing.reduction
|
||||
multiprocessing.resource_sharer
|
||||
multiprocessing.resource_tracker
|
||||
multiprocessing.shared_memory
|
||||
multiprocessing.sharedctypes
|
||||
multiprocessing.spawn
|
||||
multiprocessing.synchronize
|
||||
multiprocessing.util
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_includes_first_party_modules_as_suggestions() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
zqzqzq<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source("zqzqzqzqzq.py", "")
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @"zqzqzqzqzq");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_includes_sub_modules_as_suggestions() {
|
||||
let snapshot = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
collabc<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.completion_test_builder()
|
||||
.auto_import()
|
||||
.build()
|
||||
.snapshot();
|
||||
assert_snapshot!(snapshot, @"collections.abc");
|
||||
}
|
||||
|
||||
/// A way to create a simple single-file (named `main.py`) completion test
|
||||
/// builder.
|
||||
///
|
||||
@@ -6332,7 +6055,7 @@ collabc<CURSOR>
|
||||
let module_name = c
|
||||
.module_name
|
||||
.map(ModuleName::as_str)
|
||||
.unwrap_or("<no import required>");
|
||||
.unwrap_or("Current module");
|
||||
snapshot = format!("{snapshot} :: {module_name}");
|
||||
}
|
||||
snapshot
|
||||
|
||||
@@ -230,58 +230,10 @@ calc = Calculator()
|
||||
"
|
||||
def test():
|
||||
# Cursor on a position with no symbol
|
||||
<CURSOR>
|
||||
<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.document_highlights(), @"No highlights found");
|
||||
}
|
||||
|
||||
// TODO: Should only highlight the last use and the last declaration
|
||||
#[test]
|
||||
fn redeclarations() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
a: str = "test"
|
||||
|
||||
a: int = 10
|
||||
|
||||
print(a<CURSOR>)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.document_highlights(), @r#"
|
||||
info[document_highlights]: Highlight 1 (Write)
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
| ^
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
|
|
||||
|
||||
info[document_highlights]: Highlight 2 (Write)
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
| ^
|
||||
5 |
|
||||
6 | print(a)
|
||||
|
|
||||
|
||||
info[document_highlights]: Highlight 3 (Read)
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -824,12 +824,12 @@ mod tests {
|
||||
Check out this great example code::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
|
||||
print("done")
|
||||
|
||||
You love to see it.
|
||||
@@ -862,12 +862,12 @@ mod tests {
|
||||
Check out this great example code ::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
|
||||
print("done")
|
||||
|
||||
You love to see it.
|
||||
@@ -901,12 +901,12 @@ mod tests {
|
||||
::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
|
||||
print("done")
|
||||
|
||||
You love to see it.
|
||||
@@ -939,12 +939,12 @@ mod tests {
|
||||
let docstring = r#"
|
||||
Check out this great example code::
|
||||
x_y = "hello"
|
||||
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
|
||||
print("done")
|
||||
You love to see it.
|
||||
"#;
|
||||
@@ -975,12 +975,12 @@ mod tests {
|
||||
Check out this great example code::
|
||||
|
||||
x_y = "hello"
|
||||
|
||||
|
||||
if len(x_y) > 4:
|
||||
print(x_y)
|
||||
else:
|
||||
print("too short :(")
|
||||
|
||||
|
||||
print("done")"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
@@ -898,42 +898,6 @@ cls = MyClass
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation_recursive() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: "a<CURSOR>b"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | ab: "ab"
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:2:6
|
||||
|
|
||||
2 | ab: "ab"
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_string_annotation_unknown() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
x: "foo<CURSOR>bar"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_match_name_stmt() {
|
||||
let test = cursor_test(
|
||||
@@ -1906,259 +1870,4 @@ func<CURSOR>_alias()
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_submodule_import_from_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// TODO(submodule-imports): this should light up both instances of `subpkg`
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_submodule_import_from_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg.submod import val
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// TODO(submodule-imports): this should light up both instances of `subpkg`
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_submodule_import_from_wrong_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>mod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// No references is actually correct (or it should only see itself)
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_submodule_import_from_wrong_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.sub<CURSOR>mod import val
|
||||
|
||||
x = submod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// No references is actually correct (or it should only see itself)
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_submodule_import_from_confusing_shadowed_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg import subpkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// No references is actually correct (or it should only see itself)
|
||||
assert_snapshot!(test.references(), @"No references found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_submodule_import_from_confusing_real_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import sub<CURSOR>pkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> mypackage/__init__.py:2:21
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> mypackage/subpkg/__init__.py:2:1
|
||||
|
|
||||
2 | subpkg: int = 10
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_submodule_import_from_confusing_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import subpkg
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// TODO: this should also highlight the RHS subpkg in the import
|
||||
assert_snapshot!(test.references(), @r"
|
||||
info[references]: Reference 1
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
// TODO: Should only return references to the last declaration
|
||||
#[test]
|
||||
fn declarations() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
a: str = "test"
|
||||
|
||||
a: int = 10
|
||||
|
||||
print(a<CURSOR>)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.references(), @r#"
|
||||
info[references]: Reference 1
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
| ^
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
|
|
||||
|
||||
info[references]: Reference 2
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
| ^
|
||||
5 |
|
||||
6 | print(a)
|
||||
|
|
||||
|
||||
info[references]: Reference 3
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,29 +73,19 @@ pub(crate) enum GotoTarget<'a> {
|
||||
/// ```
|
||||
ImportModuleAlias {
|
||||
alias: &'a ast::Alias,
|
||||
asname: &'a ast::Identifier,
|
||||
},
|
||||
|
||||
/// In an import statement, the named under which the symbol is exported
|
||||
/// in the imported file.
|
||||
///
|
||||
/// ```py
|
||||
/// from foo import bar as baz
|
||||
/// ^^^
|
||||
/// ```
|
||||
ImportExportedName {
|
||||
alias: &'a ast::Alias,
|
||||
import_from: &'a ast::StmtImportFrom,
|
||||
},
|
||||
|
||||
/// Import alias in from import statement
|
||||
/// ```py
|
||||
/// from foo import bar as baz
|
||||
/// ^^^
|
||||
/// from foo import bar as baz
|
||||
/// ^^^
|
||||
/// ```
|
||||
ImportSymbolAlias {
|
||||
alias: &'a ast::Alias,
|
||||
asname: &'a ast::Identifier,
|
||||
range: TextRange,
|
||||
import_from: &'a ast::StmtImportFrom,
|
||||
},
|
||||
|
||||
/// Go to on the exception handler variable
|
||||
@@ -300,9 +290,8 @@ impl GotoTarget<'_> {
|
||||
GotoTarget::FunctionDef(function) => function.inferred_type(model),
|
||||
GotoTarget::ClassDef(class) => class.inferred_type(model),
|
||||
GotoTarget::Parameter(parameter) => parameter.inferred_type(model),
|
||||
GotoTarget::ImportSymbolAlias { alias, .. }
|
||||
| GotoTarget::ImportModuleAlias { alias, .. }
|
||||
| GotoTarget::ImportExportedName { alias, .. } => alias.inferred_type(model),
|
||||
GotoTarget::ImportSymbolAlias { alias, .. } => alias.inferred_type(model),
|
||||
GotoTarget::ImportModuleAlias { alias } => alias.inferred_type(model),
|
||||
GotoTarget::ExceptVariable(except) => except.inferred_type(model),
|
||||
GotoTarget::KeywordArgument { keyword, .. } => keyword.value.inferred_type(model),
|
||||
// When asking the type of a callable, usually you want the callable itself?
|
||||
@@ -389,9 +378,7 @@ impl GotoTarget<'_> {
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Option<Definitions<'db>> {
|
||||
let definitions = match self {
|
||||
GotoTarget::Expression(expression) => {
|
||||
definitions_for_expression(model, *expression, alias_resolution)
|
||||
}
|
||||
GotoTarget::Expression(expression) => definitions_for_expression(model, *expression),
|
||||
// For already-defined symbols, they are their own definitions
|
||||
GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition(
|
||||
function.definition(model),
|
||||
@@ -406,21 +393,22 @@ impl GotoTarget<'_> {
|
||||
)]),
|
||||
|
||||
// For import aliases (offset within 'y' or 'z' in "from x import y as z")
|
||||
GotoTarget::ImportSymbolAlias { asname, .. } => Some(definitions_for_name(
|
||||
model,
|
||||
asname.as_str(),
|
||||
AnyNodeRef::from(*asname),
|
||||
alias_resolution,
|
||||
)),
|
||||
|
||||
GotoTarget::ImportExportedName { alias, import_from } => {
|
||||
let symbol_name = alias.name.as_str();
|
||||
Some(definitions_for_imported_symbol(
|
||||
model,
|
||||
import_from,
|
||||
symbol_name,
|
||||
alias_resolution,
|
||||
))
|
||||
GotoTarget::ImportSymbolAlias {
|
||||
alias, import_from, ..
|
||||
} => {
|
||||
if let Some(asname) = alias.asname.as_ref()
|
||||
&& alias_resolution == ImportAliasResolution::PreserveAliases
|
||||
{
|
||||
Some(definitions_for_name(model, asname.as_str(), asname.into()))
|
||||
} else {
|
||||
let symbol_name = alias.name.as_str();
|
||||
Some(definitions_for_imported_symbol(
|
||||
model,
|
||||
import_from,
|
||||
symbol_name,
|
||||
alias_resolution,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
GotoTarget::ImportModuleComponent {
|
||||
@@ -435,12 +423,15 @@ impl GotoTarget<'_> {
|
||||
}
|
||||
|
||||
// Handle import aliases (offset within 'z' in "import x.y as z")
|
||||
GotoTarget::ImportModuleAlias { asname, .. } => Some(definitions_for_name(
|
||||
model,
|
||||
asname.as_str(),
|
||||
AnyNodeRef::from(*asname),
|
||||
alias_resolution,
|
||||
)),
|
||||
GotoTarget::ImportModuleAlias { alias } => {
|
||||
if let Some(asname) = alias.asname.as_ref()
|
||||
&& alias_resolution == ImportAliasResolution::PreserveAliases
|
||||
{
|
||||
Some(definitions_for_name(model, asname.as_str(), asname.into()))
|
||||
} else {
|
||||
definitions_for_module(model, Some(alias.name.as_str()), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle keyword arguments in call expressions
|
||||
GotoTarget::KeywordArgument {
|
||||
@@ -463,22 +454,12 @@ impl GotoTarget<'_> {
|
||||
// because they're not expressions
|
||||
GotoTarget::PatternMatchRest(pattern_mapping) => {
|
||||
pattern_mapping.rest.as_ref().map(|name| {
|
||||
definitions_for_name(
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
alias_resolution,
|
||||
)
|
||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
||||
})
|
||||
}
|
||||
|
||||
GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| {
|
||||
definitions_for_name(
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
alias_resolution,
|
||||
)
|
||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
||||
}),
|
||||
|
||||
GotoTarget::PatternKeywordArgument(pattern_keyword) => {
|
||||
@@ -487,18 +468,12 @@ impl GotoTarget<'_> {
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
alias_resolution,
|
||||
))
|
||||
}
|
||||
|
||||
GotoTarget::PatternMatchStarName(pattern_star) => {
|
||||
pattern_star.name.as_ref().map(|name| {
|
||||
definitions_for_name(
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
alias_resolution,
|
||||
)
|
||||
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -506,18 +481,9 @@ impl GotoTarget<'_> {
|
||||
//
|
||||
// Prefer the function impl over the callable so that its docstrings win if defined.
|
||||
GotoTarget::Call { callable, call } => {
|
||||
let mut definitions = Vec::new();
|
||||
|
||||
// We prefer the specific overload for hover, go-to-def etc. However,
|
||||
// `definitions_for_callable` always resolves import aliases. That's why we
|
||||
// skip it in cases import alias resolution is turned of (rename, highlight references).
|
||||
if alias_resolution == ImportAliasResolution::ResolveAliases {
|
||||
definitions.extend(definitions_for_callable(model, call));
|
||||
}
|
||||
|
||||
let mut definitions = definitions_for_callable(model, call);
|
||||
let expr_definitions =
|
||||
definitions_for_expression(model, *callable, alias_resolution)
|
||||
.unwrap_or_default();
|
||||
definitions_for_expression(model, *callable).unwrap_or_default();
|
||||
definitions.extend(expr_definitions);
|
||||
|
||||
if definitions.is_empty() {
|
||||
@@ -551,7 +517,7 @@ impl GotoTarget<'_> {
|
||||
let subexpr = covering_node(subast.syntax().into(), *subrange)
|
||||
.node()
|
||||
.as_expr_ref()?;
|
||||
definitions_for_expression(&submodel, subexpr, alias_resolution)
|
||||
definitions_for_expression(&submodel, subexpr)
|
||||
}
|
||||
|
||||
// nonlocal and global are essentially loads, but again they're statements,
|
||||
@@ -561,7 +527,6 @@ impl GotoTarget<'_> {
|
||||
model,
|
||||
identifier.as_str(),
|
||||
AnyNodeRef::Identifier(identifier),
|
||||
alias_resolution,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -572,7 +537,6 @@ impl GotoTarget<'_> {
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
alias_resolution,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -582,7 +546,6 @@ impl GotoTarget<'_> {
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
alias_resolution,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -592,7 +555,6 @@ impl GotoTarget<'_> {
|
||||
model,
|
||||
name.as_str(),
|
||||
AnyNodeRef::Identifier(name),
|
||||
alias_resolution,
|
||||
))
|
||||
}
|
||||
};
|
||||
@@ -618,9 +580,12 @@ impl GotoTarget<'_> {
|
||||
GotoTarget::FunctionDef(function) => Some(Cow::Borrowed(function.name.as_str())),
|
||||
GotoTarget::ClassDef(class) => Some(Cow::Borrowed(class.name.as_str())),
|
||||
GotoTarget::Parameter(parameter) => Some(Cow::Borrowed(parameter.name.as_str())),
|
||||
GotoTarget::ImportSymbolAlias { asname, .. } => Some(Cow::Borrowed(asname.as_str())),
|
||||
GotoTarget::ImportExportedName { alias, .. } => {
|
||||
Some(Cow::Borrowed(alias.name.as_str()))
|
||||
GotoTarget::ImportSymbolAlias { alias, .. } => {
|
||||
if let Some(asname) = &alias.asname {
|
||||
Some(Cow::Borrowed(asname.as_str()))
|
||||
} else {
|
||||
Some(Cow::Borrowed(alias.name.as_str()))
|
||||
}
|
||||
}
|
||||
GotoTarget::ImportModuleComponent {
|
||||
module_name,
|
||||
@@ -634,7 +599,13 @@ impl GotoTarget<'_> {
|
||||
Some(Cow::Borrowed(module_name))
|
||||
}
|
||||
}
|
||||
GotoTarget::ImportModuleAlias { asname, .. } => Some(Cow::Borrowed(asname.as_str())),
|
||||
GotoTarget::ImportModuleAlias { alias } => {
|
||||
if let Some(asname) = &alias.asname {
|
||||
Some(Cow::Borrowed(asname.as_str()))
|
||||
} else {
|
||||
Some(Cow::Borrowed(alias.name.as_str()))
|
||||
}
|
||||
}
|
||||
GotoTarget::ExceptVariable(except) => {
|
||||
Some(Cow::Borrowed(except.name.as_ref()?.as_str()))
|
||||
}
|
||||
@@ -696,7 +667,7 @@ impl GotoTarget<'_> {
|
||||
// Is the offset within the alias name (asname) part?
|
||||
if let Some(asname) = &alias.asname {
|
||||
if asname.range.contains_inclusive(offset) {
|
||||
return Some(GotoTarget::ImportModuleAlias { alias, asname });
|
||||
return Some(GotoTarget::ImportModuleAlias { alias });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,13 +699,21 @@ impl GotoTarget<'_> {
|
||||
// Is the offset within the alias name (asname) part?
|
||||
if let Some(asname) = &alias.asname {
|
||||
if asname.range.contains_inclusive(offset) {
|
||||
return Some(GotoTarget::ImportSymbolAlias { alias, asname });
|
||||
return Some(GotoTarget::ImportSymbolAlias {
|
||||
alias,
|
||||
range: asname.range,
|
||||
import_from,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Is the offset in the original name part?
|
||||
if alias.name.range.contains_inclusive(offset) {
|
||||
return Some(GotoTarget::ImportExportedName { alias, import_from });
|
||||
return Some(GotoTarget::ImportSymbolAlias {
|
||||
alias,
|
||||
range: alias.name.range,
|
||||
import_from,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
@@ -914,13 +893,12 @@ impl Ranged for GotoTarget<'_> {
|
||||
GotoTarget::FunctionDef(function) => function.name.range,
|
||||
GotoTarget::ClassDef(class) => class.name.range,
|
||||
GotoTarget::Parameter(parameter) => parameter.name.range,
|
||||
GotoTarget::ImportSymbolAlias { asname, .. } => asname.range,
|
||||
Self::ImportExportedName { alias, .. } => alias.name.range,
|
||||
GotoTarget::ImportSymbolAlias { range, .. } => *range,
|
||||
GotoTarget::ImportModuleComponent {
|
||||
component_range, ..
|
||||
} => *component_range,
|
||||
GotoTarget::StringAnnotationSubexpr { subrange, .. } => *subrange,
|
||||
GotoTarget::ImportModuleAlias { asname, .. } => asname.range,
|
||||
GotoTarget::ImportModuleAlias { alias } => alias.asname.as_ref().unwrap().range,
|
||||
GotoTarget::ExceptVariable(except) => except.name.as_ref().unwrap().range,
|
||||
GotoTarget::KeywordArgument { keyword, .. } => keyword.arg.as_ref().unwrap().range,
|
||||
GotoTarget::PatternMatchRest(rest) => rest.rest.as_ref().unwrap().range,
|
||||
@@ -977,14 +955,12 @@ fn convert_resolved_definitions_to_targets<'db>(
|
||||
fn definitions_for_expression<'db>(
|
||||
model: &SemanticModel<'db>,
|
||||
expression: ruff_python_ast::ExprRef<'_>,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
||||
match expression {
|
||||
ast::ExprRef::Name(name) => Some(definitions_for_name(
|
||||
model,
|
||||
name.id.as_str(),
|
||||
expression.into(),
|
||||
alias_resolution,
|
||||
)),
|
||||
ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute(
|
||||
model, attribute,
|
||||
|
||||
@@ -273,7 +273,7 @@ mod tests {
|
||||
r#"
|
||||
class A:
|
||||
x = 1
|
||||
|
||||
|
||||
def method(self):
|
||||
def inner():
|
||||
return <CURSOR>x # Should NOT find class variable x
|
||||
@@ -1073,41 +1073,6 @@ def another_helper(path):
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation_recursive() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: "a<CURSOR>b"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | ab: "ab"
|
||||
| ^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:6
|
||||
|
|
||||
2 | ab: "ab"
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_string_annotation_unknown() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
x: "foo<CURSOR>bar"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_nested_instance_attribute() {
|
||||
let test = cursor_test(
|
||||
@@ -1255,12 +1220,12 @@ x: i<CURSOR>nt = 42
|
||||
r#"
|
||||
def outer():
|
||||
x = "outer_value"
|
||||
|
||||
|
||||
def inner():
|
||||
nonlocal x
|
||||
x = "modified"
|
||||
return x<CURSOR> # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
@@ -1295,12 +1260,12 @@ def outer():
|
||||
r#"
|
||||
def outer():
|
||||
xy = "outer_value"
|
||||
|
||||
|
||||
def inner():
|
||||
nonlocal x<CURSOR>y
|
||||
xy = "modified"
|
||||
return x # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
@@ -1636,7 +1601,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
@@ -1675,7 +1640,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
@@ -1713,7 +1678,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
@@ -1751,7 +1716,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
@@ -1919,7 +1884,7 @@ def function():
|
||||
class C:
|
||||
def __init__(self):
|
||||
self._value = 0
|
||||
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
@@ -2029,7 +1994,7 @@ def function():
|
||||
r#"
|
||||
class MyClass:
|
||||
ClassType = int
|
||||
|
||||
|
||||
def generic_method[T](self, value: Class<CURSOR>Type) -> T:
|
||||
return value
|
||||
"#,
|
||||
@@ -2602,378 +2567,6 @@ def ab(a: int, *, c: int): ...
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_submodule_import_from_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// TODO(submodule-imports): this should only highlight `subpkg` in the import statement
|
||||
// This happens because DefinitionKind::ImportFromSubmodule claims the entire ImportFrom node,
|
||||
// which is correct but unhelpful. Unfortunately even if it only claimed the LHS identifier it
|
||||
// would highlight `subpkg.submod` which is strictly better but still isn't what we want.
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> mypackage/__init__.py:2:1
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_submodule_import_from_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg.submod import val
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// TODO(submodule-imports): I don't *think* this is what we want..?
|
||||
// It's a bit confusing because this symbol is essentially the LHS *and* RHS of
|
||||
// `subpkg = mypackage.subpkg`. As in, it's both defining a local `subpkg` and
|
||||
// loading the module `mypackage.subpkg`, so, it's understandable to get confused!
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> mypackage/subpkg/__init__.py:1:1
|
||||
|
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:7
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_submodule_import_from_wrong_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>mod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// No result is correct!
|
||||
assert_snapshot!(test.goto_declaration(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_submodule_import_from_wrong_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.sub<CURSOR>mod import val
|
||||
|
||||
x = submod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// Going to the submod module is correct!
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> mypackage/subpkg/submod.py:1:1
|
||||
|
|
||||
1 |
|
||||
| ^
|
||||
2 | val: int = 0
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:14
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = submod
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_submodule_import_from_confusing_shadowed_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg import subpkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// Going to the subpkg module is correct!
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> mypackage/subpkg/__init__.py:1:1
|
||||
|
|
||||
1 |
|
||||
| ^
|
||||
2 | subpkg: int = 10
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:7
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_submodule_import_from_confusing_real_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import sub<CURSOR>pkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// Going to the subpkg `int` is correct!
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> mypackage/subpkg/__init__.py:2:1
|
||||
|
|
||||
2 | subpkg: int = 10
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:21
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_declaration_submodule_import_from_confusing_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import subpkg
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// TODO(submodule-imports): Ok this one is FASCINATING and it's kinda right but confusing!
|
||||
//
|
||||
// So there's 3 relevant definitions here:
|
||||
//
|
||||
// * `subpkg: int = 10` in the other file is in fact the original definition
|
||||
//
|
||||
// * the LHS `subpkg` in the import is an instance of `subpkg = ...`
|
||||
// because it's a `DefinitionKind::ImportFromSubmodle`.
|
||||
// This is the span that covers the entire import.
|
||||
//
|
||||
// * `the RHS `subpkg` in the import is a second instance of `subpkg = ...`
|
||||
// that *immediately* overwrites the `ImportFromSubmodule`'s definition
|
||||
// This span seemingly doesn't appear at all!? Is it getting hidden by the LHS span?
|
||||
assert_snapshot!(test.goto_declaration(), @r"
|
||||
info[goto-declaration]: Declaration
|
||||
--> mypackage/__init__.py:2:1
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
|
||||
info[goto-declaration]: Declaration
|
||||
--> mypackage/subpkg/__init__.py:2:1
|
||||
|
|
||||
2 | subpkg: int = 10
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
// TODO: Should only return `a: int`
|
||||
#[test]
|
||||
fn redeclarations() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
a: str = "test"
|
||||
|
||||
a: int = 10
|
||||
|
||||
print(a<CURSOR>)
|
||||
|
||||
a: bool = True
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_declaration(), @r#"
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
| ^
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
|
|
||||
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
| ^
|
||||
5 |
|
||||
6 | print(a)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
|
|
||||
|
||||
info[goto-declaration]: Declaration
|
||||
--> main.py:8:1
|
||||
|
|
||||
6 | print(a)
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
| ^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn goto_declaration(&self) -> String {
|
||||
let Some(targets) = goto_declaration(&self.db, self.cursor.file, self.cursor.offset)
|
||||
|
||||
@@ -1714,86 +1714,6 @@ Traceb<CURSOR>ackType
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
// TODO: Should only list `a: int`
|
||||
#[test]
|
||||
fn redeclarations() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
a: str = "test"
|
||||
|
||||
a: int = 10
|
||||
|
||||
print(a<CURSOR>)
|
||||
|
||||
a: bool = True
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r#"
|
||||
info[goto-definition]: Definition
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
| ^
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
|
|
||||
|
||||
info[goto-definition]: Definition
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | a: str = "test"
|
||||
3 |
|
||||
4 | a: int = 10
|
||||
| ^
|
||||
5 |
|
||||
6 | print(a)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
|
|
||||
|
||||
info[goto-definition]: Definition
|
||||
--> main.py:8:1
|
||||
|
|
||||
6 | print(a)
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
| ^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:7
|
||||
|
|
||||
4 | a: int = 10
|
||||
5 |
|
||||
6 | print(a)
|
||||
| ^
|
||||
7 |
|
||||
8 | a: bool = True
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn goto_definition(&self) -> String {
|
||||
let Some(targets) = goto_definition(&self.db, self.cursor.file, self.cursor.offset)
|
||||
|
||||
@@ -145,14 +145,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/typing.pyi:781:1
|
||||
--> stdlib/typing.pyi:770:1
|
||||
|
|
||||
779 | def __class_getitem__(cls, args: TypeVar | tuple[TypeVar, ...]) -> _Final: ...
|
||||
780 |
|
||||
781 | Generic: type[_Generic]
|
||||
768 | def __class_getitem__(cls, args: TypeVar | tuple[TypeVar, ...]) -> _Final: ...
|
||||
769 |
|
||||
770 | Generic: type[_Generic]
|
||||
| ^^^^^^^
|
||||
782 |
|
||||
783 | class _ProtocolMeta(ABCMeta):
|
||||
771 |
|
||||
772 | class _ProtocolMeta(ABCMeta):
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:1
|
||||
@@ -964,60 +964,6 @@ mod tests {
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation_recursive() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: "a<CURSOR>b"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/ty_extensions.pyi:20:1
|
||||
|
|
||||
19 | # Types
|
||||
20 | Unknown = object()
|
||||
| ^^^^^^^
|
||||
21 | AlwaysTruthy = object()
|
||||
22 | AlwaysFalsy = object()
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:6
|
||||
|
|
||||
2 | ab: "ab"
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_string_annotation_unknown() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
x: "foo<CURSOR>bar"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/ty_extensions.pyi:20:1
|
||||
|
|
||||
19 | # Types
|
||||
20 | Unknown = object()
|
||||
| ^^^^^^^
|
||||
21 | AlwaysTruthy = object()
|
||||
22 | AlwaysFalsy = object()
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | x: "foobar"
|
||||
| ^^^^^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_match_name_stmt() {
|
||||
let test = cursor_test(
|
||||
@@ -1111,7 +1057,7 @@ mod tests {
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
@@ -1131,7 +1077,7 @@ mod tests {
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
@@ -1151,7 +1097,7 @@ mod tests {
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
@@ -1189,7 +1135,7 @@ mod tests {
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
@@ -1398,12 +1344,12 @@ f(**kwargs<CURSOR>)
|
||||
r#"
|
||||
def outer():
|
||||
x = "outer_value"
|
||||
|
||||
|
||||
def inner():
|
||||
nonlocal x
|
||||
x = "modified"
|
||||
return x<CURSOR> # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
@@ -1438,12 +1384,12 @@ def outer():
|
||||
r#"
|
||||
def outer():
|
||||
xy = "outer_value"
|
||||
|
||||
|
||||
def inner():
|
||||
nonlocal x<CURSOR>y
|
||||
xy = "modified"
|
||||
return x # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
@@ -1672,283 +1618,6 @@ def function():
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_submodule_import_from_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The module is the correct type definition
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> mypackage/subpkg/__init__.py:1:1
|
||||
|
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_submodule_import_from_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg.submod import val
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The module is the correct type definition
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> mypackage/subpkg/__init__.py:1:1
|
||||
|
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:7
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_submodule_import_from_wrong_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>mod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// Unknown is correct, `submod` is not in scope
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/ty_extensions.pyi:20:1
|
||||
|
|
||||
19 | # Types
|
||||
20 | Unknown = object()
|
||||
| ^^^^^^^
|
||||
21 | AlwaysTruthy = object()
|
||||
22 | AlwaysFalsy = object()
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
3 |
|
||||
4 | x = submod
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_submodule_import_from_wrong_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.sub<CURSOR>mod import val
|
||||
|
||||
x = submod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The module is correct
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> mypackage/subpkg/submod.py:1:1
|
||||
|
|
||||
1 | /
|
||||
2 | | val: int = 0
|
||||
| |_____________^
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:14
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = submod
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_submodule_import_from_confusing_shadowed_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg import subpkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The module is correct
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> mypackage/subpkg/__init__.py:1:1
|
||||
|
|
||||
1 | /
|
||||
2 | | subpkg: int = 10
|
||||
| |_________________^
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:7
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_submodule_import_from_confusing_real_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import sub<CURSOR>pkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// `int` is correct
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:348:7
|
||||
|
|
||||
347 | @disjoint_base
|
||||
348 | class int:
|
||||
| ^^^
|
||||
349 | """int([x]) -> integer
|
||||
350 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:2:21
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^^^^
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_submodule_import_from_confusing_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import subpkg
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// `int` is correct
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:348:7
|
||||
|
|
||||
347 | @disjoint_base
|
||||
348 | class int:
|
||||
| ^^^
|
||||
349 | """int([x]) -> integer
|
||||
350 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^^^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn goto_type_definition(&self) -> String {
|
||||
let Some(targets) =
|
||||
|
||||
@@ -1089,60 +1089,6 @@ mod tests {
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation_recursive() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: "a<CURSOR>b"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
Unknown
|
||||
---------------------------------------------
|
||||
```python
|
||||
Unknown
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:6
|
||||
|
|
||||
2 | ab: "ab"
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_string_annotation_unknown() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
x: "foo<CURSOR>bar"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
Unknown
|
||||
---------------------------------------------
|
||||
```python
|
||||
Unknown
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | x: "foobar"
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_overload_type_disambiguated1() {
|
||||
let test = CursorTest::builder()
|
||||
@@ -1708,12 +1654,12 @@ def ab(a: int, *, c: int):
|
||||
r#"
|
||||
def outer():
|
||||
x = "outer_value"
|
||||
|
||||
|
||||
def inner():
|
||||
nonlocal x
|
||||
x = "modified"
|
||||
return x<CURSOR> # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
@@ -1747,12 +1693,12 @@ def outer():
|
||||
r#"
|
||||
def outer():
|
||||
xy = "outer_value"
|
||||
|
||||
|
||||
def inner():
|
||||
nonlocal x<CURSOR>y
|
||||
xy = "modified"
|
||||
return x # Should find the nonlocal x declaration in outer scope
|
||||
|
||||
|
||||
return inner
|
||||
"#,
|
||||
);
|
||||
@@ -1960,7 +1906,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=a<CURSOR>b):
|
||||
@@ -1980,7 +1926,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
@@ -2018,7 +1964,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Cl<CURSOR>ick(x, button=ab):
|
||||
@@ -2057,7 +2003,7 @@ def function():
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, but<CURSOR>ton=ab):
|
||||
@@ -2143,13 +2089,15 @@ def function():
|
||||
"#,
|
||||
);
|
||||
|
||||
// TODO: This should just be `**AB@Alias2 (<variance>)`
|
||||
// https://github.com/astral-sh/ty/issues/1581
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(**AB@Alias2) -> tuple[AB@Alias2]
|
||||
(
|
||||
...
|
||||
) -> tuple[typing.ParamSpec]
|
||||
---------------------------------------------
|
||||
```python
|
||||
(**AB@Alias2) -> tuple[AB@Alias2]
|
||||
(
|
||||
...
|
||||
) -> tuple[typing.ParamSpec]
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
@@ -2290,12 +2238,12 @@ def function():
|
||||
"#,
|
||||
);
|
||||
|
||||
// TODO: Should this be constravariant instead?
|
||||
// TODO: This should be `P@Alias (<variance>)`
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
P@Alias (bivariant)
|
||||
typing.ParamSpec
|
||||
---------------------------------------------
|
||||
```python
|
||||
P@Alias (bivariant)
|
||||
typing.ParamSpec
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
@@ -3319,297 +3267,6 @@ def function():
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_submodule_import_from_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The module is correct
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
<module 'mypackage.subpkg'>
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_submodule_import_from_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg.submod import val
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The module is correct
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
<module 'mypackage.subpkg'>
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> mypackage/__init__.py:2:7
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_submodule_import_from_wrong_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.submod import val
|
||||
|
||||
x = sub<CURSOR>mod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// Unknown is correct
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
Unknown
|
||||
---------------------------------------------
|
||||
```python
|
||||
Unknown
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
3 |
|
||||
4 | x = submod
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_submodule_import_from_wrong_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg.sub<CURSOR>mod import val
|
||||
|
||||
x = submod
|
||||
"#,
|
||||
)
|
||||
.source("mypackage/subpkg/__init__.py", r#""#)
|
||||
.source(
|
||||
"mypackage/subpkg/submod.py",
|
||||
r#"
|
||||
val: int = 0
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The submodule is correct
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg.submod'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
<module 'mypackage.subpkg.submod'>
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> mypackage/__init__.py:2:14
|
||||
|
|
||||
2 | from .subpkg.submod import val
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
3 |
|
||||
4 | x = submod
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_submodule_import_from_confusing_shadowed_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .sub<CURSOR>pkg import subpkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// The module is correct
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
<module 'mypackage.subpkg'>
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> mypackage/__init__.py:2:7
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_submodule_import_from_confusing_real_def() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import sub<CURSOR>pkg
|
||||
|
||||
x = subpkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// int is correct
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
int
|
||||
---------------------------------------------
|
||||
```python
|
||||
int
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> mypackage/__init__.py:2:21
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_submodule_import_from_confusing_use() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"mypackage/__init__.py",
|
||||
r#"
|
||||
from .subpkg import subpkg
|
||||
|
||||
x = sub<CURSOR>pkg
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"mypackage/subpkg/__init__.py",
|
||||
r#"
|
||||
subpkg: int = 10
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
// int is correct
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
int
|
||||
---------------------------------------------
|
||||
```python
|
||||
int
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> mypackage/__init__.py:4:5
|
||||
|
|
||||
2 | from .subpkg import subpkg
|
||||
3 |
|
||||
4 | x = subpkg
|
||||
| ^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn hover(&self) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -145,7 +145,7 @@ impl<'a> Importer<'a> {
|
||||
members: &MembersInScope,
|
||||
) -> ImportAction {
|
||||
let request = request.avoid_conflicts(self.db, self.file, members);
|
||||
let mut symbol_text: Box<str> = request.member.unwrap_or(request.module).into();
|
||||
let mut symbol_text: Box<str> = request.member.into();
|
||||
let Some(response) = self.find(&request, members.at) else {
|
||||
let insertion = if let Some(future) = self.find_last_future_import(members.at) {
|
||||
Insertion::end_of_statement(future.stmt, self.source, self.stylist)
|
||||
@@ -157,27 +157,14 @@ impl<'a> Importer<'a> {
|
||||
Insertion::start_of_file(self.parsed.suite(), self.source, self.stylist, range)
|
||||
};
|
||||
let import = insertion.into_edit(&request.to_string());
|
||||
if let Some(member) = request.member
|
||||
&& matches!(request.style, ImportStyle::Import)
|
||||
{
|
||||
symbol_text = format!("{}.{}", request.module, member).into();
|
||||
if matches!(request.style, ImportStyle::Import) {
|
||||
symbol_text = format!("{}.{}", request.module, request.member).into();
|
||||
}
|
||||
return ImportAction {
|
||||
import: Some(import),
|
||||
symbol_text,
|
||||
};
|
||||
};
|
||||
|
||||
// When we just have a request to import a module (and not
|
||||
// any members from that module), then the only way we can be
|
||||
// here is if we found a pre-existing import that definitively
|
||||
// satisfies the request. So we're done.
|
||||
let Some(member) = request.member else {
|
||||
return ImportAction {
|
||||
import: None,
|
||||
symbol_text,
|
||||
};
|
||||
};
|
||||
match response.kind {
|
||||
ImportResponseKind::Unqualified { ast, alias } => {
|
||||
let member = alias.asname.as_ref().unwrap_or(&alias.name).as_str();
|
||||
@@ -202,10 +189,13 @@ impl<'a> Importer<'a> {
|
||||
let import = if let Some(insertion) =
|
||||
Insertion::existing_import(response.import.stmt, self.tokens)
|
||||
{
|
||||
insertion.into_edit(member)
|
||||
insertion.into_edit(request.member)
|
||||
} else {
|
||||
Insertion::end_of_statement(response.import.stmt, self.source, self.stylist)
|
||||
.into_edit(&format!("from {} import {member}", request.module))
|
||||
.into_edit(&format!(
|
||||
"from {} import {}",
|
||||
request.module, request.member
|
||||
))
|
||||
};
|
||||
ImportAction {
|
||||
import: Some(import),
|
||||
@@ -337,7 +327,9 @@ impl<'ast> MembersInScope<'ast> {
|
||||
.members_in_scope_at(node)
|
||||
.into_iter()
|
||||
.map(|(name, memberdef)| {
|
||||
let def = memberdef.first_reachable_definition;
|
||||
let Some(def) = memberdef.definition else {
|
||||
return (name, MemberInScope::other(memberdef.ty));
|
||||
};
|
||||
let kind = match *def.kind(db) {
|
||||
DefinitionKind::Import(ref kind) => {
|
||||
MemberImportKind::Imported(AstImportKind::Import(kind.import(parsed)))
|
||||
@@ -491,17 +483,6 @@ impl<'ast> AstImportKind<'ast> {
|
||||
Some(ImportResponseKind::Qualified { ast, alias })
|
||||
}
|
||||
AstImportKind::ImportFrom(ast) => {
|
||||
// If the request is for a module itself, then we
|
||||
// assume that it can never be satisfies by a
|
||||
// `from ... import ...` statement. For example, a
|
||||
// `request for collections.abc` needs an
|
||||
// `import collections.abc`. Now, there could be a
|
||||
// `from collections import abc`, and we could
|
||||
// plausibly consider that a match and return a
|
||||
// symbol text of `abc`. But it's not clear if that's
|
||||
// the right choice or not.
|
||||
let member = request.member?;
|
||||
|
||||
if request.force_style && !matches!(request.style, ImportStyle::ImportFrom) {
|
||||
return None;
|
||||
}
|
||||
@@ -513,7 +494,9 @@ impl<'ast> AstImportKind<'ast> {
|
||||
let kind = ast
|
||||
.names
|
||||
.iter()
|
||||
.find(|alias| alias.name.as_str() == "*" || alias.name.as_str() == member)
|
||||
.find(|alias| {
|
||||
alias.name.as_str() == "*" || alias.name.as_str() == request.member
|
||||
})
|
||||
.map(|alias| ImportResponseKind::Unqualified { ast, alias })
|
||||
.unwrap_or_else(|| ImportResponseKind::Partial(ast));
|
||||
Some(kind)
|
||||
@@ -529,10 +512,7 @@ pub(crate) struct ImportRequest<'a> {
|
||||
/// `foo`, in `from foo import bar`).
|
||||
module: &'a str,
|
||||
/// The member to import (e.g., `bar`, in `from foo import bar`).
|
||||
///
|
||||
/// When `member` is absent, then this request reflects an import
|
||||
/// of the module itself. i.e., `import module`.
|
||||
member: Option<&'a str>,
|
||||
member: &'a str,
|
||||
/// The preferred style to use when importing the symbol (e.g.,
|
||||
/// `import foo` or `from foo import bar`).
|
||||
///
|
||||
@@ -554,7 +534,7 @@ impl<'a> ImportRequest<'a> {
|
||||
pub(crate) fn import(module: &'a str, member: &'a str) -> Self {
|
||||
Self {
|
||||
module,
|
||||
member: Some(member),
|
||||
member,
|
||||
style: ImportStyle::Import,
|
||||
force_style: false,
|
||||
}
|
||||
@@ -567,26 +547,12 @@ impl<'a> ImportRequest<'a> {
|
||||
pub(crate) fn import_from(module: &'a str, member: &'a str) -> Self {
|
||||
Self {
|
||||
module,
|
||||
member: Some(member),
|
||||
member,
|
||||
style: ImportStyle::ImportFrom,
|
||||
force_style: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`ImportRequest`] for bringing the given module
|
||||
/// into scope.
|
||||
///
|
||||
/// This is for just importing the module itself, always via an
|
||||
/// `import` statement.
|
||||
pub(crate) fn module(module: &'a str) -> Self {
|
||||
Self {
|
||||
module,
|
||||
member: None,
|
||||
style: ImportStyle::Import,
|
||||
force_style: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Causes this request to become a command. This will force the
|
||||
/// requested import style, even if another style would be more
|
||||
/// appropriate generally.
|
||||
@@ -601,13 +567,7 @@ impl<'a> ImportRequest<'a> {
|
||||
/// of an import conflict are minimized (although not always reduced
|
||||
/// to zero).
|
||||
fn avoid_conflicts(self, db: &dyn Db, importing_file: File, members: &MembersInScope) -> Self {
|
||||
let Some(member) = self.member else {
|
||||
return Self {
|
||||
style: ImportStyle::Import,
|
||||
..self
|
||||
};
|
||||
};
|
||||
match (members.map.get(self.module), members.map.get(member)) {
|
||||
match (members.map.get(self.module), members.map.get(self.member)) {
|
||||
// Neither symbol exists, so we can just proceed as
|
||||
// normal.
|
||||
(None, None) => self,
|
||||
@@ -672,10 +632,7 @@ impl std::fmt::Display for ImportRequest<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self.style {
|
||||
ImportStyle::Import => write!(f, "import {}", self.module),
|
||||
ImportStyle::ImportFrom => match self.member {
|
||||
None => write!(f, "import {}", self.module),
|
||||
Some(member) => write!(f, "from {} import {member}", self.module),
|
||||
},
|
||||
ImportStyle::ImportFrom => write!(f, "from {} import {}", self.module, self.member),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -888,10 +845,6 @@ mod tests {
|
||||
self.add(ImportRequest::import_from(module, member))
|
||||
}
|
||||
|
||||
fn module(&self, module: &str) -> String {
|
||||
self.add(ImportRequest::module(module))
|
||||
}
|
||||
|
||||
fn add(&self, request: ImportRequest<'_>) -> String {
|
||||
let node = covering_node(
|
||||
self.cursor.parsed.syntax().into(),
|
||||
@@ -1938,13 +1891,13 @@ else:
|
||||
"#);
|
||||
assert_snapshot!(
|
||||
test.import_from("foo", "MAGIC"), @r#"
|
||||
from foo import MAGIC
|
||||
import foo
|
||||
if os.getenv("WHATEVER"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(MAGIC)
|
||||
(foo.MAGIC)
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -2155,13 +2108,13 @@ except ImportError:
|
||||
");
|
||||
assert_snapshot!(
|
||||
test.import_from("foo", "MAGIC"), @r"
|
||||
from foo import MAGIC
|
||||
import foo
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(MAGIC)
|
||||
(foo.MAGIC)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -2205,73 +2158,4 @@ except ImportError:
|
||||
(bar.MAGIC)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_module_blank() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
test.module("collections"), @r"
|
||||
import collections
|
||||
collections
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_module_exists() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
import collections
|
||||
<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
test.module("collections"), @r"
|
||||
import collections
|
||||
collections
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_module_from_exists() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
from collections import defaultdict
|
||||
<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
test.module("collections"), @r"
|
||||
import collections
|
||||
from collections import defaultdict
|
||||
collections
|
||||
");
|
||||
}
|
||||
|
||||
// This test is working as intended. That is,
|
||||
// `abc` is already in scope, so requesting an
|
||||
// import for `collections.abc` could feasibly
|
||||
// reuse the import and rewrite the symbol text
|
||||
// to just `abc`. But for now it seems better
|
||||
// to respect what has been written and add the
|
||||
// `import collections.abc`. This behavior could
|
||||
// plausibly be changed.
|
||||
#[test]
|
||||
fn import_module_from_via_member_exists() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
from collections import abc
|
||||
<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
test.module("collections.abc"), @r"
|
||||
import collections.abc
|
||||
from collections import abc
|
||||
collections.abc
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,22 +19,11 @@ pub struct InlayHint {
|
||||
}
|
||||
|
||||
impl InlayHint {
|
||||
fn variable_type(
|
||||
expr: &Expr,
|
||||
rhs: &Expr,
|
||||
ty: Type,
|
||||
db: &dyn Db,
|
||||
allow_edits: bool,
|
||||
) -> Option<Self> {
|
||||
fn variable_type(expr: &Expr, ty: Type, db: &dyn Db, allow_edits: bool) -> Self {
|
||||
let position = expr.range().end();
|
||||
// Render the type to a string, and get subspans for all the types that make it up
|
||||
let details = ty.display(db).to_string_parts();
|
||||
|
||||
// Filter out a reptitive hints like `x: T = T()`
|
||||
if call_matches_name(rhs, &details.label) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ok so the idea here is that we potentially have a random soup of spans here,
|
||||
// and each byte of the string can have at most one target associate with it.
|
||||
// Thankfully, they were generally pushed in print order, with the inner smaller types
|
||||
@@ -84,12 +73,12 @@ impl InlayHint {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
Self {
|
||||
position,
|
||||
kind: InlayHintKind::Type,
|
||||
label: InlayHintLabel { parts: label_parts },
|
||||
text_edits,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn call_argument_name(
|
||||
@@ -261,7 +250,7 @@ struct InlayHintVisitor<'a, 'db> {
|
||||
db: &'db dyn Db,
|
||||
model: SemanticModel<'db>,
|
||||
hints: Vec<InlayHint>,
|
||||
assignment_rhs: Option<&'a Expr>,
|
||||
in_assignment: bool,
|
||||
range: TextRange,
|
||||
settings: &'a InlayHintSettings,
|
||||
in_no_edits_allowed: bool,
|
||||
@@ -273,21 +262,21 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
db,
|
||||
model: SemanticModel::new(db, file),
|
||||
hints: Vec::new(),
|
||||
assignment_rhs: None,
|
||||
in_assignment: false,
|
||||
range,
|
||||
settings,
|
||||
in_no_edits_allowed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_type_hint(&mut self, expr: &Expr, rhs: &Expr, ty: Type<'db>, allow_edits: bool) {
|
||||
fn add_type_hint(&mut self, expr: &Expr, ty: Type<'db>, allow_edits: bool) {
|
||||
if !self.settings.variable_types {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(inlay_hint) = InlayHint::variable_type(expr, rhs, ty, self.db, allow_edits) {
|
||||
self.hints.push(inlay_hint);
|
||||
}
|
||||
let inlay_hint = InlayHint::variable_type(expr, ty, self.db, allow_edits);
|
||||
|
||||
self.hints.push(inlay_hint);
|
||||
}
|
||||
|
||||
fn add_call_argument_name(
|
||||
@@ -310,8 +299,8 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
fn enter_node(&mut self, node: AnyNodeRef<'a>) -> TraversalSignal {
|
||||
impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||
fn enter_node(&mut self, node: AnyNodeRef<'_>) -> TraversalSignal {
|
||||
if self.range.intersect(node.range()).is_some() {
|
||||
TraversalSignal::Traverse
|
||||
} else {
|
||||
@@ -319,7 +308,7 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
let node = AnyNodeRef::from(stmt);
|
||||
|
||||
if !self.enter_node(node).is_traverse() {
|
||||
@@ -328,9 +317,7 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
|
||||
match stmt {
|
||||
Stmt::Assign(assign) => {
|
||||
if !type_hint_is_excessive_for_expr(&assign.value) {
|
||||
self.assignment_rhs = Some(&*assign.value);
|
||||
}
|
||||
self.in_assignment = !type_hint_is_excessive_for_expr(&assign.value);
|
||||
if !annotations_are_valid_syntax(assign) {
|
||||
self.in_no_edits_allowed = true;
|
||||
}
|
||||
@@ -338,7 +325,7 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
self.visit_expr(target);
|
||||
}
|
||||
self.in_no_edits_allowed = false;
|
||||
self.assignment_rhs = None;
|
||||
self.in_assignment = false;
|
||||
|
||||
self.visit_expr(&assign.value);
|
||||
|
||||
@@ -357,22 +344,22 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
source_order::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
fn visit_expr(&mut self, expr: &'_ Expr) {
|
||||
match expr {
|
||||
Expr::Name(name) => {
|
||||
if let Some(rhs) = self.assignment_rhs {
|
||||
if self.in_assignment {
|
||||
if name.ctx.is_store() {
|
||||
let ty = expr.inferred_type(&self.model);
|
||||
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
|
||||
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
|
||||
}
|
||||
}
|
||||
source_order::walk_expr(self, expr);
|
||||
}
|
||||
Expr::Attribute(attribute) => {
|
||||
if let Some(rhs) = self.assignment_rhs {
|
||||
if self.in_assignment {
|
||||
if attribute.ctx.is_store() {
|
||||
let ty = expr.inferred_type(&self.model);
|
||||
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
|
||||
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
|
||||
}
|
||||
}
|
||||
source_order::walk_expr(self, expr);
|
||||
@@ -429,26 +416,6 @@ fn arg_matches_name(arg_or_keyword: &ArgOrKeyword, name: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a function call, check if the expression is the "same name"
|
||||
/// as the function being called.
|
||||
///
|
||||
/// This allows us to filter out reptitive inlay hints like `x: T = T(...)`.
|
||||
/// While still allowing non-trivial ones like `x: T[U] = T()`.
|
||||
fn call_matches_name(expr: &Expr, name: &str) -> bool {
|
||||
// Only care about function calls
|
||||
let Expr::Call(call) = expr else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match &*call.func {
|
||||
// `x: T = T()` is a match
|
||||
Expr::Name(expr_name) => expr_name.id.as_str() == name,
|
||||
// `x: T = a.T()` is a match
|
||||
Expr::Attribute(expr_attribute) => expr_attribute.attr.as_str() == name,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an expression that's the RHS of an assignment, would it be excessive to
|
||||
/// emit an inlay type hint for the variable assigned to it?
|
||||
///
|
||||
@@ -1862,16 +1829,35 @@ mod tests {
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
class A:
|
||||
def __init__(self, y):
|
||||
self.x = int(1)
|
||||
self.x[: int] = int(1)
|
||||
self.y[: Unknown] = y
|
||||
|
||||
a = A([y=]2)
|
||||
a.y = int(3)
|
||||
a[: A] = A([y=]2)
|
||||
a.y[: int] = int(3)
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:348:7
|
||||
|
|
||||
347 | @disjoint_base
|
||||
348 | class int:
|
||||
| ^^^
|
||||
349 | """int([x]) -> integer
|
||||
350 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:18
|
||||
|
|
||||
2 | class A:
|
||||
3 | def __init__(self, y):
|
||||
4 | self.x[: int] = int(1)
|
||||
| ^^^
|
||||
5 | self.y[: Unknown] = y
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/ty_extensions.pyi:20:1
|
||||
|
|
||||
@@ -1885,11 +1871,29 @@ mod tests {
|
||||
--> main2.py:5:18
|
||||
|
|
||||
3 | def __init__(self, y):
|
||||
4 | self.x = int(1)
|
||||
4 | self.x[: int] = int(1)
|
||||
5 | self.y[: Unknown] = y
|
||||
| ^^^^^^^
|
||||
6 |
|
||||
7 | a = A([y=]2)
|
||||
7 | a[: A] = A([y=]2)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class A:
|
||||
| ^
|
||||
3 | def __init__(self, y):
|
||||
4 | self.x = int(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
|
|
||||
5 | self.y[: Unknown] = y
|
||||
6 |
|
||||
7 | a[: A] = A([y=]2)
|
||||
| ^
|
||||
8 | a.y[: int] = int(3)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -1902,13 +1906,30 @@ mod tests {
|
||||
5 | self.y = y
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:8
|
||||
--> main2.py:7:13
|
||||
|
|
||||
5 | self.y[: Unknown] = y
|
||||
6 |
|
||||
7 | a = A([y=]2)
|
||||
| ^
|
||||
8 | a.y = int(3)
|
||||
7 | a[: A] = A([y=]2)
|
||||
| ^
|
||||
8 | a.y[: int] = int(3)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:348:7
|
||||
|
|
||||
347 | @disjoint_base
|
||||
348 | class int:
|
||||
| ^^^
|
||||
349 | """int([x]) -> integer
|
||||
350 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:8:7
|
||||
|
|
||||
7 | a[: A] = A([y=]2)
|
||||
8 | a.y[: int] = int(3)
|
||||
| ^^^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
@@ -1917,12 +1938,12 @@ mod tests {
|
||||
|
||||
class A:
|
||||
def __init__(self, y):
|
||||
self.x = int(1)
|
||||
self.x: int = int(1)
|
||||
self.y: Unknown = y
|
||||
|
||||
a = A(2)
|
||||
a.y = int(3)
|
||||
");
|
||||
a: A = A(2)
|
||||
a.y: int = int(3)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1991,7 +2012,7 @@ mod tests {
|
||||
def __init__(self, pos, btn):
|
||||
self.position: int = pos
|
||||
self.button: str = btn
|
||||
|
||||
|
||||
def my_func(event: Click):
|
||||
match event:
|
||||
case Click(x, button=ab):
|
||||
@@ -2916,12 +2937,31 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
|
||||
x = MyClass()
|
||||
x[: MyClass] = MyClass()
|
||||
y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
c[: MyClass], d[: MyClass] = (MyClass(), MyClass())
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class MyClass:
|
||||
| ^^^^^^^
|
||||
3 | def __init__(self):
|
||||
4 | self.x: int = 1
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:6:5
|
||||
|
|
||||
4 | self.x: int = 1
|
||||
5 |
|
||||
6 | x[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
|
|
||||
@@ -2933,7 +2973,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
| ^^^^^
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
@@ -2951,7 +2991,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:7:11
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
| ^^^^^^^
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
@@ -2969,7 +3009,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:7:20
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
| ^^^^^^^
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
@@ -2987,7 +3027,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:8:5
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
| ^^^^^^^
|
||||
@@ -3005,7 +3045,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:8:19
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
| ^^^^^^^
|
||||
@@ -3054,7 +3094,7 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
|
||||
x = MyClass()
|
||||
x: MyClass = MyClass()
|
||||
y: tuple[MyClass, MyClass] = (MyClass(), MyClass())
|
||||
a, b = MyClass(), MyClass()
|
||||
c, d = (MyClass(), MyClass())
|
||||
@@ -4057,11 +4097,31 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
val = MyClass()
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x)
|
||||
foo([x=]val.y)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:7
|
||||
|
|
||||
2 | def foo(x: int): pass
|
||||
3 | class MyClass:
|
||||
| ^^^^^^^
|
||||
4 | def __init__(self):
|
||||
5 | self.x: int = 1
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:7
|
||||
|
|
||||
5 | self.x: int = 1
|
||||
6 | self.y: int = 2
|
||||
7 | val[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
8 |
|
||||
9 | foo(val.x)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:9
|
||||
|
|
||||
@@ -4077,6 +4137,20 @@ mod tests {
|
||||
10 | foo([x=]val.y)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
val: MyClass = MyClass()
|
||||
|
||||
foo(val.x)
|
||||
foo(val.y)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4102,11 +4176,31 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
x = MyClass()
|
||||
x[: MyClass] = MyClass()
|
||||
|
||||
foo(x.x)
|
||||
foo([x=]x.y)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:7
|
||||
|
|
||||
2 | def foo(x: int): pass
|
||||
3 | class MyClass:
|
||||
| ^^^^^^^
|
||||
4 | def __init__(self):
|
||||
5 | self.x: int = 1
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
|
|
||||
5 | self.x: int = 1
|
||||
6 | self.y: int = 2
|
||||
7 | x[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
8 |
|
||||
9 | foo(x.x)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:9
|
||||
|
|
||||
@@ -4122,6 +4216,20 @@ mod tests {
|
||||
10 | foo([x=]x.y)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
x: MyClass = MyClass()
|
||||
|
||||
foo(x.x)
|
||||
foo(x.y)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4150,11 +4258,31 @@ mod tests {
|
||||
return 1
|
||||
def y() -> int:
|
||||
return 2
|
||||
val = MyClass()
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x())
|
||||
foo([x=]val.y())
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:7
|
||||
|
|
||||
2 | def foo(x: int): pass
|
||||
3 | class MyClass:
|
||||
| ^^^^^^^
|
||||
4 | def __init__(self):
|
||||
5 | def x() -> int:
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:9:7
|
||||
|
|
||||
7 | def y() -> int:
|
||||
8 | return 2
|
||||
9 | val[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
10 |
|
||||
11 | foo(val.x())
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:9
|
||||
|
|
||||
@@ -4170,6 +4298,22 @@ mod tests {
|
||||
12 | foo([x=]val.y())
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
def x() -> int:
|
||||
return 1
|
||||
def y() -> int:
|
||||
return 2
|
||||
val: MyClass = MyClass()
|
||||
|
||||
foo(val.x())
|
||||
foo(val.y())
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4202,11 +4346,31 @@ mod tests {
|
||||
return 1
|
||||
def y() -> List[int]:
|
||||
return 2
|
||||
val = MyClass()
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x()[0])
|
||||
foo([x=]val.y()[1])
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:5:7
|
||||
|
|
||||
4 | def foo(x: int): pass
|
||||
5 | class MyClass:
|
||||
| ^^^^^^^
|
||||
6 | def __init__(self):
|
||||
7 | def x() -> List[int]:
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:11:7
|
||||
|
|
||||
9 | def y() -> List[int]:
|
||||
10 | return 2
|
||||
11 | val[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
12 |
|
||||
13 | foo(val.x()[0])
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:4:9
|
||||
|
|
||||
@@ -4224,6 +4388,24 @@ mod tests {
|
||||
14 | foo([x=]val.y()[1])
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
from typing import List
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
def x() -> List[int]:
|
||||
return 1
|
||||
def y() -> List[int]:
|
||||
return 2
|
||||
val: MyClass = MyClass()
|
||||
|
||||
foo(val.x()[0])
|
||||
foo(val.y()[1])
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4515,7 +4697,7 @@ mod tests {
|
||||
class Foo:
|
||||
def __init__(self, x: int): pass
|
||||
Foo([x=]1)
|
||||
f = Foo([x=]1)
|
||||
f[: Foo] = Foo([x=]1)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:24
|
||||
@@ -4533,7 +4715,24 @@ mod tests {
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
| ^
|
||||
5 | f = Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Foo:
|
||||
| ^^^
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:5
|
||||
|
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -4546,13 +4745,22 @@ mod tests {
|
||||
5 | f = Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:10
|
||||
--> main2.py:5:17
|
||||
|
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f = Foo([x=]1)
|
||||
| ^
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
class Foo:
|
||||
def __init__(self, x: int): pass
|
||||
Foo(1)
|
||||
f: Foo = Foo(1)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4570,7 +4778,7 @@ mod tests {
|
||||
class Foo:
|
||||
def __new__(cls, x: int): pass
|
||||
Foo([x=]1)
|
||||
f = Foo([x=]1)
|
||||
f[: Foo] = Foo([x=]1)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:22
|
||||
@@ -4588,7 +4796,24 @@ mod tests {
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
| ^
|
||||
5 | f = Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Foo:
|
||||
| ^^^
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:5
|
||||
|
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -4601,13 +4826,22 @@ mod tests {
|
||||
5 | f = Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:10
|
||||
--> main2.py:5:17
|
||||
|
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f = Foo([x=]1)
|
||||
| ^
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
class Foo:
|
||||
def __new__(cls, x: int): pass
|
||||
Foo(1)
|
||||
f: Foo = Foo(1)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -6194,11 +6428,11 @@ mod tests {
|
||||
a = Literal['a', 'b', 'c']",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing import Literal
|
||||
|
||||
a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
"#);
|
||||
a[: <typing.Literal special form>] = Literal['a', 'b', 'c']
|
||||
");
|
||||
}
|
||||
|
||||
struct InlayHintLocationDiagnostic {
|
||||
|
||||
@@ -37,38 +37,6 @@ pub enum ReferencesMode {
|
||||
DocumentHighlights,
|
||||
}
|
||||
|
||||
impl ReferencesMode {
|
||||
pub(super) fn to_import_alias_resolution(self) -> ImportAliasResolution {
|
||||
match self {
|
||||
// Resolve import aliases for find references:
|
||||
// ```py
|
||||
// from warnings import deprecated as my_deprecated
|
||||
//
|
||||
// @my_deprecated
|
||||
// def foo
|
||||
// ```
|
||||
//
|
||||
// When finding references on `my_deprecated`, we want to find all usages of `deprecated` across the entire
|
||||
// project.
|
||||
Self::References | Self::ReferencesSkipDeclaration => {
|
||||
ImportAliasResolution::ResolveAliases
|
||||
}
|
||||
// For rename, don't resolve import aliases.
|
||||
//
|
||||
// ```py
|
||||
// from warnings import deprecated as my_deprecated
|
||||
//
|
||||
// @my_deprecated
|
||||
// def foo
|
||||
// ```
|
||||
// When renaming `my_deprecated`, only rename the alias, but not the original definition in `warnings`.
|
||||
Self::Rename | Self::RenameMultiFile | Self::DocumentHighlights => {
|
||||
ImportAliasResolution::PreserveAliases
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find all references to a symbol at the given position.
|
||||
/// Search for references across all files in the project.
|
||||
pub(crate) fn references(
|
||||
@@ -77,9 +45,12 @@ pub(crate) fn references(
|
||||
goto_target: &GotoTarget,
|
||||
mode: ReferencesMode,
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
// Get the definitions for the symbol at the cursor position
|
||||
|
||||
// When finding references, do not resolve any local aliases.
|
||||
let model = SemanticModel::new(db, file);
|
||||
let target_definitions = goto_target
|
||||
.get_definition_targets(&model, mode.to_import_alias_resolution())?
|
||||
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)?
|
||||
.declaration_targets(db)?;
|
||||
|
||||
// Extract the target text from the goto target for fast comparison
|
||||
@@ -347,7 +318,7 @@ impl LocalReferencesFinder<'_> {
|
||||
{
|
||||
// Get the definitions for this goto target
|
||||
if let Some(current_definitions) = goto_target
|
||||
.get_definition_targets(self.model, self.mode.to_import_alias_resolution())
|
||||
.get_definition_targets(self.model, ImportAliasResolution::PreserveAliases)
|
||||
.and_then(|definitions| definitions.declaration_targets(self.model.db()))
|
||||
{
|
||||
// Check if any of the current definitions match our target definitions
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -259,11 +259,7 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
|
||||
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
|
||||
// First try to classify the token based on its definition kind.
|
||||
let definition = definition_for_name(
|
||||
self.model,
|
||||
name,
|
||||
ty_python_semantic::ImportAliasResolution::ResolveAliases,
|
||||
);
|
||||
let definition = definition_for_name(self.model, name);
|
||||
|
||||
if let Some(definition) = definition {
|
||||
let name_str = name.id.as_str();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@ rustc-hash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -55,6 +55,7 @@ default = ["zstd"]
|
||||
deflate = ["ty_vendored/deflate"]
|
||||
schemars = [
|
||||
"dep:schemars",
|
||||
"dep:serde_json",
|
||||
"ruff_db/schemars",
|
||||
"ruff_python_ast/schemars",
|
||||
"ty_python_semantic/schemars",
|
||||
|
||||
@@ -7,7 +7,6 @@ pub use self::changes::ChangeResult;
|
||||
use crate::CollectReporter;
|
||||
use crate::metadata::settings::file_settings;
|
||||
use crate::{ProgressReporter, Project, ProjectMetadata};
|
||||
use get_size2::StandardTracker;
|
||||
use ruff_db::Db as SourceDb;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{File, Files};
|
||||
@@ -130,10 +129,7 @@ impl ProjectDatabase {
|
||||
/// Returns a [`SalsaMemoryDump`] that can be use to dump Salsa memory usage information
|
||||
/// to the CLI after a typechecker run.
|
||||
pub fn salsa_memory_dump(&self) -> SalsaMemoryDump {
|
||||
let memory_usage = ruff_memory_usage::attach_tracker(StandardTracker::new(), || {
|
||||
<dyn salsa::Database>::memory_usage(self)
|
||||
});
|
||||
|
||||
let memory_usage = <dyn salsa::Database>::memory_usage(self);
|
||||
let mut ingredients = memory_usage
|
||||
.structs
|
||||
.into_iter()
|
||||
|
||||
@@ -9,15 +9,13 @@ use ty_python_semantic::ProgramSettings;
|
||||
|
||||
use crate::metadata::options::ProjectOptionsOverrides;
|
||||
use crate::metadata::pyproject::{Project, PyProject, PyProjectError, ResolveRequiresPythonError};
|
||||
use crate::metadata::script::{Pep723Error, Pep723Metadata};
|
||||
use crate::metadata::value::{RelativePathBuf, ValueSource};
|
||||
use crate::metadata::value::ValueSource;
|
||||
pub use options::Options;
|
||||
use options::TyTomlError;
|
||||
|
||||
mod configuration_file;
|
||||
pub mod options;
|
||||
pub mod pyproject;
|
||||
pub mod script;
|
||||
pub mod settings;
|
||||
pub mod value;
|
||||
|
||||
@@ -87,32 +85,6 @@ impl ProjectMetadata {
|
||||
)
|
||||
}
|
||||
|
||||
/// Loads a project from a `pyproject.toml` file.
|
||||
pub(crate) fn from_script(
|
||||
script: Pep723Metadata,
|
||||
script_path: &SystemPath,
|
||||
) -> Result<Self, ResolveRequiresPythonError> {
|
||||
let project = Some(&script.to_project());
|
||||
let parent_dir = script_path
|
||||
.parent()
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_default();
|
||||
let mut metadata = Self::from_options(
|
||||
script.tool.and_then(|tool| tool.ty).unwrap_or_default(),
|
||||
parent_dir,
|
||||
project,
|
||||
)?;
|
||||
|
||||
// Try to get `uv sync --script` to setup the venv for us
|
||||
if let Some(python) = script::uv_sync_script(script_path) {
|
||||
let mut environment = metadata.options.environment.unwrap_or_default();
|
||||
environment.python = Some(RelativePathBuf::new(python, ValueSource::Cli));
|
||||
metadata.options.environment = Some(environment);
|
||||
}
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Loads a project from a set of options with an optional pyproject-project table.
|
||||
pub fn from_options(
|
||||
mut options: Options,
|
||||
@@ -148,46 +120,6 @@ impl ProjectMetadata {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn discover_script(
|
||||
path: &SystemPath,
|
||||
system: &dyn System,
|
||||
) -> Result<ProjectMetadata, ProjectMetadataError> {
|
||||
tracing::debug!("Searching for a PEP-723 Script in '{path}'");
|
||||
if !system.is_file(path) {
|
||||
return Err(ProjectMetadataError::NotAScript(path.to_path_buf()));
|
||||
}
|
||||
|
||||
let script_metadata = if let Ok(script_str) = system.read_to_string(path) {
|
||||
match Pep723Metadata::from_script_str(
|
||||
script_str.as_bytes(),
|
||||
ValueSource::File(Arc::new(path.to_owned())),
|
||||
) {
|
||||
Ok(Some(pyproject)) => Some(pyproject),
|
||||
Ok(None) => None,
|
||||
Err(error) => {
|
||||
return Err(ProjectMetadataError::InvalidScript {
|
||||
path: path.to_owned(),
|
||||
source: Box::new(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let Some(script_metadata) = script_metadata else {
|
||||
return Err(ProjectMetadataError::NotAScript(path.to_path_buf()));
|
||||
};
|
||||
|
||||
let metadata = ProjectMetadata::from_script(script_metadata, path).map_err(|err| {
|
||||
ProjectMetadataError::InvalidRequiresPythonConstraint {
|
||||
source: err,
|
||||
path: path.to_owned(),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
/// Discovers the closest project at `path` and returns its metadata.
|
||||
///
|
||||
/// The algorithm traverses upwards in the `path`'s ancestor chain and uses the following precedence
|
||||
@@ -387,21 +319,12 @@ pub enum ProjectMetadataError {
|
||||
#[error("project path '{0}' is not a directory")]
|
||||
NotADirectory(SystemPathBuf),
|
||||
|
||||
#[error("project path '{0}' is not a PEP-723 script")]
|
||||
NotAScript(SystemPathBuf),
|
||||
|
||||
#[error("{path} is not a valid `pyproject.toml`: {source}")]
|
||||
InvalidPyProject {
|
||||
source: Box<PyProjectError>,
|
||||
path: SystemPathBuf,
|
||||
},
|
||||
|
||||
#[error("{path} is not a valid PEP-723 script: {source}")]
|
||||
InvalidScript {
|
||||
source: Box<Pep723Error>,
|
||||
path: SystemPathBuf,
|
||||
},
|
||||
|
||||
#[error("{path} is not a valid `ty.toml`: {source}")]
|
||||
InvalidTyToml {
|
||||
source: Box<TyTomlError>,
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
use std::{io, process::Command, str::FromStr};
|
||||
|
||||
use camino::Utf8PathBuf;
|
||||
use pep440_rs::VersionSpecifiers;
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use ruff_python_ast::script::ScriptTag;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::metadata::{
|
||||
pyproject::{Project, Tool},
|
||||
value::{RangedValue, ValueSource, ValueSourceGuard},
|
||||
};
|
||||
|
||||
/// PEP 723 metadata as parsed from a `script` comment block.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0723/>
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Pep723Metadata {
|
||||
pub dependencies: Option<RangedValue<Vec<toml::Value>>>,
|
||||
pub requires_python: Option<RangedValue<VersionSpecifiers>>,
|
||||
pub tool: Option<Tool>,
|
||||
|
||||
/// The raw unserialized document.
|
||||
#[serde(skip)]
|
||||
pub raw: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Pep723Error {
|
||||
#[error(
|
||||
"An opening tag (`# /// script`) was found without a closing tag (`# ///`). Ensure that every line between the opening and closing tags (including empty lines) starts with a leading `#`."
|
||||
)]
|
||||
UnclosedBlock,
|
||||
#[error("The PEP 723 metadata block is missing from the script.")]
|
||||
MissingTag,
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
#[error(transparent)]
|
||||
Toml(#[from] toml::de::Error),
|
||||
#[error("Invalid filename `{0}` supplied")]
|
||||
InvalidFilename(String),
|
||||
}
|
||||
|
||||
impl Pep723Metadata {
|
||||
/// Parse the PEP 723 metadata from `stdin`.
|
||||
pub fn from_script_str(
|
||||
contents: &[u8],
|
||||
source: ValueSource,
|
||||
) -> Result<Option<Self>, Pep723Error> {
|
||||
let _guard = ValueSourceGuard::new(source, true);
|
||||
|
||||
// Extract the `script` tag.
|
||||
let Some(ScriptTag { metadata, .. }) = ScriptTag::parse(contents) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Parse the metadata.
|
||||
Ok(Some(Self::from_str(&metadata)?))
|
||||
}
|
||||
|
||||
pub fn to_project(&self) -> Project {
|
||||
Project {
|
||||
name: None,
|
||||
version: None,
|
||||
requires_python: self.requires_python.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"schema": {
|
||||
"version": "preview"
|
||||
},
|
||||
"target": "script",
|
||||
"script": {
|
||||
"path": "/Users/myuser/code/myproj/scripts/load-test.py"
|
||||
},
|
||||
"sync": {
|
||||
"environment": {
|
||||
"path": "/Users/myuser/.cache/uv/environments-v2/load-test-d6edaf5bfab110a8",
|
||||
"python": {
|
||||
"path": "/Users/myuser/.cache/uv/environments-v2/load-test-d6edaf5bfab110a8/bin/python3",
|
||||
"version": "3.14.0",
|
||||
"implementation": "cpython"
|
||||
}
|
||||
},
|
||||
"action": "check"
|
||||
},
|
||||
"lock": null,
|
||||
"dry_run": false
|
||||
}
|
||||
*/
|
||||
|
||||
/// The output of `uv sync --output-format=json --script ...`
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct UvMetadata {
|
||||
sync: Option<UvSync>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct UvSync {
|
||||
environment: Option<UvEnvironment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct UvEnvironment {
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
/// Ask `uv` to sync the script's venv to some temp dir so we can analyze dependencies properly
|
||||
///
|
||||
/// Returns the path to the venv on success
|
||||
pub fn uv_sync_script(script_path: &SystemPath) -> Option<SystemPathBuf> {
|
||||
tracing::info!("Asking uv to sync the script's venv");
|
||||
let mut command = Command::new("uv");
|
||||
command
|
||||
.arg("sync")
|
||||
.arg("--output-format=json")
|
||||
.arg("--script")
|
||||
.arg(script_path.as_str());
|
||||
let output = command
|
||||
.output()
|
||||
.inspect_err(|e| {
|
||||
tracing::info!(
|
||||
"failed to run `uv sync --output-format=json --script {script_path}`: {e}"
|
||||
);
|
||||
})
|
||||
.ok()?;
|
||||
let metadata: UvMetadata = serde_json::from_slice(&output.stdout)
|
||||
.inspect_err(|e| {
|
||||
tracing::info!(
|
||||
"failed to parse `uv sync --output-format=json --script {script_path}`: {e}"
|
||||
);
|
||||
})
|
||||
.ok()?;
|
||||
let env_path = metadata.sync?.environment?.path?;
|
||||
let utf8_path = Utf8PathBuf::from(env_path);
|
||||
Some(SystemPathBuf::from_utf8_path_buf(utf8_path))
|
||||
}
|
||||
|
||||
impl FromStr for Pep723Metadata {
|
||||
type Err = toml::de::Error;
|
||||
|
||||
/// Parse `Pep723Metadata` from a raw TOML string.
|
||||
fn from_str(raw: &str) -> Result<Self, Self::Err> {
|
||||
let metadata = toml::from_str(raw)?;
|
||||
Ok(Self {
|
||||
raw: raw.to_string(),
|
||||
..metadata
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -37,16 +37,14 @@ class MDTestRunner:
|
||||
mdtest_executable: Path | None
|
||||
console: Console
|
||||
filters: list[str]
|
||||
enable_external: bool
|
||||
|
||||
def __init__(self, filters: list[str] | None, enable_external: bool) -> None:
|
||||
def __init__(self, filters: list[str] | None = None) -> None:
|
||||
self.mdtest_executable = None
|
||||
self.console = Console()
|
||||
self.filters = [
|
||||
f.removesuffix(".md").replace("/", "_").replace("-", "_")
|
||||
for f in (filters or [])
|
||||
]
|
||||
self.enable_external = enable_external
|
||||
|
||||
def _run_cargo_test(self, *, message_format: Literal["human", "json"]) -> str:
|
||||
return subprocess.check_output(
|
||||
@@ -122,7 +120,6 @@ class MDTestRunner:
|
||||
CLICOLOR_FORCE="1",
|
||||
INSTA_FORCE_PASS="1",
|
||||
INSTA_OUTPUT="none",
|
||||
MDTEST_EXTERNAL="1" if self.enable_external else "0",
|
||||
),
|
||||
capture_output=capture_output,
|
||||
text=True,
|
||||
@@ -269,19 +266,11 @@ def main() -> None:
|
||||
nargs="*",
|
||||
help="Partial paths or mangled names, e.g., 'loops/for.md' or 'loops_for'",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--enable-external",
|
||||
"-e",
|
||||
action="store_true",
|
||||
help="Enable tests with external dependencies",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
runner = MDTestRunner(
|
||||
filters=args.filters, enable_external=args.enable_external
|
||||
)
|
||||
runner = MDTestRunner(filters=args.filters)
|
||||
runner.watch()
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from typing import Protocol
|
||||
|
||||
class A(Protocol):
|
||||
@property
|
||||
def f(self): ...
|
||||
|
||||
type Recursive = int | tuple[Recursive, ...]
|
||||
|
||||
class B[T: A]: ...
|
||||
|
||||
class C[T: A](A):
|
||||
x: tuple[Recursive, ...]
|
||||
|
||||
class D(B[C]): ...
|
||||
@@ -1,4 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
class MyClass:
|
||||
type: type = str
|
||||
@@ -1,6 +0,0 @@
|
||||
# This is a regression test for `store_expression_type`.
|
||||
# ref: https://github.com/astral-sh/ty/issues/1688
|
||||
|
||||
x: int
|
||||
|
||||
type x[T] = x[T, U]
|
||||
@@ -1,6 +0,0 @@
|
||||
class C[T: (A, B)]:
|
||||
def f(foo: T):
|
||||
try:
|
||||
pass
|
||||
except foo:
|
||||
pass
|
||||
@@ -169,13 +169,13 @@ def f(x: Any[int]):
|
||||
`Any` cannot be called (this leads to a `TypeError` at runtime):
|
||||
|
||||
```py
|
||||
Any() # error: [call-non-callable] "Object of type `<special form 'typing.Any'>` is not callable"
|
||||
Any() # error: [call-non-callable] "Object of type `typing.Any` is not callable"
|
||||
```
|
||||
|
||||
`Any` also cannot be used as a metaclass (under the hood, this leads to an implicit call to `Any`):
|
||||
|
||||
```py
|
||||
class F(metaclass=Any): ... # error: [invalid-metaclass] "Metaclass type `<special form 'typing.Any'>` is not callable"
|
||||
class F(metaclass=Any): ... # error: [invalid-metaclass] "Metaclass type `typing.Any` is not callable"
|
||||
```
|
||||
|
||||
And `Any` cannot be used in `isinstance()` checks:
|
||||
|
||||
@@ -307,10 +307,12 @@ Using a `ParamSpec` in a `Callable` annotation:
|
||||
from typing_extensions import Callable
|
||||
|
||||
def _[**P1](c: Callable[P1, int]):
|
||||
reveal_type(P1.args) # revealed: P1@_.args
|
||||
reveal_type(P1.kwargs) # revealed: P1@_.kwargs
|
||||
# TODO: Should reveal `ParamSpecArgs` and `ParamSpecKwargs`
|
||||
reveal_type(P1.args) # revealed: @Todo(ParamSpecArgs / ParamSpecKwargs)
|
||||
reveal_type(P1.kwargs) # revealed: @Todo(ParamSpecArgs / ParamSpecKwargs)
|
||||
|
||||
reveal_type(c) # revealed: (**P1@_) -> int
|
||||
# TODO: Signature should be (**P1) -> int
|
||||
reveal_type(c) # revealed: (...) -> int
|
||||
```
|
||||
|
||||
And, using the legacy syntax:
|
||||
@@ -320,8 +322,9 @@ from typing_extensions import ParamSpec
|
||||
|
||||
P2 = ParamSpec("P2")
|
||||
|
||||
# TODO: argument list should not be `...` (requires `ParamSpec` support)
|
||||
def _(c: Callable[P2, int]):
|
||||
reveal_type(c) # revealed: (**P2@_) -> int
|
||||
reveal_type(c) # revealed: (...) -> int
|
||||
```
|
||||
|
||||
## Using `typing.Unpack`
|
||||
|
||||
@@ -59,7 +59,7 @@ python-version = "3.11"
|
||||
```py
|
||||
from typing import Never
|
||||
|
||||
reveal_type(Never) # revealed: <special form 'typing.Never'>
|
||||
reveal_type(Never) # revealed: typing.Never
|
||||
```
|
||||
|
||||
### Python 3.10
|
||||
|
||||
@@ -18,8 +18,9 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||
|
||||
def g() -> TypeGuard[int]: ...
|
||||
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
|
||||
reveal_type(args) # revealed: P@i.args
|
||||
reveal_type(kwargs) # revealed: P@i.kwargs
|
||||
# TODO: Should reveal a type representing `P.args` and `P.kwargs`
|
||||
reveal_type(args) # revealed: tuple[@Todo(ParamSpecArgs / ParamSpecKwargs), ...]
|
||||
reveal_type(kwargs) # revealed: dict[str, @Todo(ParamSpecArgs / ParamSpecKwargs)]
|
||||
return callback(42, *args, **kwargs)
|
||||
|
||||
class Foo:
|
||||
@@ -64,9 +65,8 @@ def _(
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-form] "Variable of type `ParamSpec` is not allowed in a type expression"
|
||||
def foo(a_: e) -> None:
|
||||
reveal_type(a_) # revealed: Unknown
|
||||
reveal_type(a_) # revealed: @Todo(Support for `typing.ParamSpec`)
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
@@ -542,8 +542,7 @@ e: list[Any] | None = [1]
|
||||
reveal_type(e) # revealed: list[Any]
|
||||
|
||||
f: list[Any] | None = f2(1)
|
||||
# TODO: Better constraint solver.
|
||||
reveal_type(f) # revealed: list[int] | None
|
||||
reveal_type(f) # revealed: list[Any] | None
|
||||
|
||||
g: list[Any] | dict[Any, Any] = f3(1)
|
||||
# TODO: Better constraint solver.
|
||||
@@ -600,6 +599,48 @@ reveal_type(x7) # revealed: Contravariant[Any]
|
||||
reveal_type(x8) # revealed: Invariant[Any]
|
||||
```
|
||||
|
||||
## Declared type preference sees through subtyping
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Any, Iterable, Literal, MutableSequence, Sequence
|
||||
|
||||
x1: Sequence[Any] = [1, 2, 3]
|
||||
reveal_type(x1) # revealed: list[Any]
|
||||
|
||||
x2: MutableSequence[Any] = [1, 2, 3]
|
||||
reveal_type(x2) # revealed: list[Any]
|
||||
|
||||
x3: Iterable[Any] = [1, 2, 3]
|
||||
reveal_type(x3) # revealed: list[Any]
|
||||
|
||||
class X[T]:
|
||||
value: T
|
||||
|
||||
def __init__(self, value: T): ...
|
||||
|
||||
class A[T](X[T]): ...
|
||||
|
||||
def a[T](value: T) -> A[T]:
|
||||
return A(value)
|
||||
|
||||
x4: A[object] = A(1)
|
||||
reveal_type(x4) # revealed: A[object]
|
||||
|
||||
x5: X[object] = A(1)
|
||||
reveal_type(x5) # revealed: A[object]
|
||||
|
||||
x6: X[object] | None = A(1)
|
||||
reveal_type(x6) # revealed: A[object]
|
||||
|
||||
x7: X[object] | None = a(1)
|
||||
reveal_type(x7) # revealed: A[object]
|
||||
```
|
||||
|
||||
## Narrow generic unions
|
||||
|
||||
```toml
|
||||
|
||||
@@ -13,7 +13,7 @@ python-version = "3.10"
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
reveal_type(A | B) # revealed: <types.UnionType special form 'A | B'>
|
||||
reveal_type(A | B) # revealed: types.UnionType
|
||||
```
|
||||
|
||||
## Union of two classes (prior to 3.10)
|
||||
@@ -43,14 +43,14 @@ class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(sub_a: type[A], sub_b: type[B]):
|
||||
reveal_type(A | sub_b) # revealed: <types.UnionType special form>
|
||||
reveal_type(sub_a | B) # revealed: <types.UnionType special form>
|
||||
reveal_type(sub_a | sub_b) # revealed: <types.UnionType special form>
|
||||
reveal_type(A | sub_b) # revealed: types.UnionType
|
||||
reveal_type(sub_a | B) # revealed: types.UnionType
|
||||
reveal_type(sub_a | sub_b) # revealed: types.UnionType
|
||||
|
||||
class C[T]: ...
|
||||
class D[T]: ...
|
||||
|
||||
reveal_type(C | D) # revealed: <types.UnionType special form 'C[Unknown] | D[Unknown]'>
|
||||
reveal_type(C | D) # revealed: types.UnionType
|
||||
|
||||
reveal_type(C[int] | D[str]) # revealed: <types.UnionType special form 'C[int] | D[str]'>
|
||||
reveal_type(C[int] | D[str]) # revealed: types.UnionType
|
||||
```
|
||||
|
||||
@@ -227,56 +227,17 @@ def _(literals_2: Literal[0, 1], b: bool, flag: bool):
|
||||
literals_16 = 4 * literals_4 + literals_4 # Literal[0, 1, .., 15]
|
||||
literals_64 = 4 * literals_16 + literals_4 # Literal[0, 1, .., 63]
|
||||
literals_128 = 2 * literals_64 + literals_2 # Literal[0, 1, .., 127]
|
||||
literals_256 = 2 * literals_128 + literals_2 # Literal[0, 1, .., 255]
|
||||
|
||||
# Going beyond the MAX_NON_RECURSIVE_UNION_LITERALS limit (currently 256):
|
||||
reveal_type(literals_256 if flag else 256) # revealed: int
|
||||
# Going beyond the MAX_UNION_LITERALS limit (currently 200):
|
||||
literals_256 = 16 * literals_16 + literals_16
|
||||
reveal_type(literals_256) # revealed: int
|
||||
|
||||
# Going beyond the limit when another type is already part of the union
|
||||
bool_and_literals_128 = b if flag else literals_128 # bool | Literal[0, 1, ..., 127]
|
||||
literals_128_shifted = literals_128 + 128 # Literal[128, 129, ..., 255]
|
||||
literals_256_shifted = literals_256 + 256 # Literal[256, 257, ..., 511]
|
||||
|
||||
# Now union the two:
|
||||
two = bool_and_literals_128 if flag else literals_128_shifted
|
||||
# revealed: bool | Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]
|
||||
reveal_type(two)
|
||||
reveal_type(two if flag else literals_256_shifted) # revealed: int
|
||||
```
|
||||
|
||||
Recursively defined literal union types are widened earlier than non-recursively defined types for
|
||||
faster convergence.
|
||||
|
||||
```py
|
||||
class RecursiveAttr:
|
||||
def __init__(self):
|
||||
self.i = 0
|
||||
|
||||
def update(self):
|
||||
self.i = self.i + 1
|
||||
|
||||
reveal_type(RecursiveAttr().i) # revealed: Unknown | int
|
||||
|
||||
# Here are some recursive but saturating examples. Because it's difficult to statically determine whether literal unions saturate or diverge,
|
||||
# we widen them early, even though they may actually be convergent.
|
||||
class RecursiveAttr2:
|
||||
def __init__(self):
|
||||
self.i = 0
|
||||
|
||||
def update(self):
|
||||
self.i = (self.i + 1) % 9
|
||||
|
||||
reveal_type(RecursiveAttr2().i) # revealed: Unknown | Literal[0, 1, 2, 3, 4, 5, 6, 7, 8]
|
||||
|
||||
class RecursiveAttr3:
|
||||
def __init__(self):
|
||||
self.i = 0
|
||||
|
||||
def update(self):
|
||||
self.i = (self.i + 1) % 10
|
||||
|
||||
# Going beyond the MAX_RECURSIVE_UNION_LITERALS limit:
|
||||
reveal_type(RecursiveAttr3().i) # revealed: Unknown | int
|
||||
reveal_type(bool_and_literals_128 if flag else literals_128_shifted) # revealed: int
|
||||
```
|
||||
|
||||
## Simplifying gradually-equivalent types
|
||||
|
||||
@@ -603,14 +603,12 @@ super(object, object()).__class__
|
||||
# Not all objects valid in a class's bases list are valid as the first argument to `super()`.
|
||||
# For example, it's valid to inherit from `typing.ChainMap`, but it's not valid as the first argument to `super()`.
|
||||
#
|
||||
# error: [invalid-super-argument] "`<special form 'typing.ChainMap'>` is not a valid class"
|
||||
# error: [invalid-super-argument] "`typing.ChainMap` is not a valid class"
|
||||
reveal_type(super(typing.ChainMap, collections.ChainMap())) # revealed: Unknown
|
||||
|
||||
# Meanwhile, it's not valid to inherit from unsubscripted `typing.Generic`,
|
||||
# but it *is* valid as the first argument to `super()`.
|
||||
#
|
||||
# revealed: <super: <special form 'typing.Generic'>, <class 'SupportsInt'>>
|
||||
reveal_type(super(typing.Generic, typing.SupportsInt))
|
||||
reveal_type(super(typing.Generic, typing.SupportsInt)) # revealed: <super: typing.Generic, <class 'SupportsInt'>>
|
||||
|
||||
def _(x: type[typing.Any], y: typing.Any):
|
||||
reveal_type(super(x, y)) # revealed: <super: Any, Any>
|
||||
|
||||
@@ -1462,57 +1462,3 @@ def test_c():
|
||||
c = C(1)
|
||||
c.__lt__ = Mock()
|
||||
```
|
||||
|
||||
## Imperatively calling `dataclasses.dataclass`
|
||||
|
||||
While we do not currently recognize the special behaviour of `dataclasses.dataclass` if it is called
|
||||
imperatively, we recognize that it can be called imperatively and do not emit any false-positive
|
||||
diagnostics on such calls:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import TypeVar, dataclass_transform
|
||||
|
||||
U = TypeVar("U")
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def sequence(cls: type[U]) -> type[U]:
|
||||
d = dataclass(
|
||||
repr=False,
|
||||
eq=False,
|
||||
match_args=False,
|
||||
kw_only=True,
|
||||
)(cls)
|
||||
reveal_type(d) # revealed: type[U@sequence] & Any
|
||||
return d
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def sequence2(cls: type) -> type:
|
||||
d = dataclass(
|
||||
repr=False,
|
||||
eq=False,
|
||||
match_args=False,
|
||||
kw_only=True,
|
||||
)(cls)
|
||||
reveal_type(d) # revealed: type & Any
|
||||
return d
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def sequence3(cls: type[U]) -> type[U]:
|
||||
# TODO: should reveal `type[U@sequence3]`
|
||||
return reveal_type(dataclass(cls)) # revealed: Unknown
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def sequence4(cls: type) -> type:
|
||||
# TODO: should reveal `type`
|
||||
return reveal_type(dataclass(cls)) # revealed: Unknown
|
||||
|
||||
class Foo: ...
|
||||
|
||||
ordered_foo = dataclass(order=True)(Foo)
|
||||
reveal_type(ordered_foo) # revealed: type[Foo] & Any
|
||||
# TODO: should be `Foo & Any`
|
||||
reveal_type(ordered_foo()) # revealed: @Todo(Type::Intersection.call)
|
||||
# TODO: should be `Any`
|
||||
reveal_type(ordered_foo() < ordered_foo()) # revealed: @Todo(Type::Intersection.call)
|
||||
```
|
||||
|
||||
@@ -143,10 +143,6 @@ await C()
|
||||
def f():
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await C()
|
||||
|
||||
(await cor async for cor in f()) # ok
|
||||
(await cor for cor in f()) # ok
|
||||
([await c for c in cor] async for cor in f()) # ok
|
||||
```
|
||||
|
||||
Generators are evaluated lazily, so `await` is allowed, even outside of a function.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Diagnostics for invalid attribute access on special forms
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import Any, Final, LiteralString, Self
|
||||
|
||||
X = Any
|
||||
|
||||
class Foo:
|
||||
X: Final = LiteralString
|
||||
a: int
|
||||
b: Self
|
||||
|
||||
class Bar:
|
||||
def __init__(self):
|
||||
self.y: Final = LiteralString
|
||||
|
||||
X.foo # error: [unresolved-attribute]
|
||||
X.aaaaooooooo # error: [unresolved-attribute]
|
||||
Foo.X.startswith # error: [unresolved-attribute]
|
||||
Foo.Bar().y.startswith # error: [unresolved-attribute]
|
||||
|
||||
# TODO: false positive (just testing the diagnostic in the meantime)
|
||||
Foo().b.a # error: [unresolved-attribute]
|
||||
```
|
||||
@@ -7,11 +7,10 @@
|
||||
```py
|
||||
from typing_extensions import assert_type
|
||||
|
||||
def _(x: int, y: bool):
|
||||
def _(x: int):
|
||||
assert_type(x, int) # fine
|
||||
assert_type(x, str) # error: [type-assertion-failure]
|
||||
assert_type(assert_type(x, int), int)
|
||||
assert_type(y, int) # error: [type-assertion-failure]
|
||||
```
|
||||
|
||||
## Narrowing
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# mdtests with external dependencies
|
||||
|
||||
This directory contains mdtests that make use of external packages. See the mdtest `README.md` for
|
||||
more information.
|
||||
@@ -1,78 +0,0 @@
|
||||
# attrs
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
python-platform = "linux"
|
||||
|
||||
[project]
|
||||
dependencies = ["attrs==25.4.0"]
|
||||
```
|
||||
|
||||
## Basic class (`attr`)
|
||||
|
||||
```py
|
||||
import attr
|
||||
|
||||
@attr.s
|
||||
class User:
|
||||
id: int = attr.ib()
|
||||
name: str = attr.ib()
|
||||
|
||||
user = User(id=1, name="John Doe")
|
||||
|
||||
reveal_type(user.id) # revealed: int
|
||||
reveal_type(user.name) # revealed: str
|
||||
```
|
||||
|
||||
## Basic class (`define`)
|
||||
|
||||
```py
|
||||
from attrs import define, field
|
||||
|
||||
@define
|
||||
class User:
|
||||
id: int = field()
|
||||
internal_name: str = field(alias="name")
|
||||
|
||||
user = User(id=1, name="John Doe")
|
||||
reveal_type(user.id) # revealed: int
|
||||
reveal_type(user.internal_name) # revealed: str
|
||||
```
|
||||
|
||||
## Usage of `field` parameters
|
||||
|
||||
```py
|
||||
from attrs import define, field
|
||||
|
||||
@define
|
||||
class Product:
|
||||
id: int = field(init=False)
|
||||
name: str = field()
|
||||
price_cent: int = field(kw_only=True)
|
||||
|
||||
reveal_type(Product.__init__) # revealed: (self: Product, name: str, *, price_cent: int) -> None
|
||||
```
|
||||
|
||||
## Dedicated support for the `default` decorator?
|
||||
|
||||
We currently do not support this:
|
||||
|
||||
```py
|
||||
from attrs import define, field
|
||||
|
||||
@define
|
||||
class Person:
|
||||
id: int = field()
|
||||
name: str = field()
|
||||
|
||||
# error: [call-non-callable] "Object of type `_MISSING_TYPE` is not callable"
|
||||
@id.default
|
||||
def _default_id(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `id`"
|
||||
person = Person(name="Alice")
|
||||
reveal_type(person.id) # revealed: int
|
||||
reveal_type(person.name) # revealed: str
|
||||
```
|
||||
@@ -1,23 +0,0 @@
|
||||
# numpy
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
python-platform = "linux"
|
||||
|
||||
[project]
|
||||
dependencies = ["numpy==2.3.0"]
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
```py
|
||||
import numpy as np
|
||||
|
||||
xs = np.array([1, 2, 3])
|
||||
reveal_type(xs) # revealed: ndarray[tuple[Any, ...], dtype[Any]]
|
||||
|
||||
xs = np.array([1.0, 2.0, 3.0], dtype=np.float64)
|
||||
# TODO: should be `ndarray[tuple[Any, ...], dtype[float64]]`
|
||||
reveal_type(xs) # revealed: ndarray[tuple[Any, ...], dtype[Unknown]]
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
# Pydantic
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
python-platform = "linux"
|
||||
|
||||
[project]
|
||||
dependencies = ["pydantic==2.12.2"]
|
||||
```
|
||||
|
||||
## Basic model
|
||||
|
||||
```py
|
||||
from pydantic import BaseModel
|
||||
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
reveal_type(User.__init__) # revealed: (self: User, *, id: int, name: str) -> None
|
||||
|
||||
user = User(id=1, name="John Doe")
|
||||
reveal_type(user.id) # revealed: int
|
||||
reveal_type(user.name) # revealed: str
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `name`"
|
||||
invalid_user = User(id=2)
|
||||
```
|
||||
|
||||
## Usage of `Field`
|
||||
|
||||
```py
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Product(BaseModel):
|
||||
id: int = Field(init=False)
|
||||
name: str = Field(..., kw_only=False, min_length=1)
|
||||
internal_price_cent: int = Field(..., gt=0, alias="price_cent")
|
||||
|
||||
reveal_type(Product.__init__) # revealed: (self: Product, name: str = Any, *, price_cent: int = Any) -> None
|
||||
|
||||
product = Product("Laptop", price_cent=999_00)
|
||||
|
||||
reveal_type(product.id) # revealed: int
|
||||
reveal_type(product.name) # revealed: str
|
||||
reveal_type(product.internal_price_cent) # revealed: int
|
||||
```
|
||||
@@ -1,27 +0,0 @@
|
||||
# pytest
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
python-platform = "linux"
|
||||
|
||||
[project]
|
||||
dependencies = ["pytest==9.0.1"]
|
||||
```
|
||||
|
||||
## `pytest.fail`
|
||||
|
||||
Make sure that we recognize `pytest.fail` calls as terminal:
|
||||
|
||||
```py
|
||||
import pytest
|
||||
|
||||
def some_runtime_condition() -> bool:
|
||||
return True
|
||||
|
||||
def test_something():
|
||||
if not some_runtime_condition():
|
||||
pytest.fail("Runtime condition failed")
|
||||
|
||||
no_error_here_this_is_unreachable
|
||||
```
|
||||
@@ -1,210 +0,0 @@
|
||||
# SQLAlchemy
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
python-platform = "linux"
|
||||
|
||||
[project]
|
||||
dependencies = ["SQLAlchemy==2.0.44"]
|
||||
```
|
||||
|
||||
## ORM Model
|
||||
|
||||
This test makes sure that ty understands SQLAlchemy's `dataclass_transform` setup:
|
||||
|
||||
```py
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, init=False)
|
||||
internal_name: Mapped[str] = mapped_column(alias="name")
|
||||
|
||||
user = User(name="John Doe")
|
||||
reveal_type(user.id) # revealed: int
|
||||
reveal_type(user.internal_name) # revealed: str
|
||||
```
|
||||
|
||||
Unfortunately, SQLAlchemy overrides `__init__` and explicitly accepts all combinations of keyword
|
||||
arguments. This is why we currently cannot flag invalid constructor calls:
|
||||
|
||||
```py
|
||||
reveal_type(User.__init__) # revealed: def __init__(self, **kw: Any) -> Unknown
|
||||
|
||||
# TODO: this should ideally be an error
|
||||
invalid_user = User(invalid_arg=42)
|
||||
```
|
||||
|
||||
## Basic query example
|
||||
|
||||
First, set up a `Session`:
|
||||
|
||||
```py
|
||||
from sqlalchemy import select, Integer, Text, Boolean
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
engine = create_engine("sqlite://example.db")
|
||||
session = Session(engine)
|
||||
```
|
||||
|
||||
And define a simple model:
|
||||
|
||||
```py
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(Text)
|
||||
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
```
|
||||
|
||||
Finally, we can execute queries:
|
||||
|
||||
```py
|
||||
stmt = select(User)
|
||||
reveal_type(stmt) # revealed: Select[tuple[User]]
|
||||
|
||||
users = session.scalars(stmt).all()
|
||||
reveal_type(users) # revealed: Sequence[User]
|
||||
|
||||
for row in session.execute(stmt):
|
||||
reveal_type(row) # revealed: Row[tuple[User]]
|
||||
|
||||
stmt = select(User).where(User.name == "Alice")
|
||||
alice1 = session.scalars(stmt).first()
|
||||
reveal_type(alice1) # revealed: User | None
|
||||
|
||||
alice2 = session.scalar(stmt)
|
||||
reveal_type(alice2) # revealed: User | None
|
||||
|
||||
result = session.execute(stmt)
|
||||
row = result.one_or_none()
|
||||
assert row is not None
|
||||
(alice3,) = row._tuple()
|
||||
reveal_type(alice3) # revealed: User
|
||||
```
|
||||
|
||||
This also works with more complex queries:
|
||||
|
||||
```py
|
||||
stmt = select(User).where(User.is_admin == True).order_by(User.name).limit(10)
|
||||
admin_users = session.scalars(stmt).all()
|
||||
reveal_type(admin_users) # revealed: Sequence[User]
|
||||
```
|
||||
|
||||
We can also specify particular columns to select:
|
||||
|
||||
```py
|
||||
stmt = select(User.id, User.name)
|
||||
# TODO: should be `Select[tuple[int, str]]`
|
||||
reveal_type(stmt) # revealed: Select[tuple[Unknown, Unknown]]
|
||||
|
||||
ids_and_names = session.execute(stmt).all()
|
||||
# TODO: should be `Sequence[Row[tuple[int, str]]]`
|
||||
reveal_type(ids_and_names) # revealed: Sequence[Row[tuple[Unknown, Unknown]]]
|
||||
|
||||
for row in session.execute(stmt):
|
||||
# TODO: should be `Row[tuple[int, str]]`
|
||||
reveal_type(row) # revealed: Row[tuple[Unknown, Unknown]]
|
||||
|
||||
for user_id, name in session.execute(stmt).tuples():
|
||||
# TODO: should be `int`
|
||||
reveal_type(user_id) # revealed: Unknown
|
||||
# TODO: should be `str`
|
||||
reveal_type(name) # revealed: Unknown
|
||||
|
||||
result = session.execute(stmt)
|
||||
row = result.one_or_none()
|
||||
assert row is not None
|
||||
(user_id, name) = row._tuple()
|
||||
# TODO: should be `int`
|
||||
reveal_type(user_id) # revealed: Unknown
|
||||
# TODO: should be `str`
|
||||
reveal_type(name) # revealed: Unknown
|
||||
|
||||
stmt = select(User.id).where(User.name == "Alice")
|
||||
|
||||
# TODO: should be `Select[tuple[int]]`
|
||||
reveal_type(stmt) # revealed: Select[tuple[Unknown]]
|
||||
|
||||
alice_id = session.scalars(stmt).first()
|
||||
# TODO: should be `int | None`
|
||||
reveal_type(alice_id) # revealed: Unknown | None
|
||||
|
||||
alice_id = session.scalar(stmt)
|
||||
# TODO: should be `int | None`
|
||||
reveal_type(alice_id) # revealed: Unknown | None
|
||||
```
|
||||
|
||||
Using the legacy `query` API also works:
|
||||
|
||||
```py
|
||||
users_legacy = session.query(User).all()
|
||||
reveal_type(users_legacy) # revealed: list[User]
|
||||
|
||||
query = session.query(User)
|
||||
reveal_type(query) # revealed: Query[User]
|
||||
|
||||
reveal_type(query.all()) # revealed: list[User]
|
||||
|
||||
for row in query:
|
||||
reveal_type(row) # revealed: User
|
||||
```
|
||||
|
||||
And similarly when specifying particular columns:
|
||||
|
||||
```py
|
||||
query = session.query(User.id, User.name)
|
||||
# TODO: should be `RowReturningQuery[tuple[int, str]]`
|
||||
reveal_type(query) # revealed: RowReturningQuery[tuple[Unknown, Unknown]]
|
||||
|
||||
# TODO: should be `list[Row[tuple[int, str]]]`
|
||||
reveal_type(query.all()) # revealed: list[Row[tuple[Unknown, Unknown]]]
|
||||
|
||||
for row in query:
|
||||
# TODO: should be `Row[tuple[int, str]]`
|
||||
reveal_type(row) # revealed: Row[tuple[Unknown, Unknown]]
|
||||
```
|
||||
|
||||
## Async API
|
||||
|
||||
The async API is supported as well:
|
||||
|
||||
```py
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, Integer, Text
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(Text)
|
||||
|
||||
async def test_async(session: AsyncSession):
|
||||
stmt = select(User).where(User.name == "Alice")
|
||||
alice = await session.scalar(stmt)
|
||||
reveal_type(alice) # revealed: User | None
|
||||
|
||||
stmt = select(User.id, User.name)
|
||||
result = await session.execute(stmt)
|
||||
for user_id, name in result.tuples():
|
||||
# TODO: should be `int`
|
||||
reveal_type(user_id) # revealed: Unknown
|
||||
# TODO: should be `str`
|
||||
reveal_type(name) # revealed: Unknown
|
||||
```
|
||||
@@ -1,30 +0,0 @@
|
||||
# SQLModel
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
python-platform = "linux"
|
||||
|
||||
[project]
|
||||
dependencies = ["sqlmodel==0.0.27"]
|
||||
```
|
||||
|
||||
## Basic model
|
||||
|
||||
```py
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
class User(SQLModel):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
user = User(id=1, name="John Doe")
|
||||
reveal_type(user.id) # revealed: int
|
||||
reveal_type(user.name) # revealed: str
|
||||
|
||||
# TODO: this should not mention `__pydantic_self__`, and have proper parameters defined by the fields
|
||||
reveal_type(User.__init__) # revealed: def __init__(__pydantic_self__, **data: Any) -> None
|
||||
|
||||
# TODO: this should be an error
|
||||
User()
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user