Compare commits
12 Commits
alex/into_
...
ibraheem/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f4dcbf651 | ||
|
|
1d6ae8596a | ||
|
|
cf4e82d4b0 | ||
|
|
1baf98aab3 | ||
|
|
3179b05221 | ||
|
|
172e8d4ae0 | ||
|
|
735ec0c1f9 | ||
|
|
3585c96ea5 | ||
|
|
4b026c2a55 | ||
|
|
4b758b3746 | ||
|
|
8737a2d5f5 | ||
|
|
3be3a10a2f |
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,5 +1,60 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.3
|
||||
|
||||
Released on 2025-10-30.
|
||||
|
||||
### Preview features
|
||||
|
||||
- Respect `--output-format` with `--watch` ([#21097](https://github.com/astral-sh/ruff/pull/21097))
|
||||
- \[`pydoclint`\] Fix false positive on explicit exception re-raising (`DOC501`, `DOC502`) ([#21011](https://github.com/astral-sh/ruff/pull/21011))
|
||||
- \[`pyflakes`\] Revert to stable behavior if imports for module lie in alternate branches for `F401` ([#20878](https://github.com/astral-sh/ruff/pull/20878))
|
||||
- \[`pylint`\] Implement `stop-iteration-return` (`PLR1708`) ([#20733](https://github.com/astral-sh/ruff/pull/20733))
|
||||
- \[`ruff`\] Add support for additional eager conversion patterns (`RUF065`) ([#20657](https://github.com/astral-sh/ruff/pull/20657))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix finding keyword range for clause header after statement ending with semicolon ([#21067](https://github.com/astral-sh/ruff/pull/21067))
|
||||
- Fix syntax error false positive on nested alternative patterns ([#21104](https://github.com/astral-sh/ruff/pull/21104))
|
||||
- \[`ISC001`\] Fix panic when string literals are unclosed ([#21034](https://github.com/astral-sh/ruff/pull/21034))
|
||||
- \[`flake8-django`\] Apply `DJ001` to annotated fields ([#20907](https://github.com/astral-sh/ruff/pull/20907))
|
||||
- \[`flake8-pyi`\] Fix `PYI034` to not trigger on metaclasses (`PYI034`) ([#20881](https://github.com/astral-sh/ruff/pull/20881))
|
||||
- \[`flake8-type-checking`\] Fix `TC003` false positive with `future-annotations` ([#21125](https://github.com/astral-sh/ruff/pull/21125))
|
||||
- \[`pyflakes`\] Fix false positive for `__class__` in lambda expressions within class definitions (`F821`) ([#20564](https://github.com/astral-sh/ruff/pull/20564))
|
||||
- \[`pyupgrade`\] Fix false positive for `TypeVar` with default on Python \<3.13 (`UP046`,`UP047`) ([#21045](https://github.com/astral-sh/ruff/pull/21045))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Add missing docstring sections to the numpy list ([#20931](https://github.com/astral-sh/ruff/pull/20931))
|
||||
- \[`airflow`\] Extend `airflow.models..Param` check (`AIR311`) ([#21043](https://github.com/astral-sh/ruff/pull/21043))
|
||||
- \[`airflow`\] Warn that `airflow....DAG.create_dagrun` has been removed (`AIR301`) ([#21093](https://github.com/astral-sh/ruff/pull/21093))
|
||||
- \[`refurb`\] Preserve digit separators in `Decimal` constructor (`FURB157`) ([#20588](https://github.com/astral-sh/ruff/pull/20588))
|
||||
|
||||
### Server
|
||||
|
||||
- Avoid sending an unnecessary "clear diagnostics" message for clients supporting pull diagnostics ([#21105](https://github.com/astral-sh/ruff/pull/21105))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-bandit`\] Fix correct example for `S308` ([#21128](https://github.com/astral-sh/ruff/pull/21128))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Clearer error message when `line-length` goes beyond threshold ([#21072](https://github.com/astral-sh/ruff/pull/21072))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@jvacek](https://github.com/jvacek)
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@augustelalande](https://github.com/augustelalande)
|
||||
- [@prakhar1144](https://github.com/prakhar1144)
|
||||
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||
- [@dylwil3](https://github.com/dylwil3)
|
||||
- [@fatelei](https://github.com/fatelei)
|
||||
- [@ShaharNaveh](https://github.com/ShaharNaveh)
|
||||
- [@Lee-W](https://github.com/Lee-W)
|
||||
|
||||
## 0.14.2
|
||||
|
||||
Released on 2025-10-23.
|
||||
|
||||
44
Cargo.lock
generated
44
Cargo.lock
generated
@@ -243,7 +243,7 @@ dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -633,7 +633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -642,7 +642,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1007,7 +1007,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1093,7 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1115,13 +1115,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "etcetera"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6"
|
||||
checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"home",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1366,15 +1365,6 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html-escape"
|
||||
version = "0.2.13"
|
||||
@@ -1563,7 +1553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -1690,7 +1680,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1754,7 +1744,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2835,7 +2825,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3092,7 +3082,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3447,7 +3437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3545,7 +3535,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3941,7 +3931,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5021,7 +5011,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -84,7 +84,7 @@ dashmap = { version = "6.0.1" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
dunce = { version = "1.0.5" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
etcetera = { version = "0.10.0" }
|
||||
etcetera = { version = "0.11.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
|
||||
@@ -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.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.2/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.3/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.3/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.2
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -470,6 +470,11 @@ impl File {
|
||||
self.source_type(db).is_stub()
|
||||
}
|
||||
|
||||
/// Returns `true` if the file is an `__init__.pyi`
|
||||
pub fn is_package_stub(self, db: &dyn Db) -> bool {
|
||||
self.path(db).as_str().ends_with("__init__.pyi")
|
||||
}
|
||||
|
||||
pub fn source_type(self, db: &dyn Db) -> PySourceType {
|
||||
match self.path(db) {
|
||||
FilePath::System(path) => path
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use lsp_types::Url;
|
||||
|
||||
use crate::{
|
||||
Session,
|
||||
lint::DiagnosticsMap,
|
||||
session::{Client, DocumentQuery, DocumentSnapshot},
|
||||
};
|
||||
@@ -22,21 +19,10 @@ pub(super) fn generate_diagnostics(snapshot: &DocumentSnapshot) -> DiagnosticsMa
|
||||
}
|
||||
|
||||
pub(super) fn publish_diagnostics_for_document(
|
||||
session: &Session,
|
||||
url: &Url,
|
||||
snapshot: &DocumentSnapshot,
|
||||
client: &Client,
|
||||
) -> crate::server::Result<()> {
|
||||
// Publish diagnostics if the client doesn't support pull diagnostics
|
||||
if session.resolved_client_capabilities().pull_diagnostics {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let snapshot = session
|
||||
.take_snapshot(url.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("Unable to take snapshot for document with URL {url}"))
|
||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||
|
||||
for (uri, diagnostics) in generate_diagnostics(&snapshot) {
|
||||
for (uri, diagnostics) in generate_diagnostics(snapshot) {
|
||||
client
|
||||
.send_notification::<lsp_types::notification::PublishDiagnostics>(
|
||||
lsp_types::PublishDiagnosticsParams {
|
||||
@@ -52,14 +38,9 @@ pub(super) fn publish_diagnostics_for_document(
|
||||
}
|
||||
|
||||
pub(super) fn clear_diagnostics_for_document(
|
||||
session: &Session,
|
||||
query: &DocumentQuery,
|
||||
client: &Client,
|
||||
) -> crate::server::Result<()> {
|
||||
if session.resolved_client_capabilities().pull_diagnostics {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
client
|
||||
.send_notification::<lsp_types::notification::PublishDiagnostics>(
|
||||
lsp_types::PublishDiagnosticsParams {
|
||||
|
||||
@@ -31,7 +31,11 @@ impl super::SyncNotificationHandler for DidChange {
|
||||
.update_text_document(&key, content_changes, new_version)
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
|
||||
publish_diagnostics_for_document(session, &key.into_url(), client)?;
|
||||
// Publish diagnostics if the client doesn't support pull diagnostics
|
||||
if !session.resolved_client_capabilities().pull_diagnostics {
|
||||
let snapshot = session.take_snapshot(key.into_url()).unwrap();
|
||||
publish_diagnostics_for_document(&snapshot, client)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@ impl super::SyncNotificationHandler for DidChangeNotebook {
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
|
||||
// publish new diagnostics
|
||||
publish_diagnostics_for_document(session, &key.into_url(), client)?;
|
||||
let snapshot = session
|
||||
.take_snapshot(key.into_url())
|
||||
.expect("snapshot should be available");
|
||||
publish_diagnostics_for_document(&snapshot, client)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -31,13 +31,19 @@ impl super::SyncNotificationHandler for DidChangeWatchedFiles {
|
||||
} else {
|
||||
// publish diagnostics for text documents
|
||||
for url in session.text_document_urls() {
|
||||
publish_diagnostics_for_document(session, url, client)?;
|
||||
let snapshot = session
|
||||
.take_snapshot(url.clone())
|
||||
.expect("snapshot should be available");
|
||||
publish_diagnostics_for_document(&snapshot, client)?;
|
||||
}
|
||||
}
|
||||
|
||||
// always publish diagnostics for notebook files (since they don't use pull diagnostics)
|
||||
for url in session.notebook_document_urls() {
|
||||
publish_diagnostics_for_document(session, url, client)?;
|
||||
let snapshot = session
|
||||
.take_snapshot(url.clone())
|
||||
.expect("snapshot should be available");
|
||||
publish_diagnostics_for_document(&snapshot, client)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ impl super::SyncNotificationHandler for DidClose {
|
||||
);
|
||||
return Ok(());
|
||||
};
|
||||
clear_diagnostics_for_document(session, snapshot.query(), client)?;
|
||||
clear_diagnostics_for_document(snapshot.query(), client)?;
|
||||
|
||||
session
|
||||
.close_document(&key)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::TextDocument;
|
||||
use crate::server::Result;
|
||||
use crate::server::api::LSPResult;
|
||||
use crate::server::api::diagnostics::publish_diagnostics_for_document;
|
||||
use crate::session::{Client, Session};
|
||||
use lsp_types as types;
|
||||
@@ -29,7 +30,16 @@ impl super::SyncNotificationHandler for DidOpen {
|
||||
|
||||
session.open_text_document(uri.clone(), document);
|
||||
|
||||
publish_diagnostics_for_document(session, &uri, client)?;
|
||||
// Publish diagnostics if the client doesn't support pull diagnostics
|
||||
if !session.resolved_client_capabilities().pull_diagnostics {
|
||||
let snapshot = session
|
||||
.take_snapshot(uri.clone())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Unable to take snapshot for document with URL {uri}")
|
||||
})
|
||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||
publish_diagnostics_for_document(&snapshot, client)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ impl super::SyncNotificationHandler for DidOpenNotebook {
|
||||
session.open_notebook_document(uri.clone(), notebook);
|
||||
|
||||
// publish diagnostics
|
||||
publish_diagnostics_for_document(session, &uri, client)?;
|
||||
let snapshot = session
|
||||
.take_snapshot(uri)
|
||||
.expect("snapshot should be available");
|
||||
publish_diagnostics_for_document(&snapshot, client)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
110
crates/ty/docs/rules.md
generated
110
crates/ty/docs/rules.md
generated
@@ -474,7 +474,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#L598" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L606" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -501,7 +501,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#L638" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L646" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +529,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#L1749" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1757" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -563,7 +563,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#L660" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -599,7 +599,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#L690" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L698" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -623,7 +623,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#L741" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L749" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -650,7 +650,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#L762" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L770" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -679,7 +679,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#L785" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L793" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -723,7 +723,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.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#L821" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L829" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -762,11 +762,15 @@ Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for subscript accesses with invalid keys.
|
||||
Checks for subscript accesses with invalid keys and `TypedDict` construction with an
|
||||
unknown key.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Using an invalid key will raise a `KeyError` at runtime.
|
||||
Subscripting with an invalid key will raise a `KeyError` at runtime.
|
||||
|
||||
Creating a `TypedDict` with an unknown key is likely a mistake; if the `TypedDict` is
|
||||
`closed=true` it also violates the expectations of the type.
|
||||
|
||||
**Examples**
|
||||
|
||||
@@ -779,6 +783,10 @@ class Person(TypedDict):
|
||||
|
||||
alice = Person(name="Alice", age=30)
|
||||
alice["height"] # KeyError: 'height'
|
||||
|
||||
bob: Person = { "name": "Bob", "age": 30 } # typo!
|
||||
|
||||
carol = Person(name="Carol", age=25) # typo!
|
||||
```
|
||||
|
||||
## `invalid-legacy-type-variable`
|
||||
@@ -787,7 +795,7 @@ alice["height"] # KeyError: 'height'
|
||||
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#L847" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -822,7 +830,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#L896" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L904" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -888,7 +896,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||
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#L923" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L931" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -938,7 +946,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#L1022" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1030" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -998,7 +1006,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#L1042" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1050" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1047,7 +1055,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#L619" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1072,7 +1080,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#L1085" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1093" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1130,7 +1138,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#L875" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L883" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1157,7 +1165,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.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#L1124" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1132" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1187,7 +1195,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#L1148" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1156" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1217,7 +1225,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#L1200" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1251,7 +1259,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#L1172" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1180" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1285,7 +1293,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#L1228" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1236" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1320,7 +1328,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#L1257" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1265" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1345,7 +1353,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#L1850" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1858" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1378,7 +1386,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#L1276" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1284" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1407,7 +1415,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#L1299" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1307" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1431,7 +1439,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#L1317" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1325" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1457,7 +1465,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.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#L1368" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1376" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1484,7 +1492,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#L1603" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1611" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1542,7 +1550,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#L1725" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1733" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1572,7 +1580,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#L1459" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1467" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1601,7 +1609,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> ·
|
||||
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#L1504" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1512" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1628,7 +1636,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#L1482" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1490" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1656,7 +1664,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#L1525" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1533" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1702,7 +1710,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#L1582" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1590" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1729,7 +1737,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#L1624" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1632" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1757,7 +1765,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#L1646" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1654" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1782,7 +1790,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#L1665" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1673" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1807,7 +1815,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#L1337" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1345" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1844,7 +1852,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#L1684" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1692" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1872,7 +1880,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#L1706" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1714" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2026,7 +2034,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#L1389" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2086,7 +2094,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#L1411" 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>
|
||||
|
||||
|
||||
@@ -2118,7 +2126,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#L1777" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1785" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2145,7 +2153,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#L1564" 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>
|
||||
|
||||
|
||||
@@ -2169,7 +2177,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#L1798" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1806" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2227,7 +2235,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#L708" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L716" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2266,7 +2274,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#L966" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L974" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2353,7 +2361,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#L1437" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1445" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -212,7 +212,10 @@ pub fn completion<'db>(
|
||||
offset: TextSize,
|
||||
) -> Vec<Completion<'db>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
if is_in_comment(&parsed, offset) || is_in_string(&parsed, offset) {
|
||||
|
||||
let tokens = tokens_start_before(parsed.tokens(), offset);
|
||||
|
||||
if is_in_comment(tokens) || is_in_string(tokens) || is_in_definition_place(db, tokens, file) {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
@@ -829,8 +832,7 @@ fn find_typed_text(
|
||||
|
||||
/// Whether the given offset within the parsed module is within
|
||||
/// a comment or not.
|
||||
fn is_in_comment(parsed: &ParsedModuleRef, offset: TextSize) -> bool {
|
||||
let tokens = tokens_start_before(parsed.tokens(), offset);
|
||||
fn is_in_comment(tokens: &[Token]) -> bool {
|
||||
tokens.last().is_some_and(|t| t.kind().is_comment())
|
||||
}
|
||||
|
||||
@@ -839,8 +841,7 @@ fn is_in_comment(parsed: &ParsedModuleRef, offset: TextSize) -> bool {
|
||||
///
|
||||
/// Note that this will return `false` when positioned within an
|
||||
/// interpolation block in an f-string or a t-string.
|
||||
fn is_in_string(parsed: &ParsedModuleRef, offset: TextSize) -> bool {
|
||||
let tokens = tokens_start_before(parsed.tokens(), offset);
|
||||
fn is_in_string(tokens: &[Token]) -> bool {
|
||||
tokens.last().is_some_and(|t| {
|
||||
matches!(
|
||||
t.kind(),
|
||||
@@ -849,6 +850,31 @@ fn is_in_string(parsed: &ParsedModuleRef, offset: TextSize) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// If the tokens end with `class f` or `def f` we return true.
|
||||
/// If the tokens end with `class` or `def`, we return false.
|
||||
/// This is fine because we don't provide completions anyway.
|
||||
fn is_in_definition_place(db: &dyn Db, tokens: &[Token], file: File) -> bool {
|
||||
let is_definition_keyword = |token: &Token| {
|
||||
if matches!(
|
||||
token.kind(),
|
||||
TokenKind::Def | TokenKind::Class | TokenKind::Type
|
||||
) {
|
||||
true
|
||||
} else if token.kind() == TokenKind::Name {
|
||||
let source = source_text(db, file);
|
||||
&source[token.range()] == "type"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
tokens
|
||||
.len()
|
||||
.checked_sub(2)
|
||||
.and_then(|i| tokens.get(i))
|
||||
.is_some_and(is_definition_keyword)
|
||||
}
|
||||
|
||||
/// Order completions according to the following rules:
|
||||
///
|
||||
/// 1) Names with no underscore prefix
|
||||
@@ -4058,6 +4084,86 @@ def f[T](x: T):
|
||||
test.build().contains("__repr__");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_function_def_name() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def f<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert!(builder.auto_import().build().completions().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_in_function_def_empty_name() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def <CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
// This is okay because the ide will not request completions when the cursor is in this position.
|
||||
assert!(!builder.auto_import().build().completions().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_class_def_name() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
class f<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert!(builder.auto_import().build().completions().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_in_class_def_empty_name() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
class <CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
// This is okay because the ide will not request completions when the cursor is in this position.
|
||||
assert!(!builder.auto_import().build().completions().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_type_def_name() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
type f<CURSOR> = int
|
||||
",
|
||||
);
|
||||
|
||||
assert!(builder.auto_import().build().completions().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_maybe_type_def_name() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
type f<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert!(builder.auto_import().build().completions().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_in_type_def_empty_name() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
type <CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
// This is okay because the ide will not request completions when the cursor is in this position.
|
||||
assert!(!builder.auto_import().build().completions().is_empty());
|
||||
}
|
||||
|
||||
/// A way to create a simple single-file (named `main.py`) completion test
|
||||
/// builder.
|
||||
///
|
||||
|
||||
@@ -427,14 +427,13 @@ a = f("a")
|
||||
reveal_type(a) # revealed: list[Literal["a"]]
|
||||
|
||||
b: list[int | Literal["a"]] = f("a")
|
||||
reveal_type(b) # revealed: list[Literal["a"] | int]
|
||||
reveal_type(b) # revealed: list[int | Literal["a"]]
|
||||
|
||||
c: list[int | str] = f("a")
|
||||
reveal_type(c) # revealed: list[str | int]
|
||||
reveal_type(c) # revealed: list[int | str]
|
||||
|
||||
d: list[int | tuple[int, int]] = f((1, 2))
|
||||
# TODO: We could avoid reordering the union elements here.
|
||||
reveal_type(d) # revealed: list[tuple[int, int] | int]
|
||||
reveal_type(d) # revealed: list[int | tuple[int, int]]
|
||||
|
||||
e: list[int] = f(True)
|
||||
reveal_type(e) # revealed: list[int]
|
||||
@@ -455,7 +454,54 @@ j: int | str = f2(True)
|
||||
reveal_type(j) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
Types are not widened unnecessarily:
|
||||
## Prefer the declared type of generic classes
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
def f2[T](x: T) -> list[T] | None:
|
||||
return [x]
|
||||
|
||||
def f3[T](x: T) -> list[T] | dict[T, T]:
|
||||
return [x]
|
||||
|
||||
a = f(1)
|
||||
reveal_type(a) # revealed: list[Literal[1]]
|
||||
|
||||
b: list[Any] = f(1)
|
||||
reveal_type(b) # revealed: list[Any]
|
||||
|
||||
c: list[Any] = [1]
|
||||
reveal_type(c) # revealed: list[Any]
|
||||
|
||||
d: list[Any] | None = f(1)
|
||||
reveal_type(d) # revealed: list[Any]
|
||||
|
||||
e: list[Any] | None = [1]
|
||||
reveal_type(e) # revealed: list[Any]
|
||||
|
||||
f: list[Any] | None = f2(1)
|
||||
reveal_type(f) # revealed: list[Any] | None
|
||||
|
||||
g: list[Any] | dict[Any, Any] = f3(1)
|
||||
# TODO: Better constraint solver.
|
||||
reveal_type(g) # revealed: list[Literal[1]] | dict[Literal[1], Literal[1]]
|
||||
```
|
||||
|
||||
## Prefer the inferred type of non-generic classes
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
def id[T](x: T) -> T:
|
||||
|
||||
@@ -50,8 +50,6 @@ def _(l: list[int] | None = None):
|
||||
def f[T](x: T, cond: bool) -> T | list[T]:
|
||||
return x if cond else [x]
|
||||
|
||||
# TODO: no error
|
||||
# error: [invalid-assignment] "Object of type `Literal[1] | list[Literal[1]]` is not assignable to `int | list[int]`"
|
||||
l5: int | list[int] = f(1, True)
|
||||
```
|
||||
|
||||
|
||||
@@ -838,6 +838,40 @@ class WrappedIntAndExtraData[T](Wrap[int]):
|
||||
reveal_type(WrappedIntAndExtraData[bytes].__init__)
|
||||
```
|
||||
|
||||
### Non-dataclass inheriting from generic dataclass
|
||||
|
||||
This is a regression test for <https://github.com/astral-sh/ty/issues/1427>.
|
||||
|
||||
When a non-dataclass inherits from a generic dataclass, the generic type parameters should still be
|
||||
properly inferred when calling the inherited `__init__` method.
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class ParentDataclass[T]:
|
||||
value: T
|
||||
|
||||
# Non-dataclass inheriting from generic dataclass
|
||||
class ChildOfParentDataclass[T](ParentDataclass[T]): ...
|
||||
|
||||
def uses_dataclass[T](x: T) -> ChildOfParentDataclass[T]:
|
||||
return ChildOfParentDataclass(x)
|
||||
|
||||
# TODO: ParentDataclass.__init__ should show generic types, not Unknown
|
||||
# revealed: (self: ParentDataclass[Unknown], value: Unknown) -> None
|
||||
reveal_type(ParentDataclass.__init__)
|
||||
|
||||
# revealed: (self: ParentDataclass[T@ChildOfParentDataclass], value: T@ChildOfParentDataclass) -> None
|
||||
reveal_type(ChildOfParentDataclass.__init__)
|
||||
|
||||
result_int = uses_dataclass(42)
|
||||
reveal_type(result_int) # revealed: ChildOfParentDataclass[Literal[42]]
|
||||
|
||||
result_str = uses_dataclass("hello")
|
||||
reveal_type(result_str) # revealed: ChildOfParentDataclass[Literal["hello"]]
|
||||
```
|
||||
|
||||
## Descriptor-typed fields
|
||||
|
||||
### Same type in `__get__` and `__set__`
|
||||
|
||||
@@ -545,3 +545,28 @@ def f(x: T, y: Not[T]) -> T:
|
||||
y = x # error: [invalid-assignment]
|
||||
return x
|
||||
```
|
||||
|
||||
## Prefer exact matches for constrained typevars
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
class Base: ...
|
||||
class Sub(Base): ...
|
||||
|
||||
# We solve to `Sub`, regardless of the order of constraints.
|
||||
T = TypeVar("T", Base, Sub)
|
||||
T2 = TypeVar("T2", Sub, Base)
|
||||
|
||||
def f(x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
def f2(x: T2) -> list[T2]:
|
||||
return [x]
|
||||
|
||||
x: list[Sub] = f(Sub())
|
||||
reveal_type(x) # revealed: list[Sub]
|
||||
|
||||
y: list[Sub] = f2(Sub())
|
||||
reveal_type(y) # revealed: list[Sub]
|
||||
```
|
||||
|
||||
@@ -0,0 +1,824 @@
|
||||
# Nonstandard Import Conventions
|
||||
|
||||
This document covers ty-specific extensions to the
|
||||
[standard import conventions](https://typing.python.org/en/latest/spec/distributing.html#import-conventions).
|
||||
|
||||
It's a common idiom for a package's `__init__.py(i)` to include several imports like
|
||||
`from . import mysubmodule`, with the intent that the `mypackage.mysubmodule` attribute should work
|
||||
for anyone who only imports `mypackage`.
|
||||
|
||||
In the context of a `.py` we handle this well through our general attempts to faithfully implement
|
||||
import side-effects. However for `.pyi` files we are expected to apply
|
||||
[a more strict set of rules](https://typing.python.org/en/latest/spec/distributing.html#import-conventions)
|
||||
to encourage intentional API design. Although `.pyi` files are explicitly designed to work with
|
||||
typecheckers, which ostensibly should all enforce these strict rules, every typechecker has its own
|
||||
defacto "extensions" to them and so a few idioms like `from . import mysubmodule` have found their
|
||||
way into `.pyi` files too.
|
||||
|
||||
Thus for the sake of compatibility, we need to define our own "extensions". Any extensions we define
|
||||
here have several competing concerns:
|
||||
|
||||
- Extensions should ideally be kept narrow to continue to encourage explicit API design
|
||||
- Extensions should be easy to explain, document, and understand
|
||||
- Extensions should ideally still be a subset of runtime behaviour (if it works in a stub, it works
|
||||
at runtime)
|
||||
- Extensions should ideally not make `.pyi` files more permissive than `.py` files (if it works in a
|
||||
stub, it works in an impl)
|
||||
|
||||
To that end we define the following extension:
|
||||
|
||||
> If an `__init__.pyi` for `mypackage` contains a `from...import` targetting a direct submodule of
|
||||
> `mypackage`, then that submodule should be available as an attribute of `mypackage`.
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`
|
||||
|
||||
The `from . import submodule` idiom in an `__init__.pyi` is fairly explicit and we should definitely
|
||||
support it.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from . import imported
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`mypackage/fails.pyi`:
|
||||
|
||||
```pyi
|
||||
Y: int = 47
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__` (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from . import imported
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`mypackage/fails.py`:
|
||||
|
||||
```py
|
||||
Y: int = 47
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Absolute `from` Import of Direct Submodule in `__init__`
|
||||
|
||||
If an absolute `from...import` happens to import a submodule, it works just as well as a relative
|
||||
one.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from mypackage import imported
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`mypackage/fails.pyi`:
|
||||
|
||||
```pyi
|
||||
Y: int = 47
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Absolute `from` Import of Direct Submodule in `__init__` (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from mypackage import imported
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`mypackage/fails.py`:
|
||||
|
||||
```py
|
||||
Y: int = 47
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Import of Direct Submodule in `__init__`
|
||||
|
||||
An `import` that happens to import a submodule does not expose the submodule as an attribute. (This
|
||||
is an arbitrary decision and can be changed easily!)
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
import mypackage.imported
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this is probably safe to allow, as it's an unambiguous import of a submodule
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Import of Direct Submodule in `__init__` (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
import mypackage.imported
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this is probably safe to allow, as it's an unambiguous import of a submodule
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Relative `from` Import of Nested Submodule in `__init__`
|
||||
|
||||
`from .submodule import nested` in an `__init__.pyi` is currently not supported as a way to expose
|
||||
`mypackage.submodule` or `mypackage.submodule.nested` but it could be.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from .submodule import nested
|
||||
```
|
||||
|
||||
`mypackage/submodule/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
```
|
||||
|
||||
`mypackage/submodule/nested.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to allow
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Relative `from` Import of Nested Submodule in `__init__` (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from .submodule import nested
|
||||
```
|
||||
|
||||
`mypackage/submodule/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`mypackage/submodule/nested.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Absolute `from` Import of Nested Submodule in `__init__`
|
||||
|
||||
`from mypackage.submodule import nested` in an `__init__.pyi` is currently not supported as a way to
|
||||
expose `mypackage.submodule` or `mypackage.submodule.nested` but it could be.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from mypackage.submodule import nested
|
||||
```
|
||||
|
||||
`mypackage/submodule/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
```
|
||||
|
||||
`mypackage/submodule/nested.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Absolute `from` Import of Nested Submodule in `__init__` (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from mypackage.submodule import nested
|
||||
```
|
||||
|
||||
`mypackage/submodule/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`mypackage/submodule/nested.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Import of Nested Submodule in `__init__`
|
||||
|
||||
`import mypackage.submodule.nested` in an `__init__.pyi` is currently not supported as a way to
|
||||
expose `mypackage.submodule` or `mypackage.submodule.nested` but it could be.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
import mypackage.submodule.nested
|
||||
```
|
||||
|
||||
`mypackage/submodule/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
```
|
||||
|
||||
`mypackage/submodule/nested.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support, and is probably safe to do as it's unambiguous
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Import of Nested Submodule in `__init__` (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
import mypackage.submodule.nested
|
||||
```
|
||||
|
||||
`mypackage/submodule/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`mypackage/submodule/nested.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support, and is probably safe to do as it's unambiguous
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`, Mismatched Alias
|
||||
|
||||
Renaming the submodule to something else disables the `__init__.pyi` idiom.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from . import imported as imported_m
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: "has no member `imported_m`"
|
||||
reveal_type(mypackage.imported_m.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`, Mismatched Alias (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from . import imported as imported_m
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported_m.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`, Matched Alias
|
||||
|
||||
The `__init__.pyi` idiom should definitely always work if the submodule is renamed to itself, as
|
||||
this is the re-export idiom.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from . import imported as imported
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`, Matched Alias (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from . import imported as imported
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
## Star Import Unaffected
|
||||
|
||||
Even if the `__init__` idiom is in effect, star imports do not pick it up. (This is an arbitrary
|
||||
decision that mostly fell out of the implementation details and can be changed!)
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from . import imported
|
||||
Z: int = 17
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from mypackage import *
|
||||
|
||||
# TODO: this would be nice to support (available_submodule_attributes isn't visible to `*` imports)
|
||||
# error: "`imported` used when not defined"
|
||||
reveal_type(imported.X) # revealed: Unknown
|
||||
reveal_type(Z) # revealed: int
|
||||
```
|
||||
|
||||
## Star Import Unaffected (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from . import imported
|
||||
|
||||
Z: int = 17
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from mypackage import *
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
reveal_type(Z) # revealed: int
|
||||
```
|
||||
|
||||
## `from` Import of Non-Submodule
|
||||
|
||||
A from import that terminates in a non-submodule should not expose the intermediate submodules as
|
||||
attributes. This is an arbitrary decision but on balance probably safe and correct, as otherwise it
|
||||
would be hard for a stub author to be intentional about the submodules being exposed as attributes.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from .imported import X
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `from` Import of Non-Submodule (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from .imported import X
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `from` Import of Other Package's Submodule
|
||||
|
||||
`from mypackage import submodule` from outside the package is not modeled as a side-effect on
|
||||
`mypackage`, even in the importing file (this could be changed!).
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
# TODO: this would be nice to support, but it's dangerous with available_submodule_attributes
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `from` Import of Other Package's Submodule (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `from` Import of Sibling Module
|
||||
|
||||
`from . import submodule` from a sibling module is not modeled as a side-effect on `mypackage` or a
|
||||
re-export from `submodule`.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
```
|
||||
|
||||
`mypackage/imported.pyi`:
|
||||
|
||||
```pyi
|
||||
from . import fails
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`mypackage/fails.pyi`:
|
||||
|
||||
```pyi
|
||||
Y: int = 47
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(imported.fails.Y) # revealed: Unknown
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `from` Import of Sibling Module (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`mypackage/imported.py`:
|
||||
|
||||
```py
|
||||
from . import fails
|
||||
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`mypackage/fails.py`:
|
||||
|
||||
```py
|
||||
Y: int = 47
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
reveal_type(imported.fails.Y) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Fractal Re-export Nameclash Problems
|
||||
|
||||
This precise configuration of:
|
||||
|
||||
- a subpackage that defines a submodule with its own name
|
||||
- that in turn defines a function/class with its own name
|
||||
- and re-exporting that name through every layer using `from` imports and `__all__`
|
||||
|
||||
Can easily result in the typechecker getting "confused" and thinking imports of the name from the
|
||||
top-level package are referring to the subpackage and not the function/class. This issue can be
|
||||
found with the `lobpcg` function in `scipy.sparse.linalg`.
|
||||
|
||||
This kind of failure mode is why the rule is restricted to *direct* submodule imports, as anything
|
||||
more powerful than that in the current implementation strategy quickly gets the functions and
|
||||
submodules mixed up.
|
||||
|
||||
`mypackage/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from .funcmod import funcmod
|
||||
|
||||
__all__ = ["funcmod"]
|
||||
```
|
||||
|
||||
`mypackage/funcmod/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from .funcmod import funcmod
|
||||
|
||||
__all__ = ["funcmod"]
|
||||
```
|
||||
|
||||
`mypackage/funcmod/funcmod.pyi`:
|
||||
|
||||
```pyi
|
||||
__all__ = ["funcmod"]
|
||||
|
||||
def funcmod(x: int) -> int: ...
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from mypackage import funcmod
|
||||
|
||||
x = funcmod(1)
|
||||
```
|
||||
|
||||
## Fractal Re-export Nameclash Problems (Non-Stub Check)
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from .funcmod import funcmod
|
||||
|
||||
__all__ = ["funcmod"]
|
||||
```
|
||||
|
||||
`mypackage/funcmod/__init__.py`:
|
||||
|
||||
```py
|
||||
from .funcmod import funcmod
|
||||
|
||||
__all__ = ["funcmod"]
|
||||
```
|
||||
|
||||
`mypackage/funcmod/funcmod.py`:
|
||||
|
||||
```py
|
||||
__all__ = ["funcmod"]
|
||||
|
||||
def funcmod(x: int) -> int:
|
||||
return x
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from mypackage import funcmod
|
||||
|
||||
x = funcmod(1)
|
||||
```
|
||||
@@ -37,20 +37,24 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/typed_dict.md
|
||||
23 |
|
||||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||
25 | person[str_key] = "Alice" # error: [invalid-key]
|
||||
26 | from typing_extensions import ReadOnly
|
||||
27 |
|
||||
28 | class Employee(TypedDict):
|
||||
29 | id: ReadOnly[int]
|
||||
30 | name: str
|
||||
26 |
|
||||
27 | def create_with_invalid_string_key():
|
||||
28 | alice: Person = {"name": "Alice", "age": 30, "unknown": "Foo"} # error: [invalid-key]
|
||||
29 | bob = Person(name="Bob", age=25, unknown="Bar") # error: [invalid-key]
|
||||
30 | from typing_extensions import ReadOnly
|
||||
31 |
|
||||
32 | def write_to_readonly_key(employee: Employee):
|
||||
33 | employee["id"] = 42 # error: [invalid-assignment]
|
||||
32 | class Employee(TypedDict):
|
||||
33 | id: ReadOnly[int]
|
||||
34 | name: str
|
||||
35 |
|
||||
36 | def write_to_readonly_key(employee: Employee):
|
||||
37 | employee["id"] = 42 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key access on TypedDict `Person`
|
||||
error[invalid-key]: Invalid key for TypedDict `Person`
|
||||
--> src/mdtest_snippet.py:8:5
|
||||
|
|
||||
7 | def access_invalid_literal_string_key(person: Person):
|
||||
@@ -66,7 +70,7 @@ info: rule `invalid-key` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key access on TypedDict `Person`
|
||||
error[invalid-key]: Invalid key for TypedDict `Person`
|
||||
--> src/mdtest_snippet.py:13:5
|
||||
|
|
||||
12 | def access_invalid_key(person: Person):
|
||||
@@ -82,7 +86,7 @@ info: rule `invalid-key` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: TypedDict `Person` cannot be indexed with a key of type `str`
|
||||
error[invalid-key]: Invalid key for TypedDict `Person` of type `str`
|
||||
--> src/mdtest_snippet.py:16:12
|
||||
|
|
||||
15 | def access_with_str_key(person: Person, str_key: str):
|
||||
@@ -123,7 +127,7 @@ info: rule `invalid-assignment` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key access on TypedDict `Person`
|
||||
error[invalid-key]: Invalid key for TypedDict `Person`
|
||||
--> src/mdtest_snippet.py:22:5
|
||||
|
|
||||
21 | def write_to_non_existing_key(person: Person):
|
||||
@@ -145,7 +149,39 @@ error[invalid-key]: Cannot access `Person` with a key of type `str`. Only string
|
||||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||
25 | person[str_key] = "Alice" # error: [invalid-key]
|
||||
| ^^^^^^^
|
||||
26 | from typing_extensions import ReadOnly
|
||||
26 |
|
||||
27 | def create_with_invalid_string_key():
|
||||
|
|
||||
info: rule `invalid-key` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key for TypedDict `Person`
|
||||
--> src/mdtest_snippet.py:28:21
|
||||
|
|
||||
27 | def create_with_invalid_string_key():
|
||||
28 | alice: Person = {"name": "Alice", "age": 30, "unknown": "Foo"} # error: [invalid-key]
|
||||
| -----------------------------^^^^^^^^^--------
|
||||
| | |
|
||||
| | Unknown key "unknown"
|
||||
| TypedDict `Person`
|
||||
29 | bob = Person(name="Bob", age=25, unknown="Bar") # error: [invalid-key]
|
||||
30 | from typing_extensions import ReadOnly
|
||||
|
|
||||
info: rule `invalid-key` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key for TypedDict `Person`
|
||||
--> src/mdtest_snippet.py:29:11
|
||||
|
|
||||
27 | def create_with_invalid_string_key():
|
||||
28 | alice: Person = {"name": "Alice", "age": 30, "unknown": "Foo"} # error: [invalid-key]
|
||||
29 | bob = Person(name="Bob", age=25, unknown="Bar") # error: [invalid-key]
|
||||
| ------ TypedDict `Person` ^^^^^^^^^^^^^ Unknown key "unknown"
|
||||
30 | from typing_extensions import ReadOnly
|
||||
|
|
||||
info: rule `invalid-key` is enabled by default
|
||||
|
||||
@@ -153,21 +189,21 @@ info: rule `invalid-key` is enabled by default
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Cannot assign to key "id" on TypedDict `Employee`
|
||||
--> src/mdtest_snippet.py:33:5
|
||||
--> src/mdtest_snippet.py:37:5
|
||||
|
|
||||
32 | def write_to_readonly_key(employee: Employee):
|
||||
33 | employee["id"] = 42 # error: [invalid-assignment]
|
||||
36 | def write_to_readonly_key(employee: Employee):
|
||||
37 | employee["id"] = 42 # error: [invalid-assignment]
|
||||
| -------- ^^^^ key is marked read-only
|
||||
| |
|
||||
| TypedDict `Employee`
|
||||
|
|
||||
info: Item declaration
|
||||
--> src/mdtest_snippet.py:29:5
|
||||
--> src/mdtest_snippet.py:33:5
|
||||
|
|
||||
28 | class Employee(TypedDict):
|
||||
29 | id: ReadOnly[int]
|
||||
32 | class Employee(TypedDict):
|
||||
33 | id: ReadOnly[int]
|
||||
| ----------------- Read-only item declared here
|
||||
30 | name: str
|
||||
34 | name: str
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
|
||||
@@ -87,6 +87,31 @@ static_assert(is_disjoint_from(memoryview, Foo))
|
||||
static_assert(is_disjoint_from(type[memoryview], type[Foo]))
|
||||
```
|
||||
|
||||
## Specialized `@final` types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import static_assert, is_disjoint_from
|
||||
|
||||
@final
|
||||
class Foo[T]:
|
||||
def get(self) -> T:
|
||||
raise NotImplementedError
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
static_assert(not is_disjoint_from(Foo[A], Foo[B]))
|
||||
|
||||
# TODO: `int` and `str` are disjoint bases, so these should be disjoint.
|
||||
static_assert(not is_disjoint_from(Foo[int], Foo[str]))
|
||||
```
|
||||
|
||||
## "Disjoint base" builtin types
|
||||
|
||||
Most other builtins can be subclassed and can even be used in multiple inheritance. However, builtin
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
# Constraint set satisfaction
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Constraint sets exist to help us check assignability and subtyping of types in the presence of
|
||||
typevars. We construct a constraint set describing the conditions under which assignability holds
|
||||
between the two types. Then we check whether that constraint set is satisfied for the valid
|
||||
specializations of the relevant typevars. This file tests that final step.
|
||||
|
||||
## Inferable vs non-inferable typevars
|
||||
|
||||
Typevars can appear in _inferable_ or _non-inferable_ positions.
|
||||
|
||||
When a typevar is in an inferable position, the constraint set only needs to be satisfied for _some_
|
||||
valid specialization. The most common inferable position occurs when invoking a generic function:
|
||||
all of the function's typevars are inferable, because we want to use the argument types to infer
|
||||
which specialization is being invoked.
|
||||
|
||||
When a typevar is in a non-inferable position, the constraint set must be satisfied for _every_
|
||||
valid specialization. The most common non-inferable position occurs in the body of a generic
|
||||
function or class: here we don't know in advance what type the typevar will be specialized to, and
|
||||
so we have to ensure that the body is valid for all possible specializations.
|
||||
|
||||
```py
|
||||
def f[T](t: T) -> T:
|
||||
# In the function body, T is non-inferable. All assignability checks involving T must be
|
||||
# satisfied for _all_ valid specializations of T.
|
||||
return t
|
||||
|
||||
# When invoking the function, T is inferable — we attempt to infer a specialization that is valid
|
||||
# for the particular arguments that are passed to the function. Assignability checks (in particular,
|
||||
# that the argument type is assignable to the parameter type) only need to succeed for _at least
|
||||
# one_ specialization.
|
||||
f(1)
|
||||
```
|
||||
|
||||
In all of the examples below, for ease of reproducibility, we explicitly list the typevars that are
|
||||
inferable in each `satisfied_by_all_typevars` call; any typevar not listed is assumed to be
|
||||
non-inferable.
|
||||
|
||||
## Unbounded typevar
|
||||
|
||||
If a typevar has no bound or constraints, then it can specialize to any type. In an inferable
|
||||
position, that means we just need a single type (any type at all!) that satisfies the constraint
|
||||
set. In a non-inferable position, that means the constraint set must be satisfied for every possible
|
||||
type.
|
||||
|
||||
```py
|
||||
from typing import final, Never
|
||||
from ty_extensions import ConstraintSet, static_assert
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def unbounded[T]():
|
||||
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
|
||||
|
||||
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
|
||||
|
||||
# (T = Never) is a valid specialization, which satisfies (T ≤ Unrelated).
|
||||
static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated).
|
||||
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) is a valid specialization, which satisfies (T ≤ Super).
|
||||
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Super).
|
||||
static_assert(not ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) is a valid specialization, which satisfies (T ≤ Base).
|
||||
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Base).
|
||||
static_assert(not ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Sub) is a valid specialization, which satisfies (T ≤ Sub).
|
||||
static_assert(ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Sub).
|
||||
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars())
|
||||
```
|
||||
|
||||
## Typevar with an upper bound
|
||||
|
||||
If a typevar has an upper bound, then it must specialize to a type that is a subtype of that bound.
|
||||
For an inferable typevar, that means we need a single type that satisfies both the constraint set
|
||||
and the upper bound. For a non-inferable typevar, that means the constraint set must be satisfied
|
||||
for every type that satisfies the upper bound.
|
||||
|
||||
```py
|
||||
from typing import final, Never
|
||||
from ty_extensions import ConstraintSet, static_assert
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def bounded[T: Base]():
|
||||
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
|
||||
|
||||
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) is a valid specialization, which satisfies (T ≤ Super).
|
||||
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# Every valid specialization satisfies (T ≤ Base). Since (Base ≤ Super), every valid
|
||||
# specialization also satisfies (T ≤ Super).
|
||||
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) is a valid specialization, which satisfies (T ≤ Base).
|
||||
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# Every valid specialization satisfies (T ≤ Base).
|
||||
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Sub) is a valid specialization, which satisfies (T ≤ Sub).
|
||||
static_assert(ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Sub).
|
||||
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Never) is a valid specialization, which satisfies (T ≤ Unrelated).
|
||||
constraints = ConstraintSet.range(Never, T, Unrelated)
|
||||
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated).
|
||||
static_assert(not constraints.satisfied_by_all_typevars())
|
||||
|
||||
# Never is the only type that satisfies both (T ≤ Base) and (T ≤ Unrelated). So there is no
|
||||
# valid specialization that satisfies (T ≤ Unrelated ∧ T ≠ Never).
|
||||
constraints = constraints & ~ConstraintSet.range(Never, T, Never)
|
||||
static_assert(not constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(not constraints.satisfied_by_all_typevars())
|
||||
```
|
||||
|
||||
## Constrained typevar
|
||||
|
||||
If a typevar has constraints, then it must specialize to one of those specific types. (Not to a
|
||||
subtype of one of those types!) For an inferable typevar, that means we need the constraint set to
|
||||
be satisfied by any one of the constraints. For a non-inferable typevar, that means we need the
|
||||
constraint set to be satisfied by all of those constraints.
|
||||
|
||||
```py
|
||||
from typing import final, Never
|
||||
from ty_extensions import ConstraintSet, static_assert
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def constrained[T: (Base, Unrelated)]():
|
||||
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
|
||||
|
||||
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
|
||||
|
||||
# (T = Unrelated) is a valid specialization, which satisfies (T ≤ Unrelated).
|
||||
static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated).
|
||||
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) is a valid specialization, which satisfies (T ≤ Super).
|
||||
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Super).
|
||||
static_assert(not ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) is a valid specialization, which satisfies (T ≤ Base).
|
||||
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Base).
|
||||
static_assert(not ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
|
||||
|
||||
# Neither (T = Base) nor (T = Unrelated) satisfy (T ≤ Sub).
|
||||
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) and (T = Unrelated) both satisfy (T ≤ Super ∨ T ≤ Unrelated).
|
||||
constraints = ConstraintSet.range(Never, T, Super) | ConstraintSet.range(Never, T, Unrelated)
|
||||
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(constraints.satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) and (T = Unrelated) both satisfy (T ≤ Base ∨ T ≤ Unrelated).
|
||||
constraints = ConstraintSet.range(Never, T, Base) | ConstraintSet.range(Never, T, Unrelated)
|
||||
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(constraints.satisfied_by_all_typevars())
|
||||
|
||||
# (T = Unrelated) is a valid specialization, which satisfies (T ≤ Sub ∨ T ≤ Unrelated).
|
||||
constraints = ConstraintSet.range(Never, T, Sub) | ConstraintSet.range(Never, T, Unrelated)
|
||||
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Sub ∨ T ≤ Unrelated).
|
||||
static_assert(not constraints.satisfied_by_all_typevars())
|
||||
|
||||
# (T = Unrelated) is a valid specialization, which satisfies (T = Super ∨ T = Unrelated).
|
||||
constraints = ConstraintSet.range(Super, T, Super) | ConstraintSet.range(Unrelated, T, Unrelated)
|
||||
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Base) is a valid specialization, which does not satisfy (T = Super ∨ T = Unrelated).
|
||||
static_assert(not constraints.satisfied_by_all_typevars())
|
||||
|
||||
# (T = Base) and (T = Unrelated) both satisfy (T = Base ∨ T = Unrelated).
|
||||
constraints = ConstraintSet.range(Base, T, Base) | ConstraintSet.range(Unrelated, T, Unrelated)
|
||||
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
static_assert(constraints.satisfied_by_all_typevars())
|
||||
|
||||
# (T = Unrelated) is a valid specialization, which satisfies (T = Sub ∨ T = Unrelated).
|
||||
constraints = ConstraintSet.range(Sub, T, Sub) | ConstraintSet.range(Unrelated, T, Unrelated)
|
||||
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
|
||||
# (T = Base) is a valid specialization, which does not satisfy (T = Sub ∨ T = Unrelated).
|
||||
static_assert(not constraints.satisfied_by_all_typevars())
|
||||
```
|
||||
@@ -29,7 +29,7 @@ alice: Person = {"name": "Alice", "age": 30}
|
||||
reveal_type(alice["name"]) # revealed: str
|
||||
reveal_type(alice["age"]) # revealed: int | None
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "non_existing""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "non_existing""
|
||||
reveal_type(alice["non_existing"]) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -41,7 +41,7 @@ bob = Person(name="Bob", age=25)
|
||||
reveal_type(bob["name"]) # revealed: str
|
||||
reveal_type(bob["age"]) # revealed: int | None
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "non_existing""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "non_existing""
|
||||
reveal_type(bob["non_existing"]) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -69,7 +69,7 @@ def name_or_age() -> Literal["name", "age"]:
|
||||
carol: Person = {NAME: "Carol", AGE: 20}
|
||||
|
||||
reveal_type(carol[NAME]) # revealed: str
|
||||
# error: [invalid-key] "TypedDict `Person` cannot be indexed with a key of type `str`"
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person` of type `str`"
|
||||
reveal_type(carol[non_literal()]) # revealed: Unknown
|
||||
reveal_type(carol[name_or_age()]) # revealed: str | int | None
|
||||
|
||||
@@ -81,7 +81,7 @@ def _():
|
||||
|
||||
CAPITALIZED_NAME = "Name"
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "Name" - did you mean "name"?"
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "Name" - did you mean "name"?"
|
||||
# error: [missing-typed-dict-key] "Missing required key 'name' in TypedDict `Person` constructor"
|
||||
dave: Person = {CAPITALIZED_NAME: "Dave", "age": 20}
|
||||
|
||||
@@ -104,9 +104,9 @@ eve2a: Person = {"age": 22}
|
||||
# error: [missing-typed-dict-key] "Missing required key 'name' in TypedDict `Person` constructor"
|
||||
eve2b = Person(age=22)
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
eve3a: Person = {"name": "Eve", "age": 25, "extra": True}
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
eve3b = Person(name="Eve", age=25, extra=True)
|
||||
```
|
||||
|
||||
@@ -157,10 +157,10 @@ bob["name"] = None
|
||||
Assignments to non-existing keys are disallowed:
|
||||
|
||||
```py
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
alice["extra"] = True
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
bob["extra"] = True
|
||||
```
|
||||
|
||||
@@ -185,10 +185,10 @@ alice: Person = {"inner": {"name": "Alice", "age": 30}}
|
||||
reveal_type(alice["inner"]["name"]) # revealed: str
|
||||
reveal_type(alice["inner"]["age"]) # revealed: int | None
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Inner`: Unknown key "non_existing""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Inner`: Unknown key "non_existing""
|
||||
reveal_type(alice["inner"]["non_existing"]) # revealed: Unknown
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Inner`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Inner`: Unknown key "extra""
|
||||
alice: Person = {"inner": {"name": "Alice", "age": 30, "extra": 1}}
|
||||
```
|
||||
|
||||
@@ -267,22 +267,22 @@ a_person = {"name": None, "age": 30}
|
||||
All of these have an extra field that is not defined in the `TypedDict`:
|
||||
|
||||
```py
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
alice4: Person = {"name": "Alice", "age": 30, "extra": True}
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
Person(name="Alice", age=30, extra=True)
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
Person({"name": "Alice", "age": 30, "extra": True})
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
accepts_person({"name": "Alice", "age": 30, "extra": True})
|
||||
# TODO: this should be an error
|
||||
house.owner = {"name": "Alice", "age": 30, "extra": True}
|
||||
|
||||
a_person: Person
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
a_person = {"name": "Alice", "age": 30, "extra": True}
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra""
|
||||
(a_person := {"name": "Alice", "age": 30, "extra": True})
|
||||
```
|
||||
|
||||
@@ -323,7 +323,7 @@ user2 = User({"name": "Bob"})
|
||||
# error: [invalid-argument-type] "Invalid argument to key "name" with declared type `str` on TypedDict `User`: value of type `None`"
|
||||
user3 = User({"name": None, "age": 25})
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `User`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `User`: Unknown key "extra""
|
||||
user4 = User({"name": "Charlie", "age": 30, "extra": True})
|
||||
```
|
||||
|
||||
@@ -360,7 +360,7 @@ invalid = OptionalPerson(name=123)
|
||||
Extra fields are still not allowed, even with `total=False`:
|
||||
|
||||
```py
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `OptionalPerson`: Unknown key "extra""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `OptionalPerson`: Unknown key "extra""
|
||||
invalid_extra = OptionalPerson(name="George", extra=True)
|
||||
```
|
||||
|
||||
@@ -503,10 +503,10 @@ def _(person: Person, literal_key: Literal["age"], union_of_keys: Literal["age",
|
||||
|
||||
reveal_type(person[union_of_keys]) # revealed: int | None | str
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "non_existing""
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "non_existing""
|
||||
reveal_type(person["non_existing"]) # revealed: Unknown
|
||||
|
||||
# error: [invalid-key] "TypedDict `Person` cannot be indexed with a key of type `str`"
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person` of type `str`"
|
||||
reveal_type(person[str_key]) # revealed: Unknown
|
||||
|
||||
# No error here:
|
||||
@@ -530,7 +530,7 @@ def _(person: Person):
|
||||
person["name"] = "Alice"
|
||||
person["age"] = 30
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "naem" - did you mean "name"?"
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "naem" - did you mean "name"?"
|
||||
person["naem"] = "Alice"
|
||||
|
||||
def _(person: Person):
|
||||
@@ -646,7 +646,7 @@ def _(p: Person) -> None:
|
||||
reveal_type(p.setdefault("name", "Alice")) # revealed: str
|
||||
reveal_type(p.setdefault("extra", "default")) # revealed: str
|
||||
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extraz" - did you mean "extra"?"
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extraz" - did you mean "extra"?"
|
||||
reveal_type(p.setdefault("extraz", "value")) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -1015,6 +1015,10 @@ def write_to_non_existing_key(person: Person):
|
||||
|
||||
def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||
person[str_key] = "Alice" # error: [invalid-key]
|
||||
|
||||
def create_with_invalid_string_key():
|
||||
alice: Person = {"name": "Alice", "age": 30, "unknown": "Foo"} # error: [invalid-key]
|
||||
bob = Person(name="Bob", age=25, unknown="Bar") # error: [invalid-key]
|
||||
```
|
||||
|
||||
Assignment to `ReadOnly` keys:
|
||||
|
||||
@@ -6,12 +6,12 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_index::{IndexSlice, IndexVec};
|
||||
|
||||
use ruff_python_ast::NodeIndex;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_parser::semantic_errors::SemanticSyntaxError;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use salsa::Update;
|
||||
use salsa::plumbing::AsId;
|
||||
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::semantic_index::ast_ids::AstIds;
|
||||
@@ -28,6 +28,7 @@ use crate::semantic_index::scope::{
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use crate::semantic_index::use_def::{EnclosingSnapshotKey, ScopedEnclosingSnapshotId, UseDefMap};
|
||||
use crate::semantic_model::HasTrackedScope;
|
||||
use crate::{Db, Module, resolve_module};
|
||||
|
||||
pub mod ast_ids;
|
||||
mod builder;
|
||||
@@ -75,20 +76,73 @@ pub(crate) fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<Plac
|
||||
|
||||
/// Returns the set of modules that are imported anywhere in `file`.
|
||||
///
|
||||
/// This set only considers `import` statements, not `from...import` statements, because:
|
||||
///
|
||||
/// - In `from foo import bar`, we cannot determine whether `foo.bar` is a submodule (and is
|
||||
/// therefore imported) without looking outside the content of this file. (We could turn this
|
||||
/// into a _potentially_ imported modules set, but that would change how it's used in our type
|
||||
/// inference logic.)
|
||||
///
|
||||
/// - We cannot resolve relative imports (which aren't allowed in `import` statements) without
|
||||
/// knowing the name of the current module, and whether it's a package.
|
||||
/// This set only considers `import` statements, not `from...import` statements.
|
||||
/// See [`ModuleLiteralType::available_submodule_attributes`] for discussion
|
||||
/// of why this analysis is intentionally limited.
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn imported_modules<'db>(db: &'db dyn Db, file: File) -> Arc<FxHashSet<ModuleName>> {
|
||||
semantic_index(db, file).imported_modules.clone()
|
||||
}
|
||||
|
||||
/// Returns the set of relative submodules that are explicitly imported anywhere in
|
||||
/// `importing_module`.
|
||||
///
|
||||
/// This set only considers `from...import` statements (but it could also include `import`).
|
||||
/// It also only returns a non-empty result for `__init__.pyi` files.
|
||||
/// See [`ModuleLiteralType::available_submodule_attributes`] for discussion
|
||||
/// of why this analysis is intentionally limited.
|
||||
///
|
||||
/// This function specifically implements the rule that if an `__init__.pyi` file
|
||||
/// contains a `from...import` that imports a direct submodule of the package,
|
||||
/// that submodule should be available as an attribute of the package.
|
||||
///
|
||||
/// While we endeavour to accurately model import side-effects for `.py` files, we intentionally
|
||||
/// limit them for `.pyi` files to encourage more intentional API design. The standard escape
|
||||
/// hatches for this are the `import x as x` idiom or listing them in `__all__`, but in practice
|
||||
/// some other idioms are popular.
|
||||
///
|
||||
/// In particular, many packages have their `__init__` include lines like
|
||||
/// `from . import subpackage`, with the intent that `mypackage.subpackage` should be
|
||||
/// available for anyone who only does `import mypackage`.
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn imported_relative_submodules_of_stub_package<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_module: Module<'db>,
|
||||
) -> Box<[ModuleName]> {
|
||||
let Some(file) = importing_module.file(db) else {
|
||||
return Box::default();
|
||||
};
|
||||
if !file.is_package_stub(db) {
|
||||
return Box::default();
|
||||
}
|
||||
semantic_index(db, file)
|
||||
.maybe_imported_modules
|
||||
.iter()
|
||||
.filter_map(|import| {
|
||||
let mut submodule = ModuleName::from_identifier_parts(
|
||||
db,
|
||||
file,
|
||||
import.from_module.as_deref(),
|
||||
import.level,
|
||||
)
|
||||
.ok()?;
|
||||
// We only actually care if this is a direct submodule of the package
|
||||
// so this part should actually be exactly the importing module.
|
||||
let importing_module_name = importing_module.name(db);
|
||||
if importing_module_name != &submodule {
|
||||
return None;
|
||||
}
|
||||
submodule.extend(&ModuleName::new(import.submodule.as_str())?);
|
||||
// Throw out the result if this doesn't resolve to an actual module.
|
||||
// This is quite expensive, but we've gone through a lot of hoops to
|
||||
// get here so it won't happen too much.
|
||||
resolve_module(db, &submodule)?;
|
||||
// Return only the relative part
|
||||
submodule.relative_to(importing_module_name)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the use-def map for a specific `scope`.
|
||||
///
|
||||
/// Using [`use_def_map`] over [`semantic_index`] has the advantage that
|
||||
@@ -230,6 +284,9 @@ pub(crate) struct SemanticIndex<'db> {
|
||||
/// The set of modules that are imported anywhere within this file.
|
||||
imported_modules: Arc<FxHashSet<ModuleName>>,
|
||||
|
||||
/// `from...import` statements within this file that might import a submodule.
|
||||
maybe_imported_modules: FxHashSet<MaybeModuleImport>,
|
||||
|
||||
/// Flags about the global scope (code usage impacting inference)
|
||||
has_future_annotations: bool,
|
||||
|
||||
@@ -243,6 +300,16 @@ pub(crate) struct SemanticIndex<'db> {
|
||||
generator_functions: FxHashSet<FileScopeId>,
|
||||
}
|
||||
|
||||
/// A `from...import` that may be an import of a module
|
||||
///
|
||||
/// Later analysis will determine if it is.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, get_size2::GetSize)]
|
||||
pub(crate) struct MaybeModuleImport {
|
||||
level: u32,
|
||||
from_module: Option<Name>,
|
||||
submodule: Name,
|
||||
}
|
||||
|
||||
impl<'db> SemanticIndex<'db> {
|
||||
/// Returns the place table for a specific scope.
|
||||
///
|
||||
|
||||
@@ -47,7 +47,9 @@ use crate::semantic_index::symbol::{ScopedSymbolId, Symbol};
|
||||
use crate::semantic_index::use_def::{
|
||||
EnclosingSnapshotKey, FlowSnapshot, ScopedEnclosingSnapshotId, UseDefMapBuilder,
|
||||
};
|
||||
use crate::semantic_index::{ExpressionsScopeMap, SemanticIndex, VisibleAncestorsIter};
|
||||
use crate::semantic_index::{
|
||||
ExpressionsScopeMap, MaybeModuleImport, SemanticIndex, VisibleAncestorsIter,
|
||||
};
|
||||
use crate::semantic_model::HasTrackedScope;
|
||||
use crate::unpack::{EvaluationMode, Unpack, UnpackKind, UnpackPosition, UnpackValue};
|
||||
use crate::{Db, Program};
|
||||
@@ -111,6 +113,7 @@ pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||
definitions_by_node: FxHashMap<DefinitionNodeKey, Definitions<'db>>,
|
||||
expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
|
||||
imported_modules: FxHashSet<ModuleName>,
|
||||
maybe_imported_modules: FxHashSet<MaybeModuleImport>,
|
||||
/// Hashset of all [`FileScopeId`]s that correspond to [generator functions].
|
||||
///
|
||||
/// [generator functions]: https://docs.python.org/3/glossary.html#term-generator
|
||||
@@ -148,6 +151,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
definitions_by_node: FxHashMap::default(),
|
||||
expressions_by_node: FxHashMap::default(),
|
||||
|
||||
maybe_imported_modules: FxHashSet::default(),
|
||||
imported_modules: FxHashSet::default(),
|
||||
generator_functions: FxHashSet::default(),
|
||||
|
||||
@@ -1262,6 +1266,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
self.scopes_by_node.shrink_to_fit();
|
||||
self.generator_functions.shrink_to_fit();
|
||||
self.enclosing_snapshots.shrink_to_fit();
|
||||
self.maybe_imported_modules.shrink_to_fit();
|
||||
|
||||
SemanticIndex {
|
||||
place_tables,
|
||||
@@ -1274,6 +1279,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
scopes_by_node: self.scopes_by_node,
|
||||
use_def_maps,
|
||||
imported_modules: Arc::new(self.imported_modules),
|
||||
maybe_imported_modules: self.maybe_imported_modules,
|
||||
has_future_annotations: self.has_future_annotations,
|
||||
enclosing_snapshots: self.enclosing_snapshots,
|
||||
semantic_syntax_errors: self.semantic_syntax_errors.into_inner(),
|
||||
@@ -1558,6 +1564,15 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
(&alias.name.id, false)
|
||||
};
|
||||
|
||||
// If there's no alias or a redundant alias, record this as a potential import of a submodule
|
||||
if alias.asname.is_none() || is_reexported {
|
||||
self.maybe_imported_modules.insert(MaybeModuleImport {
|
||||
level: node.level,
|
||||
from_module: node.module.clone().map(Into::into),
|
||||
submodule: alias.name.clone().into(),
|
||||
});
|
||||
}
|
||||
|
||||
// Look for imports `from __future__ import annotations`, ignore `as ...`
|
||||
// We intentionally don't enforce the rules about location of `__future__`
|
||||
// imports here, we assume the user's intent was to apply the `__future__`
|
||||
|
||||
@@ -39,7 +39,9 @@ use crate::place::{
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::place::ScopedPlaceId;
|
||||
use crate::semantic_index::scope::ScopeId;
|
||||
use crate::semantic_index::{imported_modules, place_table, semantic_index};
|
||||
use crate::semantic_index::{
|
||||
imported_modules, imported_relative_submodules_of_stub_package, place_table, semantic_index,
|
||||
};
|
||||
use crate::suppression::check_suppressions;
|
||||
use crate::types::bound_super::BoundSuperType;
|
||||
use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding};
|
||||
@@ -889,13 +891,24 @@ impl<'db> Type<'db> {
|
||||
known_class: KnownClass,
|
||||
) -> Option<Specialization<'db>> {
|
||||
let class_literal = known_class.try_to_class_literal(db)?;
|
||||
self.specialization_of(db, Some(class_literal))
|
||||
self.specialization_of(db, class_literal)
|
||||
}
|
||||
|
||||
// If this type is a class instance, returns its specialization.
|
||||
pub(crate) fn class_specialization(self, db: &'db dyn Db) -> Option<Specialization<'db>> {
|
||||
self.specialization_of_optional(db, None)
|
||||
}
|
||||
|
||||
// If the type is a specialized instance of the given class, returns the specialization.
|
||||
//
|
||||
// If no class is provided, returns the specialization of any class instance.
|
||||
pub(crate) fn specialization_of(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
expected_class: ClassLiteral<'_>,
|
||||
) -> Option<Specialization<'db>> {
|
||||
self.specialization_of_optional(db, Some(expected_class))
|
||||
}
|
||||
|
||||
fn specialization_of_optional(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
expected_class: Option<ClassLiteral<'_>>,
|
||||
@@ -1212,22 +1225,28 @@ impl<'db> Type<'db> {
|
||||
|
||||
/// If the type is a union, filters union elements based on the provided predicate.
|
||||
///
|
||||
/// Otherwise, returns the type unchanged.
|
||||
/// Otherwise, considers the type to be the sole inhabitant of a single-valued union,
|
||||
/// and filters it, returning `Never` if the predicate returns `false`, or the type
|
||||
/// unchanged if `true`.
|
||||
pub(crate) fn filter_union(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
f: impl FnMut(&Type<'db>) -> bool,
|
||||
mut f: impl FnMut(&Type<'db>) -> bool,
|
||||
) -> Type<'db> {
|
||||
if let Type::Union(union) = self {
|
||||
union.filter(db, f)
|
||||
} else {
|
||||
} else if f(&self) {
|
||||
self
|
||||
} else {
|
||||
Type::Never
|
||||
}
|
||||
}
|
||||
|
||||
/// If the type is a union, removes union elements that are disjoint from `target`.
|
||||
///
|
||||
/// Otherwise, returns the type unchanged.
|
||||
/// Otherwise, considers the type to be the sole inhabitant of a single-valued union,
|
||||
/// and filters it, returning `Never` if it is disjoint from `target`, or the type
|
||||
/// unchanged if `true`.
|
||||
pub(crate) fn filter_disjoint_elements(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
@@ -4159,6 +4178,14 @@ impl<'db> Type<'db> {
|
||||
))
|
||||
.into()
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked))
|
||||
if name == "satisfied_by_all_typevars" =>
|
||||
{
|
||||
Place::bound(Type::KnownBoundMethod(
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(tracked),
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
Type::ClassLiteral(class)
|
||||
if name == "__get__" && class.is_known(db, KnownClass::FunctionType) =>
|
||||
@@ -6921,6 +6948,7 @@ impl<'db> Type<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_)
|
||||
)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
@@ -7072,7 +7100,8 @@ impl<'db> Type<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
|
||||
)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
@@ -10337,6 +10366,7 @@ pub enum KnownBoundMethodType<'db> {
|
||||
ConstraintSetAlways,
|
||||
ConstraintSetNever,
|
||||
ConstraintSetImpliesSubtypeOf(TrackedConstraintSet<'db>),
|
||||
ConstraintSetSatisfiedByAllTypeVars(TrackedConstraintSet<'db>),
|
||||
}
|
||||
|
||||
pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
@@ -10364,7 +10394,8 @@ pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Size
|
||||
| KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => {}
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10432,6 +10463,10 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| (
|
||||
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
|
||||
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
|
||||
)
|
||||
| (
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
|
||||
) => ConstraintSet::from(true),
|
||||
|
||||
(
|
||||
@@ -10444,7 +10479,8 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
||||
| KnownBoundMethodType::FunctionTypeDunderCall(_)
|
||||
| KnownBoundMethodType::PropertyDunderGet(_)
|
||||
@@ -10454,7 +10490,8 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
|
||||
) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
@@ -10507,6 +10544,10 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
(
|
||||
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(left_constraints),
|
||||
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(right_constraints),
|
||||
)
|
||||
| (
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(left_constraints),
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(right_constraints),
|
||||
) => left_constraints
|
||||
.constraints(db)
|
||||
.iff(db, right_constraints.constraints(db)),
|
||||
@@ -10521,7 +10562,8 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
||||
| KnownBoundMethodType::FunctionTypeDunderCall(_)
|
||||
| KnownBoundMethodType::PropertyDunderGet(_)
|
||||
@@ -10531,7 +10573,8 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
|
||||
) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
@@ -10555,7 +10598,8 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => self,
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10571,7 +10615,10 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
KnownBoundMethodType::ConstraintSetRange
|
||||
| KnownBoundMethodType::ConstraintSetAlways
|
||||
| KnownBoundMethodType::ConstraintSetNever
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => KnownClass::ConstraintSet,
|
||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {
|
||||
KnownClass::ConstraintSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10710,6 +10757,19 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
Some(KnownClass::ConstraintSet.to_instance(db)),
|
||||
)))
|
||||
}
|
||||
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {
|
||||
Either::Right(std::iter::once(Signature::new(
|
||||
Parameters::new([Parameter::keyword_only(Name::new_static("inferable"))
|
||||
.type_form()
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[Type::homogeneous_tuple(db, Type::any()), Type::none(db)],
|
||||
))
|
||||
.with_default_type(Type::none(db))]),
|
||||
Some(KnownClass::Bool.to_instance(db)),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10830,11 +10890,68 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
self._importing_file(db)
|
||||
}
|
||||
|
||||
/// Get the submodule attributes we believe to be defined on this module.
|
||||
///
|
||||
/// Note that `ModuleLiteralType` is per-importing-file, so this analysis
|
||||
/// includes "imports the importing file has performed".
|
||||
///
|
||||
///
|
||||
/// # Danger! Powerful Hammer!
|
||||
///
|
||||
/// These results immediately make the attribute always defined in the importing file,
|
||||
/// shadowing any other attribute in the module with the same name, even if the
|
||||
/// non-submodule-attribute is in fact always the one defined in practice.
|
||||
///
|
||||
/// Intuitively this means `available_submodule_attributes` "win all tie-breaks",
|
||||
/// with the idea that if we're ever confused about complicated code then usually
|
||||
/// the import is the thing people want in scope.
|
||||
///
|
||||
/// However this "always defined, always shadows" rule if applied too aggressively
|
||||
/// creates VERY confusing conclusions that break perfectly reasonable code.
|
||||
///
|
||||
/// For instance, consider a package which has a `myfunc` submodule which defines a
|
||||
/// `myfunc` function (a common idiom). If the package "re-exports" this function
|
||||
/// (`from .myfunc import myfunc`), then at runtime in python
|
||||
/// `from mypackage import myfunc` should import the function and not the submodule.
|
||||
///
|
||||
/// However, if we were to consider `from mypackage import myfunc` as introducing
|
||||
/// the attribute `mypackage.myfunc` in `available_submodule_attributes`, we would
|
||||
/// fail to ever resolve the function. This is because `available_submodule_attributes`
|
||||
/// is *so early* and *so powerful* in our analysis that **this conclusion would be
|
||||
/// used when actually resolving `from mypackage import myfunc`**!
|
||||
///
|
||||
/// This currently cannot be fixed by considering the actual symbols defined in `mypackage`,
|
||||
/// because `available_submodule_attributes` is an *input* to that analysis.
|
||||
///
|
||||
/// We should therefore avoid marking something as an `available_submodule_attribute`
|
||||
/// when the import could be importing a non-submodule (a function, class, or value).
|
||||
///
|
||||
///
|
||||
/// # Rules
|
||||
///
|
||||
/// We have two rules for whether a submodule attribute is defined:
|
||||
///
|
||||
/// * If the importing file include `import x.y` then `x.y` is defined in the importing file.
|
||||
/// This is an easy rule to justify because `import` can only ever import a module, and so
|
||||
/// *should* shadow any non-submodule of the same name.
|
||||
///
|
||||
/// * If the module is an `__init__.pyi` for `mypackage`, and it contains a `from...import`
|
||||
/// that normalizes to `from mypackage import submodule`, then `mypackage.submodule` is
|
||||
/// defined in all files. This supports the `from . import submodule` idiom. Critically,
|
||||
/// we do *not* allow `from mypackage.nested import submodule` to affect `mypackage`.
|
||||
/// The idea here is that `from mypackage import submodule` *from mypackage itself* can
|
||||
/// only ever reasonably be an import of a submodule. It doesn't make any sense to import
|
||||
/// a function or class from yourself! (You *can* do it but... why? Don't? Please?)
|
||||
fn available_submodule_attributes(&self, db: &'db dyn Db) -> impl Iterator<Item = Name> {
|
||||
self.importing_file(db)
|
||||
.into_iter()
|
||||
.flat_map(|file| imported_modules(db, file))
|
||||
.filter_map(|submodule_name| submodule_name.relative_to(self.module(db).name(db)))
|
||||
.chain(
|
||||
imported_relative_submodules_of_stub_package(db, self.module(db))
|
||||
.iter()
|
||||
.cloned(),
|
||||
)
|
||||
.filter_map(|relative_submodule| relative_submodule.components().next().map(Name::from))
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::fmt;
|
||||
use itertools::{Either, Itertools};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::name::Name;
|
||||
use rustc_hash::FxHashSet;
|
||||
use smallvec::{SmallVec, smallvec, smallvec_inline};
|
||||
|
||||
use super::{Argument, CallArguments, CallError, CallErrorKind, InferContext, Signature, Type};
|
||||
@@ -34,10 +35,11 @@ use crate::types::generics::{
|
||||
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
|
||||
use crate::types::tuple::{TupleLength, TupleType};
|
||||
use crate::types::{
|
||||
BoundMethodType, ClassLiteral, DataclassFlags, DataclassParams, FieldInstance,
|
||||
KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy, PropertyInstanceType,
|
||||
SpecialFormType, TrackedConstraintSet, TypeAliasType, TypeContext, UnionBuilder, UnionType,
|
||||
WrapperDescriptorKind, enums, ide_support, infer_isolated_expression, todo_type,
|
||||
BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DataclassFlags, DataclassParams,
|
||||
FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy,
|
||||
NominalInstanceType, PropertyInstanceType, SpecialFormType, TrackedConstraintSet,
|
||||
TypeAliasType, TypeContext, UnionBuilder, UnionType, WrapperDescriptorKind, enums, ide_support,
|
||||
infer_isolated_expression, todo_type,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion};
|
||||
@@ -1174,6 +1176,42 @@ impl<'db> Bindings<'db> {
|
||||
));
|
||||
}
|
||||
|
||||
Type::KnownBoundMethod(
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(tracked),
|
||||
) => {
|
||||
let extract_inferable = |instance: &NominalInstanceType<'db>| {
|
||||
if instance.has_known_class(db, KnownClass::NoneType) {
|
||||
// Caller explicitly passed None, so no typevars are inferable.
|
||||
return Some(FxHashSet::default());
|
||||
}
|
||||
instance
|
||||
.tuple_spec(db)?
|
||||
.fixed_elements()
|
||||
.map(|ty| {
|
||||
ty.as_typevar()
|
||||
.map(|bound_typevar| bound_typevar.identity(db))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let inferable = match overload.parameter_types() {
|
||||
// Caller did not provide argument, so no typevars are inferable.
|
||||
[None] => FxHashSet::default(),
|
||||
[Some(Type::NominalInstance(instance))] => {
|
||||
match extract_inferable(instance) {
|
||||
Some(inferable) => inferable,
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let result = tracked
|
||||
.constraints(db)
|
||||
.satisfied_by_all_typevars(db, InferableTypeVars::One(&inferable));
|
||||
overload.set_return_type(Type::BooleanLiteral(result));
|
||||
}
|
||||
|
||||
Type::ClassLiteral(class) => match class.known(db) {
|
||||
Some(KnownClass::Bool) => match overload.parameter_types() {
|
||||
[Some(arg)] => overload.set_return_type(arg.bool(db).into_type(db)),
|
||||
@@ -2680,9 +2718,25 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
return;
|
||||
};
|
||||
|
||||
let return_with_tcx = self
|
||||
.signature
|
||||
.return_ty
|
||||
.zip(self.call_expression_tcx.annotation);
|
||||
|
||||
self.inferable_typevars = generic_context.inferable_typevars(self.db);
|
||||
let mut builder = SpecializationBuilder::new(self.db, self.inferable_typevars);
|
||||
|
||||
// Prefer the declared type of generic classes.
|
||||
let preferred_type_mappings = return_with_tcx.and_then(|(return_ty, tcx)| {
|
||||
let preferred_return_ty =
|
||||
tcx.filter_union(self.db, |ty| ty.class_specialization(self.db).is_some());
|
||||
let return_ty =
|
||||
return_ty.filter_union(self.db, |ty| ty.class_specialization(self.db).is_some());
|
||||
|
||||
builder.infer(return_ty, preferred_return_ty).ok()?;
|
||||
Some(builder.type_mappings().clone())
|
||||
});
|
||||
|
||||
let parameters = self.signature.parameters();
|
||||
for (argument_index, adjusted_argument_index, _, argument_type) in
|
||||
self.enumerate_argument_types()
|
||||
@@ -2695,9 +2749,21 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Err(error) = builder.infer(
|
||||
let filter = |declared_ty: BoundTypeVarIdentity<'_>, inferred_ty: Type<'_>| {
|
||||
// Avoid widening the inferred type if it is already assignable to the
|
||||
// preferred declared type.
|
||||
preferred_type_mappings
|
||||
.as_ref()
|
||||
.and_then(|types| types.get(&declared_ty))
|
||||
.is_none_or(|preferred_ty| {
|
||||
!inferred_ty.is_assignable_to(self.db, *preferred_ty)
|
||||
})
|
||||
};
|
||||
|
||||
if let Err(error) = builder.infer_filter(
|
||||
expected_type,
|
||||
variadic_argument_type.unwrap_or(argument_type),
|
||||
filter,
|
||||
) {
|
||||
self.errors.push(BindingError::SpecializationError {
|
||||
error,
|
||||
@@ -2707,15 +2773,14 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
}
|
||||
}
|
||||
|
||||
// Build the specialization first without inferring the type context.
|
||||
// Build the specialization first without inferring the complete type context.
|
||||
let isolated_specialization = builder.build(generic_context, *self.call_expression_tcx);
|
||||
let isolated_return_ty = self
|
||||
.return_ty
|
||||
.apply_specialization(self.db, isolated_specialization);
|
||||
|
||||
let mut try_infer_tcx = || {
|
||||
let return_ty = self.signature.return_ty?;
|
||||
let call_expression_tcx = self.call_expression_tcx.annotation?;
|
||||
let (return_ty, call_expression_tcx) = return_with_tcx?;
|
||||
|
||||
// A type variable is not a useful type-context for expression inference, and applying it
|
||||
// to the return type can lead to confusing unions in nested generic calls.
|
||||
@@ -2724,7 +2789,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
}
|
||||
|
||||
// If the return type is already assignable to the annotated type, we can ignore the
|
||||
// type context and prefer the narrower inferred type.
|
||||
// rest of the type context and prefer the narrower inferred type.
|
||||
if isolated_return_ty.is_assignable_to(self.db, call_expression_tcx) {
|
||||
return None;
|
||||
}
|
||||
@@ -2733,7 +2798,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
// annotated assignment, to closer match the order of any unions written in the type annotation.
|
||||
builder.infer(return_ty, call_expression_tcx).ok()?;
|
||||
|
||||
// Otherwise, build the specialization again after inferring the type context.
|
||||
// Otherwise, build the specialization again after inferring the complete type context.
|
||||
let specialization = builder.build(generic_context, *self.call_expression_tcx);
|
||||
let return_ty = return_ty.apply_specialization(self.db, specialization);
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ impl<'db> GenericAlias<'db> {
|
||||
) -> Self {
|
||||
let tcx = tcx
|
||||
.annotation
|
||||
.and_then(|ty| ty.specialization_of(db, Some(self.origin(db))))
|
||||
.and_then(|ty| ty.specialization_of(db, self.origin(db)))
|
||||
.map(|specialization| specialization.types(db))
|
||||
.unwrap_or(&[]);
|
||||
|
||||
@@ -637,12 +637,17 @@ impl<'db> ClassType<'db> {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optimisation: if either class is `@final`, we only need to do one `is_subclass_of` call.
|
||||
if self.is_final(db) {
|
||||
return self.is_subclass_of(db, other);
|
||||
return self
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.any(|class| class.class_literal(db).0 == other.class_literal(db).0);
|
||||
}
|
||||
if other.is_final(db) {
|
||||
return other.is_subclass_of(db, self);
|
||||
return other
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.any(|class| class.class_literal(db).0 == self.class_literal(db).0);
|
||||
}
|
||||
|
||||
// Two disjoint bases can only coexist in an MRO if one is a subclass of the other.
|
||||
@@ -2176,7 +2181,8 @@ impl<'db> ClassLiteral<'db> {
|
||||
});
|
||||
|
||||
if member.is_undefined() {
|
||||
if let Some(synthesized_member) = self.own_synthesized_member(db, specialization, name)
|
||||
if let Some(synthesized_member) =
|
||||
self.own_synthesized_member(db, specialization, inherited_generic_context, name)
|
||||
{
|
||||
return Member::definitely_declared(synthesized_member);
|
||||
}
|
||||
@@ -2192,6 +2198,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
inherited_generic_context: Option<GenericContext<'db>>,
|
||||
name: &str,
|
||||
) -> Option<Type<'db>> {
|
||||
let dataclass_params = self.dataclass_params(db);
|
||||
@@ -2320,7 +2327,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
let signature = match name {
|
||||
"__new__" | "__init__" => Signature::new_generic(
|
||||
self.inherited_generic_context(db),
|
||||
inherited_generic_context.or_else(|| self.inherited_generic_context(db)),
|
||||
Parameters::new(parameters),
|
||||
return_ty,
|
||||
),
|
||||
@@ -2702,7 +2709,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if let Some(member) = self.own_synthesized_member(db, specialization, name) {
|
||||
if let Some(member) = self.own_synthesized_member(db, specialization, None, name) {
|
||||
Place::bound(member).into()
|
||||
} else {
|
||||
KnownClass::TypedDictFallback
|
||||
|
||||
@@ -65,7 +65,10 @@ use salsa::plumbing::AsId;
|
||||
|
||||
use crate::Db;
|
||||
use crate::types::generics::InferableTypeVars;
|
||||
use crate::types::{BoundTypeVarInstance, IntersectionType, Type, TypeRelation, UnionType};
|
||||
use crate::types::{
|
||||
BoundTypeVarInstance, IntersectionType, Type, TypeRelation, TypeVarBoundOrConstraints,
|
||||
UnionType,
|
||||
};
|
||||
|
||||
/// An extension trait for building constraint sets from [`Option`] values.
|
||||
pub(crate) trait OptionConstraintsExtension<T> {
|
||||
@@ -256,6 +259,28 @@ impl<'db> ConstraintSet<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this constraint set is satisfied by all of the typevars that it mentions.
|
||||
///
|
||||
/// Each typevar has a set of _valid specializations_, which is defined by any upper bound or
|
||||
/// constraints that the typevar has.
|
||||
///
|
||||
/// Each typevar is also either _inferable_ or _non-inferable_. (You provide a list of the
|
||||
/// `inferable` typevars; all others are considered non-inferable.) For an inferable typevar,
|
||||
/// then there must be _some_ valid specialization that satisfies the constraint set. For a
|
||||
/// non-inferable typevar, then _all_ valid specializations must satisfy it.
|
||||
///
|
||||
/// Note that we don't have to consider typevars that aren't mentioned in the constraint set,
|
||||
/// since the constraint set cannot be affected by any typevars that it does not mention. That
|
||||
/// means that those additional typevars trivially satisfy the constraint set, regardless of
|
||||
/// whether they are inferable or not.
|
||||
pub(crate) fn satisfied_by_all_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> bool {
|
||||
self.node.satisfied_by_all_typevars(db, inferable)
|
||||
}
|
||||
|
||||
/// Updates this constraint set to hold the union of itself and another constraint set.
|
||||
pub(crate) fn union(&mut self, db: &'db dyn Db, other: Self) -> Self {
|
||||
self.node = self.node.or(db, other.node);
|
||||
@@ -746,6 +771,13 @@ impl<'db> Node<'db> {
|
||||
.or(db, self.negate(db).and(db, else_node))
|
||||
}
|
||||
|
||||
fn satisfies(self, db: &'db dyn Db, other: Self) -> Self {
|
||||
let simplified_self = self.simplify(db);
|
||||
let implication = simplified_self.implies(db, other);
|
||||
let (simplified, domain) = implication.simplify_and_domain(db);
|
||||
simplified.and(db, domain)
|
||||
}
|
||||
|
||||
fn when_subtype_of_given(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
@@ -767,10 +799,48 @@ impl<'db> Node<'db> {
|
||||
_ => return lhs.when_subtype_of(db, rhs, inferable).node,
|
||||
};
|
||||
|
||||
let simplified_self = self.simplify(db);
|
||||
let implication = simplified_self.implies(db, constraint);
|
||||
let (simplified, domain) = implication.simplify_and_domain(db);
|
||||
simplified.and(db, domain)
|
||||
self.satisfies(db, constraint)
|
||||
}
|
||||
|
||||
fn satisfied_by_all_typevars(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> bool {
|
||||
match self {
|
||||
Node::AlwaysTrue => return true,
|
||||
Node::AlwaysFalse => return false,
|
||||
Node::Interior(_) => {}
|
||||
}
|
||||
|
||||
let mut typevars = FxHashSet::default();
|
||||
self.for_each_constraint(db, &mut |constraint| {
|
||||
typevars.insert(constraint.typevar(db));
|
||||
});
|
||||
|
||||
for typevar in typevars {
|
||||
// Determine which valid specializations of this typevar satisfy the constraint set.
|
||||
let valid_specializations = typevar.valid_specializations(db).node;
|
||||
let when_satisfied = valid_specializations
|
||||
.satisfies(db, self)
|
||||
.and(db, valid_specializations);
|
||||
let satisfied = if typevar.is_inferable(db, inferable) {
|
||||
// If the typevar is inferable, then we only need one valid specialization to
|
||||
// satisfy the constraint set.
|
||||
!when_satisfied.is_never_satisfied()
|
||||
} else {
|
||||
// If the typevar is non-inferable, then we need _all_ valid specializations to
|
||||
// satisfy the constraint set.
|
||||
when_satisfied
|
||||
.iff(db, valid_specializations)
|
||||
.is_always_satisfied(db)
|
||||
};
|
||||
if !satisfied {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns a new BDD that returns the same results as `self`, but with some inputs fixed to
|
||||
@@ -1861,6 +1931,33 @@ impl<'db> SatisfiedClauses<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a constraint set describing the valid specializations of a typevar.
|
||||
impl<'db> BoundTypeVarInstance<'db> {
|
||||
pub(crate) fn valid_specializations(self, db: &'db dyn Db) -> ConstraintSet<'db> {
|
||||
match self.typevar(db).bound_or_constraints(db) {
|
||||
None => ConstraintSet::from(true),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => ConstraintSet::constrain_typevar(
|
||||
db,
|
||||
self,
|
||||
Type::Never,
|
||||
bound,
|
||||
TypeRelation::Assignability,
|
||||
),
|
||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||
constraints.elements(db).iter().when_any(db, |constraint| {
|
||||
ConstraintSet::constrain_typevar(
|
||||
db,
|
||||
self,
|
||||
*constraint,
|
||||
*constraint,
|
||||
TypeRelation::Assignability,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -572,10 +572,14 @@ declare_lint! {
|
||||
// Added in #19763.
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for subscript accesses with invalid keys.
|
||||
/// Checks for subscript accesses with invalid keys and `TypedDict` construction with an
|
||||
/// unknown key.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using an invalid key will raise a `KeyError` at runtime.
|
||||
/// Subscripting with an invalid key will raise a `KeyError` at runtime.
|
||||
///
|
||||
/// Creating a `TypedDict` with an unknown key is likely a mistake; if the `TypedDict` is
|
||||
/// `closed=true` it also violates the expectations of the type.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
@@ -587,9 +591,13 @@ declare_lint! {
|
||||
///
|
||||
/// alice = Person(name="Alice", age=30)
|
||||
/// alice["height"] # KeyError: 'height'
|
||||
///
|
||||
/// bob: Person = { "name": "Bob", "age": 30 } # typo!
|
||||
///
|
||||
/// carol = Person(name="Carol", age=25) # typo!
|
||||
/// ```
|
||||
pub(crate) static INVALID_KEY = {
|
||||
summary: "detects invalid subscript accesses",
|
||||
summary: "detects invalid subscript accesses or TypedDict literal keys",
|
||||
status: LintStatus::stable("0.0.1-alpha.17"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
@@ -2966,7 +2974,7 @@ pub(crate) fn report_invalid_key_on_typed_dict<'db>(
|
||||
let typed_dict_name = typed_dict_ty.display(db);
|
||||
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Invalid key access on TypedDict `{typed_dict_name}`",
|
||||
"Invalid key for TypedDict `{typed_dict_name}`",
|
||||
));
|
||||
|
||||
diagnostic.annotate(
|
||||
@@ -2989,7 +2997,7 @@ pub(crate) fn report_invalid_key_on_typed_dict<'db>(
|
||||
diagnostic
|
||||
}
|
||||
_ => builder.into_diagnostic(format_args!(
|
||||
"TypedDict `{}` cannot be indexed with a key of type `{}`",
|
||||
"Invalid key for TypedDict `{}` of type `{}`",
|
||||
typed_dict_ty.display(db),
|
||||
key_ty.display(db),
|
||||
)),
|
||||
|
||||
@@ -535,6 +535,9 @@ impl Display for DisplayRepresentation<'_> {
|
||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)) => {
|
||||
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
|
||||
}
|
||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(
|
||||
_,
|
||||
)) => f.write_str("bound method `ConstraintSet.satisfied_by_all_typevars`"),
|
||||
Type::WrapperDescriptor(kind) => {
|
||||
let (method, object) = match kind {
|
||||
WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt::Display;
|
||||
|
||||
use itertools::Itertools;
|
||||
@@ -1319,6 +1320,11 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current set of type mappings for this specialization.
|
||||
pub(crate) fn type_mappings(&self) -> &FxHashMap<BoundTypeVarIdentity<'db>, Type<'db>> {
|
||||
&self.types
|
||||
}
|
||||
|
||||
pub(crate) fn build(
|
||||
&mut self,
|
||||
generic_context: GenericContext<'db>,
|
||||
@@ -1326,7 +1332,7 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
) -> Specialization<'db> {
|
||||
let tcx_specialization = tcx
|
||||
.annotation
|
||||
.and_then(|annotation| annotation.specialization_of(self.db, None));
|
||||
.and_then(|annotation| annotation.class_specialization(self.db));
|
||||
|
||||
let types =
|
||||
(generic_context.variables_inner(self.db).iter()).map(|(identity, variable)| {
|
||||
@@ -1349,19 +1355,43 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
generic_context.specialize_partial(self.db, types)
|
||||
}
|
||||
|
||||
fn add_type_mapping(&mut self, bound_typevar: BoundTypeVarInstance<'db>, ty: Type<'db>) {
|
||||
self.types
|
||||
.entry(bound_typevar.identity(self.db))
|
||||
.and_modify(|existing| {
|
||||
*existing = UnionType::from_elements(self.db, [*existing, ty]);
|
||||
})
|
||||
.or_insert(ty);
|
||||
fn add_type_mapping(
|
||||
&mut self,
|
||||
bound_typevar: BoundTypeVarInstance<'db>,
|
||||
ty: Type<'db>,
|
||||
filter: impl Fn(BoundTypeVarIdentity<'db>, Type<'db>) -> bool,
|
||||
) {
|
||||
let identity = bound_typevar.identity(self.db);
|
||||
match self.types.entry(identity) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
if filter(identity, ty) {
|
||||
*entry.get_mut() = UnionType::from_elements(self.db, [*entry.get(), ty]);
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Infer type mappings for the specialization based on a given type and its declared type.
|
||||
pub(crate) fn infer(
|
||||
&mut self,
|
||||
formal: Type<'db>,
|
||||
actual: Type<'db>,
|
||||
) -> Result<(), SpecializationError<'db>> {
|
||||
self.infer_filter(formal, actual, |_, _| true)
|
||||
}
|
||||
|
||||
/// Infer type mappings for the specialization based on a given type and its declared type.
|
||||
///
|
||||
/// The filter predicate is provided with a type variable and the type being mapped to it. Type
|
||||
/// mappings to which the predicate returns `false` will be ignored.
|
||||
pub(crate) fn infer_filter(
|
||||
&mut self,
|
||||
formal: Type<'db>,
|
||||
actual: Type<'db>,
|
||||
filter: impl Fn(BoundTypeVarIdentity<'db>, Type<'db>) -> bool,
|
||||
) -> Result<(), SpecializationError<'db>> {
|
||||
if formal == actual {
|
||||
return Ok(());
|
||||
@@ -1442,7 +1472,7 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
if remaining_actual.is_never() {
|
||||
return Ok(());
|
||||
}
|
||||
self.add_type_mapping(*formal_bound_typevar, remaining_actual);
|
||||
self.add_type_mapping(*formal_bound_typevar, remaining_actual, filter);
|
||||
}
|
||||
(Type::Union(formal), _) => {
|
||||
// Second, if the formal is a union, and precisely one union element _is_ a typevar (not
|
||||
@@ -1452,7 +1482,7 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
let bound_typevars =
|
||||
(formal.elements(self.db).iter()).filter_map(|ty| ty.as_typevar());
|
||||
if let Ok(bound_typevar) = bound_typevars.exactly_one() {
|
||||
self.add_type_mapping(bound_typevar, actual);
|
||||
self.add_type_mapping(bound_typevar, actual, filter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1480,15 +1510,23 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
argument: ty,
|
||||
});
|
||||
}
|
||||
self.add_type_mapping(bound_typevar, ty);
|
||||
self.add_type_mapping(bound_typevar, ty, filter);
|
||||
}
|
||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||
// Prefer an exact match first.
|
||||
for constraint in constraints.elements(self.db) {
|
||||
if ty == *constraint {
|
||||
self.add_type_mapping(bound_typevar, ty, filter);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
for constraint in constraints.elements(self.db) {
|
||||
if ty
|
||||
.when_assignable_to(self.db, *constraint, self.inferable)
|
||||
.is_always_satisfied(self.db)
|
||||
{
|
||||
self.add_type_mapping(bound_typevar, *constraint);
|
||||
self.add_type_mapping(bound_typevar, *constraint, filter);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -1498,7 +1536,7 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
self.add_type_mapping(bound_typevar, ty);
|
||||
self.add_type_mapping(bound_typevar, ty, filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6260,7 +6260,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
let inferred_elt_ty = self.get_or_infer_expression(elt, elt_tcx);
|
||||
|
||||
// Simplify the inference based on the declared type of the element.
|
||||
// Avoid widening the inferred type if it is already assignable to the preferred
|
||||
// declared type.
|
||||
if let Some(elt_tcx) = elt_tcx.annotation {
|
||||
if inferred_elt_ty.is_assignable_to(self.db(), elt_tcx) {
|
||||
continue;
|
||||
|
||||
@@ -67,6 +67,16 @@ class ConstraintSet:
|
||||
.. _subtype: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
||||
"""
|
||||
|
||||
def satisfied_by_all_typevars(
|
||||
self, *, inferable: tuple[Any, ...] | None = None
|
||||
) -> bool:
|
||||
"""
|
||||
Returns whether this constraint set is satisfied by all of the typevars
|
||||
that it mentions. You must provide a tuple of the typevars that should
|
||||
be considered `inferable`. All other typevars mentioned in the
|
||||
constraint set will be considered non-inferable.
|
||||
"""
|
||||
|
||||
def __bool__(self) -> bool: ...
|
||||
def __eq__(self, other: ConstraintSet) -> bool: ...
|
||||
def __ne__(self, other: ConstraintSet) -> bool: ...
|
||||
|
||||
@@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma
|
||||
stage: build
|
||||
interruptible: true
|
||||
image:
|
||||
name: ghcr.io/astral-sh/ruff:0.14.2-alpine
|
||||
name: ghcr.io/astral-sh/ruff:0.14.3-alpine
|
||||
before_script:
|
||||
- cd $CI_PROJECT_DIR
|
||||
- ruff --version
|
||||
@@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.2
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
@@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.2
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
@@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.2
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.2
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "scripts"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
description = ""
|
||||
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ project_root="$(dirname "$script_root")"
|
||||
echo "Updating metadata with rooster..."
|
||||
cd "$project_root"
|
||||
uvx --python 3.12 --isolated -- \
|
||||
rooster@0.1.0 release "$@"
|
||||
rooster@0.1.1 release "$@"
|
||||
|
||||
echo "Updating lockfile..."
|
||||
cargo update -p ruff
|
||||
|
||||
4
ty.schema.json
generated
4
ty.schema.json
generated
@@ -584,8 +584,8 @@
|
||||
]
|
||||
},
|
||||
"invalid-key": {
|
||||
"title": "detects invalid subscript accesses",
|
||||
"description": "## What it does\nChecks for subscript accesses with invalid keys.\n\n## Why is this bad?\nUsing an invalid key will raise a `KeyError` at runtime.\n\n## Examples\n```python\nfrom typing import TypedDict\n\nclass Person(TypedDict):\n name: str\n age: int\n\nalice = Person(name=\"Alice\", age=30)\nalice[\"height\"] # KeyError: 'height'\n```",
|
||||
"title": "detects invalid subscript accesses or TypedDict literal keys",
|
||||
"description": "## What it does\nChecks for subscript accesses with invalid keys and `TypedDict` construction with an\nunknown key.\n\n## Why is this bad?\nSubscripting with an invalid key will raise a `KeyError` at runtime.\n\nCreating a `TypedDict` with an unknown key is likely a mistake; if the `TypedDict` is\n`closed=true` it also violates the expectations of the type.\n\n## Examples\n```python\nfrom typing import TypedDict\n\nclass Person(TypedDict):\n name: str\n age: int\n\nalice = Person(name=\"Alice\", age=30)\nalice[\"height\"] # KeyError: 'height'\n\nbob: Person = { \"name\": \"Bob\", \"age\": 30 } # typo!\n\ncarol = Person(name=\"Carol\", age=25) # typo!\n```",
|
||||
"default": "error",
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user