Compare commits
11 Commits
0.14.10
...
zb/fix-sub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a2f78786a | ||
|
|
7b6d0a90bc | ||
|
|
07d352741a | ||
|
|
e177cc2a5a | ||
|
|
30ce679b9a | ||
|
|
76854fdb15 | ||
|
|
5a2d3cda3d | ||
|
|
fa57253980 | ||
|
|
b7fbd986bc | ||
|
|
3d334a313e | ||
|
|
2e44a861cb |
1
.github/mypy-primer-ty.toml
vendored
1
.github/mypy-primer-ty.toml
vendored
@@ -4,5 +4,6 @@
|
||||
# Enable off-by-default rules.
|
||||
[rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
possibly-missing-import = "warn"
|
||||
unused-ignore-comment = "warn"
|
||||
division-by-zero = "warn"
|
||||
|
||||
@@ -119,7 +119,7 @@ fn generate_markdown() -> String {
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
{status_text} ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||
|
||||
@@ -86,3 +86,26 @@ def f():
|
||||
# Multiple codes but none are used
|
||||
# ruff: disable[E741, F401, F841]
|
||||
print("hello")
|
||||
|
||||
|
||||
def f():
|
||||
# Unknown rule codes
|
||||
# ruff: disable[YF829]
|
||||
# ruff: disable[F841, RQW320]
|
||||
value = 0
|
||||
# ruff: enable[F841, RQW320]
|
||||
# ruff: enable[YF829]
|
||||
|
||||
|
||||
def f():
|
||||
# External rule codes should be ignored
|
||||
# ruff: disable[TK421]
|
||||
print("hello")
|
||||
# ruff: enable[TK421]
|
||||
|
||||
|
||||
def f():
|
||||
# Empty or missing rule codes
|
||||
# ruff: disable
|
||||
# ruff: disable[]
|
||||
print("hello")
|
||||
|
||||
@@ -1064,6 +1064,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
|
||||
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
|
||||
(Ruff, "102") => rules::ruff::rules::InvalidRuleCode,
|
||||
(Ruff, "103") => rules::ruff::rules::InvalidSuppressionComment,
|
||||
(Ruff, "104") => rules::ruff::rules::UnmatchedSuppressionComment,
|
||||
|
||||
(Ruff, "200") => rules::ruff::rules::InvalidPyprojectToml,
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
|
||||
@@ -313,12 +313,20 @@ mod tests {
|
||||
Rule::UnusedVariable,
|
||||
Rule::AmbiguousVariableName,
|
||||
Rule::UnusedNOQA,
|
||||
]),
|
||||
Rule::InvalidRuleCode,
|
||||
Rule::InvalidSuppressionComment,
|
||||
Rule::UnmatchedSuppressionComment,
|
||||
])
|
||||
.with_external_rules(&["TK421"]),
|
||||
&settings::LinterSettings::for_rules(vec![
|
||||
Rule::UnusedVariable,
|
||||
Rule::AmbiguousVariableName,
|
||||
Rule::UnusedNOQA,
|
||||
Rule::InvalidRuleCode,
|
||||
Rule::InvalidSuppressionComment,
|
||||
Rule::UnmatchedSuppressionComment,
|
||||
])
|
||||
.with_external_rules(&["TK421"])
|
||||
.with_preview_mode(),
|
||||
);
|
||||
Ok(())
|
||||
|
||||
@@ -9,6 +9,21 @@ use crate::registry::Rule;
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum InvalidRuleCodeKind {
|
||||
Noqa,
|
||||
Suppression,
|
||||
}
|
||||
|
||||
impl InvalidRuleCodeKind {
|
||||
fn as_str(&self) -> &str {
|
||||
match self {
|
||||
InvalidRuleCodeKind::Noqa => "`# noqa`",
|
||||
InvalidRuleCodeKind::Suppression => "suppression",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `noqa` codes that are invalid.
|
||||
///
|
||||
@@ -36,12 +51,17 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
#[violation_metadata(preview_since = "0.11.4")]
|
||||
pub(crate) struct InvalidRuleCode {
|
||||
pub(crate) rule_code: String,
|
||||
pub(crate) kind: InvalidRuleCodeKind,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for InvalidRuleCode {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Invalid rule code in `# noqa`: {}", self.rule_code)
|
||||
format!(
|
||||
"Invalid rule code in {}: {}",
|
||||
self.kind.as_str(),
|
||||
self.rule_code
|
||||
)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
@@ -61,7 +81,9 @@ pub(crate) fn invalid_noqa_code(
|
||||
continue;
|
||||
};
|
||||
|
||||
let all_valid = directive.iter().all(|code| code_is_valid(code, external));
|
||||
let all_valid = directive
|
||||
.iter()
|
||||
.all(|code| code_is_valid(code.as_str(), external));
|
||||
|
||||
if all_valid {
|
||||
continue;
|
||||
@@ -69,7 +91,7 @@ pub(crate) fn invalid_noqa_code(
|
||||
|
||||
let (valid_codes, invalid_codes): (Vec<_>, Vec<_>) = directive
|
||||
.iter()
|
||||
.partition(|&code| code_is_valid(code, external));
|
||||
.partition(|&code| code_is_valid(code.as_str(), external));
|
||||
|
||||
if valid_codes.is_empty() {
|
||||
all_codes_invalid_diagnostic(directive, invalid_codes, context);
|
||||
@@ -81,10 +103,9 @@ pub(crate) fn invalid_noqa_code(
|
||||
}
|
||||
}
|
||||
|
||||
fn code_is_valid(code: &Code, external: &[String]) -> bool {
|
||||
let code_str = code.as_str();
|
||||
Rule::from_code(get_redirect_target(code_str).unwrap_or(code_str)).is_ok()
|
||||
|| external.iter().any(|ext| code_str.starts_with(ext))
|
||||
pub(crate) fn code_is_valid(code: &str, external: &[String]) -> bool {
|
||||
Rule::from_code(get_redirect_target(code).unwrap_or(code)).is_ok()
|
||||
|| external.iter().any(|ext| code.starts_with(ext))
|
||||
}
|
||||
|
||||
fn all_codes_invalid_diagnostic(
|
||||
@@ -100,6 +121,7 @@ fn all_codes_invalid_diagnostic(
|
||||
.map(Code::as_str)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
kind: InvalidRuleCodeKind::Noqa,
|
||||
},
|
||||
directive.range(),
|
||||
)
|
||||
@@ -116,6 +138,7 @@ fn some_codes_are_invalid_diagnostic(
|
||||
.report_diagnostic(
|
||||
InvalidRuleCode {
|
||||
rule_code: invalid_code.to_string(),
|
||||
kind: InvalidRuleCodeKind::Noqa,
|
||||
},
|
||||
invalid_code.range(),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use crate::AlwaysFixableViolation;
|
||||
use crate::suppression::{InvalidSuppressionKind, ParseErrorKind};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for invalid suppression comments
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Invalid suppression comments are ignored by Ruff, and should either
|
||||
/// be fixed or removed to avoid confusion.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// ruff: disable # missing codes
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// # ruff: disable[E501]
|
||||
/// ```
|
||||
///
|
||||
/// Or delete the invalid suppression comment.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Ruff error suppression](https://docs.astral.sh/ruff/linter/#error-suppression)
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "0.14.11")]
|
||||
pub(crate) struct InvalidSuppressionComment {
|
||||
pub(crate) kind: InvalidSuppressionCommentKind,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for InvalidSuppressionComment {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let msg = match self.kind {
|
||||
InvalidSuppressionCommentKind::Invalid(InvalidSuppressionKind::Indentation) => {
|
||||
"unexpected indentation".to_string()
|
||||
}
|
||||
InvalidSuppressionCommentKind::Invalid(InvalidSuppressionKind::Trailing) => {
|
||||
"trailing comments are not supported".to_string()
|
||||
}
|
||||
InvalidSuppressionCommentKind::Invalid(InvalidSuppressionKind::Unmatched) => {
|
||||
"no matching 'disable' comment".to_string()
|
||||
}
|
||||
InvalidSuppressionCommentKind::Error(error) => format!("{error}"),
|
||||
};
|
||||
format!("Invalid suppression comment: {msg}")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove suppression comment".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum InvalidSuppressionCommentKind {
|
||||
Invalid(InvalidSuppressionKind),
|
||||
Error(ParseErrorKind),
|
||||
}
|
||||
@@ -22,6 +22,7 @@ pub(crate) use invalid_formatter_suppression_comment::*;
|
||||
pub(crate) use invalid_index_type::*;
|
||||
pub(crate) use invalid_pyproject_toml::*;
|
||||
pub(crate) use invalid_rule_code::*;
|
||||
pub(crate) use invalid_suppression_comment::*;
|
||||
pub(crate) use legacy_form_pytest_raises::*;
|
||||
pub(crate) use logging_eager_conversion::*;
|
||||
pub(crate) use map_int_version_parsing::*;
|
||||
@@ -46,6 +47,7 @@ pub(crate) use starmap_zip::*;
|
||||
pub(crate) use static_key_dict_comprehension::*;
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
pub(crate) use test_rules::*;
|
||||
pub(crate) use unmatched_suppression_comment::*;
|
||||
pub(crate) use unnecessary_cast_to_int::*;
|
||||
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
||||
pub(crate) use unnecessary_key_check::*;
|
||||
@@ -87,6 +89,7 @@ mod invalid_formatter_suppression_comment;
|
||||
mod invalid_index_type;
|
||||
mod invalid_pyproject_toml;
|
||||
mod invalid_rule_code;
|
||||
mod invalid_suppression_comment;
|
||||
mod legacy_form_pytest_raises;
|
||||
mod logging_eager_conversion;
|
||||
mod map_int_version_parsing;
|
||||
@@ -113,6 +116,7 @@ mod static_key_dict_comprehension;
|
||||
mod suppression_comment_visitor;
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
pub(crate) mod test_rules;
|
||||
mod unmatched_suppression_comment;
|
||||
mod unnecessary_cast_to_int;
|
||||
mod unnecessary_iterable_allocation_for_first_element;
|
||||
mod unnecessary_key_check;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use crate::Violation;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unmatched range suppression comments
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Unmatched range suppression comments can inadvertently suppress violations
|
||||
/// over larger sections of code than intended, particularly at module scope.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// # ruff: disable[E501] # unmatched
|
||||
/// REALLY_LONG_VALUES = [...]
|
||||
///
|
||||
/// print(REALLY_LONG_VALUES)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// # ruff: disable[E501]
|
||||
/// REALLY_LONG_VALUES = [...]
|
||||
/// # ruff: enable[E501]
|
||||
///
|
||||
/// print(REALLY_LONG_VALUES)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Ruff error suppression](https://docs.astral.sh/ruff/linter/#error-suppression)
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "0.14.11")]
|
||||
pub(crate) struct UnmatchedSuppressionComment;
|
||||
|
||||
impl Violation for UnmatchedSuppressionComment {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Suppression comment without matching `#ruff:enable` comment".to_string()
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 14
|
||||
Added: 11
|
||||
Removed: 15
|
||||
Added: 23
|
||||
|
||||
--- Removed ---
|
||||
E741 Ambiguous variable name: `I`
|
||||
@@ -238,8 +238,60 @@ help: Remove assignment to unused variable `I`
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
F841 [*] Local variable `value` is assigned to but never used
|
||||
--> suppressions.py:95:5
|
||||
|
|
||||
93 | # ruff: disable[YF829]
|
||||
94 | # ruff: disable[F841, RQW320]
|
||||
95 | value = 0
|
||||
| ^^^^^
|
||||
96 | # ruff: enable[F841, RQW320]
|
||||
97 | # ruff: enable[YF829]
|
||||
|
|
||||
help: Remove assignment to unused variable `value`
|
||||
92 | # Unknown rule codes
|
||||
93 | # ruff: disable[YF829]
|
||||
94 | # ruff: disable[F841, RQW320]
|
||||
- value = 0
|
||||
95 + pass
|
||||
96 | # ruff: enable[F841, RQW320]
|
||||
97 | # ruff: enable[YF829]
|
||||
98 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
|
||||
--- Added ---
|
||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
||||
--> suppressions.py:11:5
|
||||
|
|
||||
9 | # These should both be ignored by the implicit range suppression.
|
||||
10 | # Should also generate an "unmatched suppression" warning.
|
||||
11 | # ruff:disable[E741,F841]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
12 | I = 1
|
||||
|
|
||||
|
||||
|
||||
RUF103 [*] Invalid suppression comment: no matching 'disable' comment
|
||||
--> suppressions.py:19:5
|
||||
|
|
||||
17 | # should be generated.
|
||||
18 | I = 1
|
||||
19 | # ruff: enable[E741, F841]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Remove suppression comment
|
||||
16 | # Neither warning is ignored, and an "unmatched suppression"
|
||||
17 | # should be generated.
|
||||
18 | I = 1
|
||||
- # ruff: enable[E741, F841]
|
||||
19 |
|
||||
20 |
|
||||
21 | def f():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (non-enabled: `E501`)
|
||||
--> suppressions.py:46:5
|
||||
|
|
||||
@@ -298,6 +350,17 @@ help: Remove unused `noqa` directive
|
||||
58 |
|
||||
|
||||
|
||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
||||
--> suppressions.py:61:5
|
||||
|
|
||||
59 | def f():
|
||||
60 | # TODO: Duplicate codes should be counted as duplicate, not unused
|
||||
61 | # ruff: disable[F841, F841]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
62 | foo = 0
|
||||
|
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `F841`)
|
||||
--> suppressions.py:61:21
|
||||
|
|
||||
@@ -318,6 +381,18 @@ help: Remove unused suppression
|
||||
64 |
|
||||
|
||||
|
||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
||||
--> suppressions.py:68:5
|
||||
|
|
||||
66 | # Overlapping range suppressions, one should be marked as used,
|
||||
67 | # and the other should trigger an unused suppression diagnostic
|
||||
68 | # ruff: disable[F841]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
69 | # ruff: disable[F841]
|
||||
70 | foo = 0
|
||||
|
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `F841`)
|
||||
--> suppressions.py:69:5
|
||||
|
|
||||
@@ -337,6 +412,17 @@ help: Remove unused suppression
|
||||
71 |
|
||||
|
||||
|
||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
||||
--> suppressions.py:75:5
|
||||
|
|
||||
73 | def f():
|
||||
74 | # Multiple codes but only one is used
|
||||
75 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
76 | foo = 0
|
||||
|
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `E741`)
|
||||
--> suppressions.py:75:21
|
||||
|
|
||||
@@ -377,6 +463,17 @@ help: Remove unused suppression
|
||||
78 |
|
||||
|
||||
|
||||
RUF104 Suppression comment without matching `#ruff:enable` comment
|
||||
--> suppressions.py:81:5
|
||||
|
|
||||
79 | def f():
|
||||
80 | # Multiple codes but only two are used
|
||||
81 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
82 | I = 0
|
||||
|
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
||||
--> suppressions.py:81:27
|
||||
|
|
||||
@@ -413,6 +510,8 @@ help: Remove unused suppression
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
87 + # ruff: disable[F401, F841]
|
||||
88 | print("hello")
|
||||
89 |
|
||||
90 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
||||
@@ -431,6 +530,8 @@ help: Remove unused suppression
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
87 + # ruff: disable[E741, F841]
|
||||
88 | print("hello")
|
||||
89 |
|
||||
90 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `F841`)
|
||||
@@ -449,3 +550,122 @@ help: Remove unused suppression
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
87 + # ruff: disable[E741, F401]
|
||||
88 | print("hello")
|
||||
89 |
|
||||
90 |
|
||||
|
||||
|
||||
RUF102 [*] Invalid rule code in suppression: YF829
|
||||
--> suppressions.py:93:21
|
||||
|
|
||||
91 | def f():
|
||||
92 | # Unknown rule codes
|
||||
93 | # ruff: disable[YF829]
|
||||
| ^^^^^
|
||||
94 | # ruff: disable[F841, RQW320]
|
||||
95 | value = 0
|
||||
|
|
||||
help: Remove the rule code
|
||||
90 |
|
||||
91 | def f():
|
||||
92 | # Unknown rule codes
|
||||
- # ruff: disable[YF829]
|
||||
93 | # ruff: disable[F841, RQW320]
|
||||
94 | value = 0
|
||||
95 | # ruff: enable[F841, RQW320]
|
||||
|
||||
|
||||
RUF102 [*] Invalid rule code in suppression: RQW320
|
||||
--> suppressions.py:94:27
|
||||
|
|
||||
92 | # Unknown rule codes
|
||||
93 | # ruff: disable[YF829]
|
||||
94 | # ruff: disable[F841, RQW320]
|
||||
| ^^^^^^
|
||||
95 | value = 0
|
||||
96 | # ruff: enable[F841, RQW320]
|
||||
|
|
||||
help: Remove the rule code
|
||||
91 | def f():
|
||||
92 | # Unknown rule codes
|
||||
93 | # ruff: disable[YF829]
|
||||
- # ruff: disable[F841, RQW320]
|
||||
94 + # ruff: disable[F841]
|
||||
95 | value = 0
|
||||
96 | # ruff: enable[F841, RQW320]
|
||||
97 | # ruff: enable[YF829]
|
||||
|
||||
|
||||
RUF102 [*] Invalid rule code in suppression: RQW320
|
||||
--> suppressions.py:96:26
|
||||
|
|
||||
94 | # ruff: disable[F841, RQW320]
|
||||
95 | value = 0
|
||||
96 | # ruff: enable[F841, RQW320]
|
||||
| ^^^^^^
|
||||
97 | # ruff: enable[YF829]
|
||||
|
|
||||
help: Remove the rule code
|
||||
93 | # ruff: disable[YF829]
|
||||
94 | # ruff: disable[F841, RQW320]
|
||||
95 | value = 0
|
||||
- # ruff: enable[F841, RQW320]
|
||||
96 + # ruff: enable[F841]
|
||||
97 | # ruff: enable[YF829]
|
||||
98 |
|
||||
99 |
|
||||
|
||||
|
||||
RUF102 [*] Invalid rule code in suppression: YF829
|
||||
--> suppressions.py:97:20
|
||||
|
|
||||
95 | value = 0
|
||||
96 | # ruff: enable[F841, RQW320]
|
||||
97 | # ruff: enable[YF829]
|
||||
| ^^^^^
|
||||
|
|
||||
help: Remove the rule code
|
||||
94 | # ruff: disable[F841, RQW320]
|
||||
95 | value = 0
|
||||
96 | # ruff: enable[F841, RQW320]
|
||||
- # ruff: enable[YF829]
|
||||
97 |
|
||||
98 |
|
||||
99 | def f():
|
||||
|
||||
|
||||
RUF103 [*] Invalid suppression comment: missing suppression codes like `[E501, ...]`
|
||||
--> suppressions.py:109:5
|
||||
|
|
||||
107 | def f():
|
||||
108 | # Empty or missing rule codes
|
||||
109 | # ruff: disable
|
||||
| ^^^^^^^^^^^^^^^
|
||||
110 | # ruff: disable[]
|
||||
111 | print("hello")
|
||||
|
|
||||
help: Remove suppression comment
|
||||
106 |
|
||||
107 | def f():
|
||||
108 | # Empty or missing rule codes
|
||||
- # ruff: disable
|
||||
109 | # ruff: disable[]
|
||||
110 | print("hello")
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
RUF103 [*] Invalid suppression comment: missing suppression codes like `[E501, ...]`
|
||||
--> suppressions.py:110:5
|
||||
|
|
||||
108 | # Empty or missing rule codes
|
||||
109 | # ruff: disable
|
||||
110 | # ruff: disable[]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
111 | print("hello")
|
||||
|
|
||||
help: Remove suppression comment
|
||||
107 | def f():
|
||||
108 | # Empty or missing rule codes
|
||||
109 | # ruff: disable
|
||||
- # ruff: disable[]
|
||||
110 | print("hello")
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -471,6 +471,13 @@ impl LinterSettings {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_external_rules(mut self, rules: &[&str]) -> Self {
|
||||
self.external
|
||||
.extend(rules.iter().map(std::string::ToString::to_string));
|
||||
self
|
||||
}
|
||||
|
||||
/// Resolve the [`TargetVersion`] to use for linting.
|
||||
///
|
||||
/// This method respects the per-file version overrides in
|
||||
|
||||
@@ -4,6 +4,7 @@ use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||
use ruff_python_ast::whitespace::indentation;
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::cell::Cell;
|
||||
use std::{error::Error, fmt::Formatter};
|
||||
use thiserror::Error;
|
||||
@@ -17,7 +18,11 @@ use crate::checkers::ast::LintContext;
|
||||
use crate::codes::Rule;
|
||||
use crate::fix::edits::delete_comment;
|
||||
use crate::preview::is_range_suppressions_enabled;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA, UnusedNOQAKind};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::ruff::rules::{
|
||||
InvalidRuleCode, InvalidRuleCodeKind, InvalidSuppressionComment, InvalidSuppressionCommentKind,
|
||||
UnmatchedSuppressionComment, UnusedCodes, UnusedNOQA, UnusedNOQAKind, code_is_valid,
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@@ -130,7 +135,7 @@ impl Suppressions {
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.valid.is_empty()
|
||||
self.valid.is_empty() && self.invalid.is_empty() && self.errors.is_empty()
|
||||
}
|
||||
|
||||
/// Check if a diagnostic is suppressed by any known range suppressions
|
||||
@@ -150,7 +155,9 @@ impl Suppressions {
|
||||
};
|
||||
|
||||
for suppression in &self.valid {
|
||||
if *code == suppression.code.as_str() && suppression.range.contains_range(range) {
|
||||
let suppression_code =
|
||||
get_redirect_target(suppression.code.as_str()).unwrap_or(suppression.code.as_str());
|
||||
if *code == suppression_code && suppression.range.contains_range(range) {
|
||||
suppression.used.set(true);
|
||||
return true;
|
||||
}
|
||||
@@ -159,81 +166,140 @@ impl Suppressions {
|
||||
}
|
||||
|
||||
pub(crate) fn check_suppressions(&self, context: &LintContext, locator: &Locator) {
|
||||
if !context.any_rule_enabled(&[Rule::UnusedNOQA, Rule::InvalidRuleCode]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let unused = self
|
||||
.valid
|
||||
.iter()
|
||||
.filter(|suppression| !suppression.used.get());
|
||||
|
||||
for suppression in unused {
|
||||
let Ok(rule) = Rule::from_code(&suppression.code) else {
|
||||
continue; // TODO: invalid code
|
||||
};
|
||||
for comment in &suppression.comments {
|
||||
let mut range = comment.range;
|
||||
let edit = if comment.codes.len() == 1 {
|
||||
delete_comment(comment.range, locator)
|
||||
} else {
|
||||
let code_index = comment
|
||||
.codes
|
||||
.iter()
|
||||
.position(|range| locator.slice(range) == suppression.code)
|
||||
.unwrap();
|
||||
range = comment.codes[code_index];
|
||||
let code_range = if code_index < (comment.codes.len() - 1) {
|
||||
TextRange::new(
|
||||
comment.codes[code_index].start(),
|
||||
comment.codes[code_index + 1].start(),
|
||||
)
|
||||
} else {
|
||||
TextRange::new(
|
||||
comment.codes[code_index - 1].end(),
|
||||
comment.codes[code_index].end(),
|
||||
)
|
||||
let mut unmatched_ranges = FxHashSet::default();
|
||||
for suppression in &self.valid {
|
||||
if !code_is_valid(&suppression.code, &context.settings().external) {
|
||||
// InvalidRuleCode
|
||||
if context.is_rule_enabled(Rule::InvalidRuleCode) {
|
||||
for comment in &suppression.comments {
|
||||
let (range, edit) = Suppressions::delete_code_or_comment(
|
||||
locator,
|
||||
suppression,
|
||||
comment,
|
||||
true,
|
||||
);
|
||||
context
|
||||
.report_diagnostic(
|
||||
InvalidRuleCode {
|
||||
rule_code: suppression.code.to_string(),
|
||||
kind: InvalidRuleCodeKind::Suppression,
|
||||
},
|
||||
range,
|
||||
)
|
||||
.set_fix(Fix::safe_edit(edit));
|
||||
}
|
||||
}
|
||||
} else if !suppression.used.get() {
|
||||
// UnusedNOQA
|
||||
if context.is_rule_enabled(Rule::UnusedNOQA) {
|
||||
let Ok(rule) = Rule::from_code(
|
||||
get_redirect_target(&suppression.code).unwrap_or(&suppression.code),
|
||||
) else {
|
||||
continue; // "external" lint code, don't treat it as unused
|
||||
};
|
||||
Edit::range_deletion(code_range)
|
||||
};
|
||||
for comment in &suppression.comments {
|
||||
let (range, edit) = Suppressions::delete_code_or_comment(
|
||||
locator,
|
||||
suppression,
|
||||
comment,
|
||||
false,
|
||||
);
|
||||
|
||||
let codes = if context.is_rule_enabled(rule) {
|
||||
UnusedCodes {
|
||||
unmatched: vec![suppression.code.to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
UnusedCodes {
|
||||
disabled: vec![suppression.code.to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
let codes = if context.is_rule_enabled(rule) {
|
||||
UnusedCodes {
|
||||
unmatched: vec![suppression.code.to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
UnusedCodes {
|
||||
disabled: vec![suppression.code.to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
let mut diagnostic = context.report_diagnostic(
|
||||
UnusedNOQA {
|
||||
codes: Some(codes),
|
||||
kind: UnusedNOQAKind::Suppression,
|
||||
},
|
||||
range,
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
context
|
||||
.report_diagnostic(
|
||||
UnusedNOQA {
|
||||
codes: Some(codes),
|
||||
kind: UnusedNOQAKind::Suppression,
|
||||
},
|
||||
range,
|
||||
)
|
||||
.set_fix(Fix::safe_edit(edit));
|
||||
}
|
||||
}
|
||||
} else if suppression.comments.len() == 1 {
|
||||
// UnmatchedSuppressionComment
|
||||
let range = suppression.comments[0].range;
|
||||
if unmatched_ranges.insert(range) {
|
||||
context.report_diagnostic_if_enabled(UnmatchedSuppressionComment {}, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for error in self
|
||||
.errors
|
||||
.iter()
|
||||
.filter(|error| error.kind == ParseErrorKind::MissingCodes)
|
||||
{
|
||||
let mut diagnostic = context.report_diagnostic(
|
||||
UnusedNOQA {
|
||||
codes: Some(UnusedCodes::default()),
|
||||
kind: UnusedNOQAKind::Suppression,
|
||||
},
|
||||
error.range,
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(delete_comment(error.range, locator)));
|
||||
if context.is_rule_enabled(Rule::InvalidSuppressionComment) {
|
||||
for error in &self.errors {
|
||||
context
|
||||
.report_diagnostic(
|
||||
InvalidSuppressionComment {
|
||||
kind: InvalidSuppressionCommentKind::Error(error.kind),
|
||||
},
|
||||
error.range,
|
||||
)
|
||||
.set_fix(Fix::unsafe_edit(delete_comment(error.range, locator)));
|
||||
}
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::InvalidSuppressionComment) {
|
||||
for invalid in &self.invalid {
|
||||
context
|
||||
.report_diagnostic(
|
||||
InvalidSuppressionComment {
|
||||
kind: InvalidSuppressionCommentKind::Invalid(invalid.kind),
|
||||
},
|
||||
invalid.comment.range,
|
||||
)
|
||||
.set_fix(Fix::unsafe_edit(delete_comment(
|
||||
invalid.comment.range,
|
||||
locator,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_code_or_comment(
|
||||
locator: &Locator<'_>,
|
||||
suppression: &Suppression,
|
||||
comment: &SuppressionComment,
|
||||
highlight_only_code: bool,
|
||||
) -> (TextRange, Edit) {
|
||||
let mut range = comment.range;
|
||||
let edit = if comment.codes.len() == 1 {
|
||||
if highlight_only_code {
|
||||
range = comment.codes[0];
|
||||
}
|
||||
delete_comment(comment.range, locator)
|
||||
} else {
|
||||
let code_index = comment
|
||||
.codes
|
||||
.iter()
|
||||
.position(|range| locator.slice(range) == suppression.code)
|
||||
.unwrap();
|
||||
range = comment.codes[code_index];
|
||||
let code_range = if code_index < (comment.codes.len() - 1) {
|
||||
TextRange::new(
|
||||
comment.codes[code_index].start(),
|
||||
comment.codes[code_index + 1].start(),
|
||||
)
|
||||
} else {
|
||||
TextRange::new(
|
||||
comment.codes[code_index - 1].end(),
|
||||
comment.codes[code_index].end(),
|
||||
)
|
||||
};
|
||||
Edit::range_deletion(code_range)
|
||||
};
|
||||
(range, edit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +457,7 @@ impl<'a> SuppressionsBuilder<'a> {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
|
||||
enum ParseErrorKind {
|
||||
pub(crate) enum ParseErrorKind {
|
||||
#[error("not a suppression comment")]
|
||||
NotASuppression,
|
||||
|
||||
@@ -401,7 +467,7 @@ enum ParseErrorKind {
|
||||
#[error("unknown ruff directive")]
|
||||
UnknownAction,
|
||||
|
||||
#[error("missing suppression codes")]
|
||||
#[error("missing suppression codes like `[E501, ...]`")]
|
||||
MissingCodes,
|
||||
|
||||
#[error("missing closing bracket")]
|
||||
|
||||
230
crates/ty/docs/rules.md
generated
230
crates/ty/docs/rules.md
generated
@@ -5,7 +5,7 @@
|
||||
## `byte-string-type-annotation`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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%20byte-string-type-annotation" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L36" target="_blank">View source</a>
|
||||
@@ -36,7 +36,7 @@ def test(): -> "int":
|
||||
## `call-non-callable`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135" target="_blank">View source</a>
|
||||
@@ -60,7 +60,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
## `conflicting-argument-forms`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L179" target="_blank">View source</a>
|
||||
@@ -92,7 +92,7 @@ f(int) # error
|
||||
## `conflicting-declarations`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L205" target="_blank">View source</a>
|
||||
@@ -123,7 +123,7 @@ a = 1
|
||||
## `conflicting-metaclass`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L230" target="_blank">View source</a>
|
||||
@@ -155,7 +155,7 @@ class C(A, B): ...
|
||||
## `cyclic-class-definition`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256" target="_blank">View source</a>
|
||||
@@ -187,7 +187,7 @@ class B(A): ...
|
||||
## `cyclic-type-alias-definition`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L282" target="_blank">View source</a>
|
||||
@@ -215,7 +215,7 @@ type B = A
|
||||
## `duplicate-base`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343" target="_blank">View source</a>
|
||||
@@ -242,7 +242,7 @@ class B(A, A): ...
|
||||
## `duplicate-kw-only`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364" target="_blank">View source</a>
|
||||
@@ -280,7 +280,7 @@ class A: # Crash at runtime
|
||||
## `escape-character-in-forward-annotation`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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%20escape-character-in-forward-annotation" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L120" target="_blank">View source</a>
|
||||
@@ -292,7 +292,7 @@ TODO #14889
|
||||
## `fstring-type-annotation`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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%20fstring-type-annotation" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L11" target="_blank">View source</a>
|
||||
@@ -323,7 +323,7 @@ def test(): -> "int":
|
||||
## `implicit-concatenated-string-type-annotation`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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%20implicit-concatenated-string-type-annotation" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L86" target="_blank">View source</a>
|
||||
@@ -354,7 +354,7 @@ def test(): -> "Literal[5]":
|
||||
## `inconsistent-mro`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L590" target="_blank">View source</a>
|
||||
@@ -384,7 +384,7 @@ class C(A, B): ...
|
||||
## `index-out-of-bounds`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L614" target="_blank">View source</a>
|
||||
@@ -410,7 +410,7 @@ t[3] # IndexError: tuple index out of range
|
||||
## `instance-layout-conflict`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L396" target="_blank">View source</a>
|
||||
@@ -499,7 +499,7 @@ an atypical memory layout.
|
||||
## `invalid-argument-type`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L668" target="_blank">View source</a>
|
||||
@@ -526,7 +526,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
## `invalid-assignment`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L708" target="_blank">View source</a>
|
||||
@@ -554,7 +554,7 @@ a: int = ''
|
||||
## `invalid-attribute-access`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L2003" target="_blank">View source</a>
|
||||
@@ -588,7 +588,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
## `invalid-await`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L730" target="_blank">View source</a>
|
||||
@@ -624,7 +624,7 @@ asyncio.run(main())
|
||||
## `invalid-base`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L760" target="_blank">View source</a>
|
||||
@@ -648,7 +648,7 @@ class A(42): ... # error: [invalid-base]
|
||||
## `invalid-context-manager`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811" target="_blank">View source</a>
|
||||
@@ -675,7 +675,7 @@ with 1:
|
||||
## `invalid-declaration`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L832" target="_blank">View source</a>
|
||||
@@ -704,7 +704,7 @@ a: str
|
||||
## `invalid-exception-caught`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L855" target="_blank">View source</a>
|
||||
@@ -748,7 +748,7 @@ except ZeroDivisionError:
|
||||
## `invalid-explicit-override`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1673" target="_blank">View source</a>
|
||||
@@ -790,7 +790,7 @@ class D(A):
|
||||
## `invalid-frozen-dataclass-subclass`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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.35">0.0.1-alpha.35</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2229" target="_blank">View source</a>
|
||||
@@ -834,7 +834,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
|
||||
## `invalid-generic-class`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L891" target="_blank">View source</a>
|
||||
@@ -872,7 +872,7 @@ class D(Generic[U, T]): ...
|
||||
## `invalid-key`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L635" target="_blank">View source</a>
|
||||
@@ -911,7 +911,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
## `invalid-legacy-type-variable`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L922" target="_blank">View source</a>
|
||||
@@ -946,7 +946,7 @@ def f(t: TypeVar("U")): ...
|
||||
## `invalid-metaclass`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1019" target="_blank">View source</a>
|
||||
@@ -980,7 +980,7 @@ class B(metaclass=f): ...
|
||||
## `invalid-method-override`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2131" target="_blank">View source</a>
|
||||
@@ -1087,7 +1087,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
## `invalid-named-tuple`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542" target="_blank">View source</a>
|
||||
@@ -1141,7 +1141,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
|
||||
## `invalid-newtype`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L995" target="_blank">View source</a>
|
||||
@@ -1171,7 +1171,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
## `invalid-overload`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1046" target="_blank">View source</a>
|
||||
@@ -1221,7 +1221,7 @@ def foo(x: int) -> int: ...
|
||||
## `invalid-parameter-default`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1145" target="_blank">View source</a>
|
||||
@@ -1247,7 +1247,7 @@ def f(a: int = ''): ...
|
||||
## `invalid-paramspec`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L950" target="_blank">View source</a>
|
||||
@@ -1278,7 +1278,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
## `invalid-protocol`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L478" target="_blank">View source</a>
|
||||
@@ -1312,7 +1312,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
## `invalid-raise`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1165" target="_blank">View source</a>
|
||||
@@ -1361,7 +1361,7 @@ def g():
|
||||
## `invalid-return-type`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L689" target="_blank">View source</a>
|
||||
@@ -1386,7 +1386,7 @@ def func() -> int:
|
||||
## `invalid-super-argument`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1208" target="_blank">View source</a>
|
||||
@@ -1432,7 +1432,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)`
|
||||
## `invalid-syntax-in-forward-annotation`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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-syntax-in-forward-annotation" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L111" target="_blank">View source</a>
|
||||
@@ -1444,7 +1444,7 @@ TODO #14889
|
||||
## `invalid-type-alias-type`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L974" target="_blank">View source</a>
|
||||
@@ -1471,7 +1471,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
## `invalid-type-arguments`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1440" target="_blank">View source</a>
|
||||
@@ -1518,7 +1518,7 @@ Bar[int] # error: too few arguments
|
||||
## `invalid-type-checking-constant`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1247" target="_blank">View source</a>
|
||||
@@ -1548,7 +1548,7 @@ TYPE_CHECKING = ''
|
||||
## `invalid-type-form`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1271" target="_blank">View source</a>
|
||||
@@ -1578,7 +1578,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
## `invalid-type-guard-call`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1323" target="_blank">View source</a>
|
||||
@@ -1612,7 +1612,7 @@ f(10) # Error
|
||||
## `invalid-type-guard-definition`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1295" target="_blank">View source</a>
|
||||
@@ -1646,7 +1646,7 @@ class C:
|
||||
## `invalid-type-variable-constraints`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1351" target="_blank">View source</a>
|
||||
@@ -1681,7 +1681,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
## `missing-argument`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1380" target="_blank">View source</a>
|
||||
@@ -1706,7 +1706,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
## `missing-typed-dict-key`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L2104" target="_blank">View source</a>
|
||||
@@ -1739,7 +1739,7 @@ alice["age"] # KeyError
|
||||
## `no-matching-overload`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1399" target="_blank">View source</a>
|
||||
@@ -1768,7 +1768,7 @@ func("string") # error: [no-matching-overload]
|
||||
## `non-subscriptable`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1422" target="_blank">View source</a>
|
||||
@@ -1792,7 +1792,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
## `not-iterable`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1481" target="_blank">View source</a>
|
||||
@@ -1818,7 +1818,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
## `override-of-final-method`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1646" target="_blank">View source</a>
|
||||
@@ -1851,7 +1851,7 @@ class B(A):
|
||||
## `parameter-already-assigned`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1532" target="_blank">View source</a>
|
||||
@@ -1878,7 +1878,7 @@ f(1, x=2) # Error raised here
|
||||
## `positional-only-parameter-as-kwarg`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1857" target="_blank">View source</a>
|
||||
@@ -1905,7 +1905,7 @@ f(x=1) # Error raised here
|
||||
## `raw-string-type-annotation`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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%20raw-string-type-annotation" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L61" target="_blank">View source</a>
|
||||
@@ -1936,7 +1936,7 @@ def test(): -> "int":
|
||||
## `static-assert-error`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1979" target="_blank">View source</a>
|
||||
@@ -1966,7 +1966,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
## `subclass-of-final-class`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1623" target="_blank">View source</a>
|
||||
@@ -1995,7 +1995,7 @@ class B(A): ... # Error raised here
|
||||
## `super-call-in-named-tuple-method`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1791" target="_blank">View source</a>
|
||||
@@ -2029,7 +2029,7 @@ class F(NamedTuple):
|
||||
## `too-many-positional-arguments`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1731" target="_blank">View source</a>
|
||||
@@ -2056,7 +2056,7 @@ f("foo") # Error raised here
|
||||
## `type-assertion-failure`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1709" target="_blank">View source</a>
|
||||
@@ -2084,7 +2084,7 @@ def _(x: int):
|
||||
## `unavailable-implicit-super-arguments`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1752" target="_blank">View source</a>
|
||||
@@ -2130,7 +2130,7 @@ class A:
|
||||
## `unknown-argument`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1836" target="_blank">View source</a>
|
||||
@@ -2157,7 +2157,7 @@ f(x=1, y=2) # Error raised here
|
||||
## `unresolved-attribute`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1878" target="_blank">View source</a>
|
||||
@@ -2185,7 +2185,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
## `unresolved-import`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1900" target="_blank">View source</a>
|
||||
@@ -2210,7 +2210,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
## `unresolved-reference`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1919" target="_blank">View source</a>
|
||||
@@ -2235,7 +2235,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
## `unsupported-bool-conversion`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1501" target="_blank">View source</a>
|
||||
@@ -2272,7 +2272,7 @@ b1 < b2 < b1 # exception raised here
|
||||
## `unsupported-operator`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1938" target="_blank">View source</a>
|
||||
@@ -2300,7 +2300,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
## `zero-stepsize-in-slice`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1960" target="_blank">View source</a>
|
||||
@@ -2325,7 +2325,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
## `ambiguous-protocol-member`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L507" target="_blank">View source</a>
|
||||
@@ -2366,7 +2366,7 @@ class SubProto(BaseProto, Protocol):
|
||||
## `deprecated`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L322" target="_blank">View source</a>
|
||||
@@ -2393,7 +2393,7 @@ old_func() # emits [deprecated] diagnostic
|
||||
## `ignore-comment-unknown-rule`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#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%20ignore-comment-unknown-rule" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L47" target="_blank">View source</a>
|
||||
@@ -2424,7 +2424,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
## `invalid-ignore-comment`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#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%20invalid-ignore-comment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L72" target="_blank">View source</a>
|
||||
@@ -2454,7 +2454,7 @@ a = 20 / 0 # type: ignore
|
||||
## `possibly-missing-attribute`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1553" target="_blank">View source</a>
|
||||
@@ -2482,7 +2482,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
## `possibly-missing-implicit-call`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L153" target="_blank">View source</a>
|
||||
@@ -2511,42 +2511,10 @@ class A:
|
||||
A()[0] # TypeError: 'A' object is not subscriptable
|
||||
```
|
||||
|
||||
## `possibly-missing-import`
|
||||
|
||||
<small>
|
||||
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#L1575" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for imports of symbols that may be missing.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Importing a missing module or name will raise a `ModuleNotFoundError`
|
||||
or `ImportError` at runtime.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
# module.py
|
||||
import datetime
|
||||
|
||||
if datetime.date.today().weekday() != 6:
|
||||
a = 1
|
||||
|
||||
# main.py
|
||||
from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
```
|
||||
|
||||
## `redundant-cast`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#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#L2031" target="_blank">View source</a>
|
||||
@@ -2573,7 +2541,7 @@ cast(int, f()) # Redundant
|
||||
## `undefined-reveal`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1818" target="_blank">View source</a>
|
||||
@@ -2597,7 +2565,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
## `unresolved-global`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#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#L2052" target="_blank">View source</a>
|
||||
@@ -2655,7 +2623,7 @@ def g():
|
||||
## `unsupported-base`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L778" target="_blank">View source</a>
|
||||
@@ -2694,7 +2662,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
## `useless-overload-body`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1089" target="_blank">View source</a>
|
||||
@@ -2757,7 +2725,7 @@ def foo(x: int | str) -> int | str:
|
||||
## `division-by-zero`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L304" target="_blank">View source</a>
|
||||
@@ -2778,10 +2746,42 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
5 / 0
|
||||
```
|
||||
|
||||
## `possibly-missing-import`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../../rules#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.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#L1575" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for imports of symbols that may be missing.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Importing a missing module or name will raise a `ModuleNotFoundError`
|
||||
or `ImportError` at runtime.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
# module.py
|
||||
import datetime
|
||||
|
||||
if datetime.date.today().weekday() != 6:
|
||||
a = 1
|
||||
|
||||
# main.py
|
||||
from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
```
|
||||
|
||||
## `possibly-unresolved-reference`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Default level: <a href="../../rules#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#L1601" target="_blank">View source</a>
|
||||
@@ -2809,7 +2809,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
## `unused-ignore-comment`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Default level: <a href="../../rules#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%20unused-ignore-comment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L22" target="_blank">View source</a>
|
||||
|
||||
@@ -451,6 +451,10 @@ class Answer(Enum):
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
|
||||
# `nonmember` attributes are unwrapped to the inner value type when accessed.
|
||||
# revealed: int
|
||||
reveal_type(Answer.OTHER)
|
||||
```
|
||||
|
||||
`member` can also be used as a decorator:
|
||||
|
||||
@@ -1726,5 +1726,298 @@ reveal_type(actual_td) # revealed: ActualTypedDict
|
||||
reveal_type(actual_td["name"]) # revealed: str
|
||||
```
|
||||
|
||||
## Disjointness with other `TypedDict`s
|
||||
|
||||
Two `TypedDict` types are disjoint if it's impossible to come up with a third (fully-static)
|
||||
`TypedDict` that's assignable to both. The simplest way to establish this is if both sides have
|
||||
fields with the same name but disjoint types:
|
||||
|
||||
```py
|
||||
from typing import TypedDict, final
|
||||
from typing_extensions import ReadOnly
|
||||
from ty_extensions import static_assert, is_disjoint_from
|
||||
|
||||
# Two simple disjoint types, to avoid relying on `@disjoint_base` special cases for built-ins like
|
||||
# `int` and `str`.
|
||||
@final
|
||||
class Final1: ...
|
||||
|
||||
@final
|
||||
class Final2: ...
|
||||
|
||||
static_assert(is_disjoint_from(Final1, Final2))
|
||||
|
||||
class DisjointTD1(TypedDict):
|
||||
# Make this example `ReadOnly` because that actually ends up checking the field types for
|
||||
# disjointness in practice. Mutable fields are stricter. We'll get to that below.
|
||||
disjoint: ReadOnly[Final1]
|
||||
# While we're here: It doesn't matter how many other compatible fields there are. Just the one
|
||||
# incompatible field above establishes disjointness.
|
||||
common1: object
|
||||
common2: object
|
||||
|
||||
class DisjointTD2(TypedDict):
|
||||
disjoint: ReadOnly[Final2]
|
||||
common1: object
|
||||
common2: object
|
||||
|
||||
static_assert(is_disjoint_from(DisjointTD1, DisjointTD2))
|
||||
```
|
||||
|
||||
However, note that most pairs of non-final classes are *not* disjoint from each other, even if
|
||||
neither inherits from the other, because we could define a third class that multiply-inherits from
|
||||
both. `TypedDict` disjointness takes this into account. For example:
|
||||
|
||||
```py
|
||||
from ty_extensions import is_assignable_to
|
||||
|
||||
class NonFinal1: ...
|
||||
class NonFinal2: ...
|
||||
class CommonSub(NonFinal1, NonFinal2): ...
|
||||
|
||||
static_assert(not is_disjoint_from(NonFinal1, NonFinal2))
|
||||
static_assert(not is_assignable_to(NonFinal1, NonFinal2))
|
||||
static_assert(is_assignable_to(CommonSub, NonFinal1))
|
||||
static_assert(is_assignable_to(CommonSub, NonFinal2))
|
||||
|
||||
class NonDisjointTD1(TypedDict):
|
||||
non_disjoint: ReadOnly[NonFinal1]
|
||||
# While we're here: It doesn't matter how many "extra" fields there are, or what order the
|
||||
# fields are in. Only shared field names can establish disjointness.
|
||||
extra1: int
|
||||
|
||||
class NonDisjointTD2(TypedDict):
|
||||
extra2: str
|
||||
non_disjoint: ReadOnly[NonFinal2]
|
||||
|
||||
class CommonSubTD(TypedDict):
|
||||
extra2: str
|
||||
extra1: int
|
||||
non_disjoint: ReadOnly[CommonSub]
|
||||
|
||||
# The first two TDs above are not assignable in either direction...
|
||||
static_assert(not is_assignable_to(NonDisjointTD1, NonDisjointTD2))
|
||||
static_assert(not is_assignable_to(NonDisjointTD2, NonDisjointTD1))
|
||||
# ...but they're still not disjoint...
|
||||
static_assert(not is_disjoint_from(NonDisjointTD1, NonDisjointTD2))
|
||||
# ...because the third TD above is assignable to both of them.
|
||||
static_assert(is_assignable_to(CommonSubTD, NonDisjointTD1))
|
||||
static_assert(is_assignable_to(CommonSubTD, NonDisjointTD2))
|
||||
static_assert(not is_disjoint_from(CommonSubTD, NonDisjointTD1))
|
||||
static_assert(not is_disjoint_from(CommonSubTD, NonDisjointTD2))
|
||||
```
|
||||
|
||||
We made the important fields `ReadOnly` above, because those only establish disjointness when
|
||||
they're disjoint themselves. However, the rules for mutable fields are stricter. Mutable fields in
|
||||
common need to have *compatible* types (in the fully-static case, equivalent types):
|
||||
|
||||
```py
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
class IntTD(TypedDict):
|
||||
x: int
|
||||
|
||||
class BoolTD(TypedDict):
|
||||
x: bool
|
||||
|
||||
# `bool` is assignable to `int`, but `int` is not assignable to `bool`. If `x` was `ReadOnly` (even,
|
||||
# as we'll see below, only on the `int` side), then these two TDs would not be disjoint, but in this
|
||||
# mutable case they are.
|
||||
|
||||
static_assert(is_disjoint_from(IntTD, BoolTD))
|
||||
static_assert(is_disjoint_from(BoolTD, IntTD))
|
||||
|
||||
# Gradual types: `int` is compatible with `bool | Any`, because that could materialize to
|
||||
# `bool | int`, which is just `int`. (And `int | Any` and `bool | Any` are compatible with each
|
||||
# other for the same reason.) However, `bool` is *not* compatible with `int | Any`, because there's
|
||||
# no materialization that's equivalent to `bool`.
|
||||
|
||||
class IntOrAnyTD(TypedDict):
|
||||
x: int | Any
|
||||
|
||||
class BoolOrAnyTD(TypedDict):
|
||||
x: bool | Any
|
||||
|
||||
static_assert(not is_disjoint_from(IntTD, IntOrAnyTD))
|
||||
static_assert(not is_disjoint_from(IntOrAnyTD, IntTD))
|
||||
static_assert(not is_disjoint_from(IntTD, BoolOrAnyTD))
|
||||
static_assert(not is_disjoint_from(BoolOrAnyTD, IntTD))
|
||||
|
||||
static_assert(not is_disjoint_from(IntOrAnyTD, BoolOrAnyTD))
|
||||
static_assert(not is_disjoint_from(BoolOrAnyTD, IntOrAnyTD))
|
||||
|
||||
static_assert(is_disjoint_from(BoolTD, IntOrAnyTD))
|
||||
static_assert(is_disjoint_from(IntOrAnyTD, BoolTD))
|
||||
static_assert(not is_disjoint_from(BoolTD, BoolOrAnyTD))
|
||||
static_assert(not is_disjoint_from(BoolOrAnyTD, BoolTD))
|
||||
|
||||
# `Any` is compatible with everything.
|
||||
|
||||
class AnyTD(TypedDict):
|
||||
x: Any
|
||||
|
||||
static_assert(not is_disjoint_from(IntTD, AnyTD))
|
||||
static_assert(not is_disjoint_from(AnyTD, IntTD))
|
||||
static_assert(not is_disjoint_from(BoolTD, AnyTD))
|
||||
static_assert(not is_disjoint_from(AnyTD, BoolTD))
|
||||
static_assert(not is_disjoint_from(IntOrAnyTD, AnyTD))
|
||||
static_assert(not is_disjoint_from(AnyTD, IntOrAnyTD))
|
||||
static_assert(not is_disjoint_from(BoolOrAnyTD, AnyTD))
|
||||
static_assert(not is_disjoint_from(AnyTD, BoolOrAnyTD))
|
||||
static_assert(not is_disjoint_from(AnyTD, AnyTD))
|
||||
|
||||
# This works with generic `TypedDict`s too.
|
||||
|
||||
class TwoIntsTD(TypedDict):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
class TwoBoolsTD(TypedDict):
|
||||
x: bool
|
||||
y: bool
|
||||
|
||||
class IntBoolTD(TypedDict):
|
||||
x: int
|
||||
y: bool
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class TwoGenericTD(TypedDict, Generic[T]):
|
||||
x: T
|
||||
y: T
|
||||
|
||||
static_assert(not is_disjoint_from(TwoGenericTD[Any], TwoIntsTD))
|
||||
static_assert(not is_disjoint_from(TwoGenericTD[int], TwoIntsTD))
|
||||
static_assert(is_disjoint_from(TwoGenericTD[bool], TwoIntsTD))
|
||||
static_assert(not is_disjoint_from(TwoGenericTD[Any], TwoBoolsTD))
|
||||
static_assert(is_disjoint_from(TwoGenericTD[int], TwoBoolsTD))
|
||||
static_assert(not is_disjoint_from(TwoGenericTD[bool], TwoBoolsTD))
|
||||
# TODO: T can't be compatible with both `int` and `bool` at the same time, so these types should be
|
||||
# disjoint, regardless of the materialization of `T`.
|
||||
static_assert(not is_disjoint_from(TwoGenericTD[Any], IntBoolTD))
|
||||
```
|
||||
|
||||
If one side is mutable but the other is not, then a "third `TypedDict` that's assignable to both"
|
||||
would have to have the same type as the mutable side, so we establish disjointness if that type
|
||||
isn't assignable to the immutable side:
|
||||
|
||||
```py
|
||||
class ReadOnlyIntTD(TypedDict):
|
||||
x: ReadOnly[int]
|
||||
|
||||
class ReadOnlyBoolTD(TypedDict):
|
||||
x: ReadOnly[bool]
|
||||
|
||||
static_assert(not is_disjoint_from(ReadOnlyIntTD, ReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyBoolTD, ReadOnlyIntTD))
|
||||
static_assert(not is_disjoint_from(BoolTD, ReadOnlyIntTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyIntTD, BoolTD))
|
||||
static_assert(is_disjoint_from(IntTD, ReadOnlyBoolTD))
|
||||
static_assert(is_disjoint_from(ReadOnlyBoolTD, IntTD))
|
||||
```
|
||||
|
||||
With mutability above we were able to make the simplifying assumption that the "third `TypedDict`
|
||||
that's assignable to both" has only mutable fields, because a mutable field is always assignable to
|
||||
its immutable counterpart. However, `Required` vs `NotRequired` are more complicated, because a a
|
||||
`Required` field is *not* necessarily assignable to its `NotRequired` counterpart. In particular, if
|
||||
a `NotRequired` field is also mutable (intuitively, if we're allowed to `del` it), then no
|
||||
`Required` field is ever assignable to it. So, if either side is `NotRequired` and mutable, and the
|
||||
other side is `Required` (regardless of mutability), then that's sufficient to establish
|
||||
disjointness:
|
||||
|
||||
```py
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
class NotRequiredIntTD(TypedDict):
|
||||
x: NotRequired[int]
|
||||
|
||||
class NotRequiredReadOnlyIntTD(TypedDict):
|
||||
x: NotRequired[ReadOnly[int]]
|
||||
|
||||
static_assert(is_disjoint_from(NotRequiredIntTD, IntTD))
|
||||
static_assert(is_disjoint_from(IntTD, NotRequiredIntTD))
|
||||
static_assert(is_disjoint_from(NotRequiredIntTD, ReadOnlyIntTD))
|
||||
static_assert(is_disjoint_from(ReadOnlyIntTD, NotRequiredIntTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredIntTD, NotRequiredReadOnlyIntTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredReadOnlyIntTD, NotRequiredIntTD))
|
||||
```
|
||||
|
||||
All those rules put together give us the "full disjointness table". We've pretty well tested above
|
||||
that disjointness is symmetrical, so here we won't worry about asserting both directions for each
|
||||
check:
|
||||
|
||||
```py
|
||||
class NotRequiredBoolTD(TypedDict):
|
||||
x: NotRequired[bool]
|
||||
|
||||
class NotRequiredReadOnlyBoolTD(TypedDict):
|
||||
x: NotRequired[ReadOnly[bool]]
|
||||
|
||||
static_assert(not is_disjoint_from(IntTD, IntTD))
|
||||
static_assert(is_disjoint_from(IntTD, BoolTD))
|
||||
static_assert(not is_disjoint_from(IntTD, ReadOnlyIntTD))
|
||||
static_assert(is_disjoint_from(IntTD, ReadOnlyBoolTD))
|
||||
static_assert(is_disjoint_from(IntTD, NotRequiredIntTD))
|
||||
static_assert(is_disjoint_from(IntTD, NotRequiredBoolTD))
|
||||
static_assert(not is_disjoint_from(IntTD, NotRequiredReadOnlyIntTD))
|
||||
static_assert(is_disjoint_from(IntTD, NotRequiredReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyIntTD, BoolTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyIntTD, ReadOnlyIntTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyIntTD, ReadOnlyBoolTD))
|
||||
static_assert(is_disjoint_from(ReadOnlyIntTD, NotRequiredIntTD))
|
||||
static_assert(is_disjoint_from(ReadOnlyIntTD, NotRequiredBoolTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyIntTD, NotRequiredReadOnlyIntTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyIntTD, NotRequiredReadOnlyBoolTD))
|
||||
static_assert(is_disjoint_from(NotRequiredIntTD, BoolTD))
|
||||
static_assert(is_disjoint_from(NotRequiredIntTD, ReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredIntTD, NotRequiredIntTD))
|
||||
static_assert(is_disjoint_from(NotRequiredIntTD, NotRequiredBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredIntTD, NotRequiredReadOnlyIntTD))
|
||||
static_assert(is_disjoint_from(NotRequiredIntTD, NotRequiredReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredReadOnlyIntTD, BoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredReadOnlyIntTD, ReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredReadOnlyIntTD, NotRequiredBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredReadOnlyIntTD, NotRequiredReadOnlyIntTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredReadOnlyIntTD, NotRequiredReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(BoolTD, BoolTD))
|
||||
static_assert(not is_disjoint_from(BoolTD, ReadOnlyBoolTD))
|
||||
static_assert(is_disjoint_from(BoolTD, NotRequiredBoolTD))
|
||||
static_assert(not is_disjoint_from(BoolTD, NotRequiredReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyBoolTD, ReadOnlyBoolTD))
|
||||
static_assert(is_disjoint_from(ReadOnlyBoolTD, NotRequiredBoolTD))
|
||||
static_assert(not is_disjoint_from(ReadOnlyBoolTD, NotRequiredReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredBoolTD, NotRequiredBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredBoolTD, NotRequiredReadOnlyBoolTD))
|
||||
static_assert(not is_disjoint_from(NotRequiredReadOnlyBoolTD, NotRequiredReadOnlyBoolTD))
|
||||
```
|
||||
|
||||
## Disjointness with other types
|
||||
|
||||
```py
|
||||
from typing import TypedDict, Mapping
|
||||
from ty_extensions import static_assert, is_disjoint_from
|
||||
|
||||
class TD(TypedDict):
|
||||
x: int
|
||||
|
||||
class RegularNonTD: ...
|
||||
|
||||
static_assert(not is_disjoint_from(TD, object))
|
||||
static_assert(not is_disjoint_from(TD, Mapping[str, object]))
|
||||
static_assert(is_disjoint_from(TD, Mapping[int, object]))
|
||||
static_assert(is_disjoint_from(TD, RegularNonTD))
|
||||
|
||||
# TODO: We approximate disjointness with other types `T` by asking whether `dict[str, Any]` is
|
||||
# assignable to `T`. That covers common cases like the ones above, but does it have some false
|
||||
# negatives with `dict` types. A `TypedDict` is almost never assignable to a `dict` (or vice versa),
|
||||
# even when all of the `TypedDict`'s field types match the `dict`'s value type (and are mutable).
|
||||
# The problem is that the `TypedDict` could have been assigned to from *another* `TypedDict` with
|
||||
# additional fields, and we don't usually know anything about the types or mutability of those. On
|
||||
# the other hand, the assignment to `dict` can be allowed if the `TypedDict` has mutable
|
||||
# `extra_items` of a compatible type. See: https://typing.python.org/en/latest/spec/typeddict.html#subtyping-with-dict
|
||||
static_assert(is_disjoint_from(TD, dict[str, int])) # error: [static-assert-error]
|
||||
static_assert(is_disjoint_from(TD, dict[str, str])) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
[subtyping section]: https://typing.python.org/en/latest/spec/typeddict.html#subtyping-between-typeddict-types
|
||||
[`typeddict`]: https://typing.python.org/en/latest/spec/typeddict.html
|
||||
|
||||
@@ -3307,11 +3307,6 @@ impl<'db> Type<'db> {
|
||||
})
|
||||
}
|
||||
|
||||
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
|
||||
// TODO: Implement disjointness for TypedDict
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// `type[T]` is disjoint from a callable or protocol instance if its upper bound or constraints are.
|
||||
(Type::SubclassOf(subclass_of), Type::Callable(_) | Type::ProtocolInstance(_))
|
||||
| (Type::Callable(_) | Type::ProtocolInstance(_), Type::SubclassOf(subclass_of))
|
||||
@@ -4038,6 +4033,34 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Type::GenericAlias(_), _) | (_, Type::GenericAlias(_)) => ConstraintSet::from(true),
|
||||
|
||||
(Type::TypedDict(self_typeddict), Type::TypedDict(other_typeddict)) => {
|
||||
disjointness_visitor.visit((self, other), || {
|
||||
self_typeddict.is_disjoint_from_impl(
|
||||
db,
|
||||
other_typeddict,
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// For any type `T`, if `dict[str, Any]` is not assignable to `T`, then all `TypedDict`
|
||||
// types will always be disjoint from `T`. This doesn't cover all cases -- in fact
|
||||
// `dict` *itself* is almost always disjoint from `TypedDict` -- but it's a good
|
||||
// approximation, and some false negatives are acceptable.
|
||||
(Type::TypedDict(_), other) | (other, Type::TypedDict(_)) => KnownClass::Dict
|
||||
.to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::any()])
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::Assignability,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.negate(db),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ impl<'db> UnionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
for ty in replace_with {
|
||||
self.add_in_place_impl(ty, seen_aliases);
|
||||
self.add_in_place_impl(ty, seen_aliases, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,10 +312,15 @@ impl<'db> UnionBuilder<'db> {
|
||||
|
||||
/// Adds a type to this union.
|
||||
pub(crate) fn add_in_place(&mut self, ty: Type<'db>) {
|
||||
self.add_in_place_impl(ty, &mut vec![]);
|
||||
self.add_in_place_impl(ty, &mut vec![], None);
|
||||
}
|
||||
|
||||
pub(crate) fn add_in_place_impl(&mut self, ty: Type<'db>, seen_aliases: &mut Vec<Type<'db>>) {
|
||||
pub(crate) fn add_in_place_impl(
|
||||
&mut self,
|
||||
ty: Type<'db>,
|
||||
seen_aliases: &mut Vec<Type<'db>>,
|
||||
skip_checks_after: Option<usize>,
|
||||
) {
|
||||
let cycle_recovery = self.cycle_recovery;
|
||||
let should_widen = |literals, recursively_defined: RecursivelyDefined| {
|
||||
if recursively_defined.is_yes() && cycle_recovery {
|
||||
@@ -329,8 +334,22 @@ impl<'db> UnionBuilder<'db> {
|
||||
Type::Union(union) => {
|
||||
let new_elements = union.elements(self.db);
|
||||
self.elements.reserve(new_elements.len());
|
||||
// Capture the current element count to avoid comparing union elements against each other.
|
||||
// The union has already been simplified, so its elements don't need redundancy checks
|
||||
// between themselves, only against pre-existing elements. However, we only apply this
|
||||
// optimization when not in cycle recovery AND both the builder and union have
|
||||
// recursively_defined=No (indicating neither is involved in recursive type definitions
|
||||
// where simplification might be incomplete).
|
||||
let batch_start = if !self.cycle_recovery
|
||||
&& self.recursively_defined == RecursivelyDefined::No
|
||||
&& union.recursively_defined(self.db) == RecursivelyDefined::No
|
||||
{
|
||||
Some(self.elements.len())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for element in new_elements {
|
||||
self.add_in_place_impl(*element, seen_aliases);
|
||||
self.add_in_place_impl(*element, seen_aliases, batch_start);
|
||||
}
|
||||
self.recursively_defined = self
|
||||
.recursively_defined
|
||||
@@ -355,7 +374,9 @@ impl<'db> UnionBuilder<'db> {
|
||||
// leave out the recursive alias. TODO surface this error.
|
||||
} else {
|
||||
seen_aliases.push(ty);
|
||||
self.add_in_place_impl(alias.value_type(self.db), seen_aliases);
|
||||
// Don't pass through skip_checks_after when unpacking aliases, since the alias
|
||||
// value is not part of the original union batch.
|
||||
self.add_in_place_impl(alias.value_type(self.db), seen_aliases, None);
|
||||
}
|
||||
}
|
||||
// If adding a string literal, look for an existing `UnionElement::StringLiterals` to
|
||||
@@ -365,26 +386,33 @@ impl<'db> UnionBuilder<'db> {
|
||||
Type::StringLiteral(literal) => {
|
||||
let mut found = None;
|
||||
let mut to_remove = None;
|
||||
let ty_negated = ty.negate(self.db);
|
||||
let mut ty_negated = None;
|
||||
for (index, element) in self.elements.iter_mut().enumerate() {
|
||||
match element {
|
||||
UnionElement::StringLiterals(literals) => {
|
||||
if should_widen(literals.len(), self.recursively_defined) {
|
||||
let replace_with = KnownClass::Str.to_instance(self.db);
|
||||
self.add_in_place_impl(replace_with, seen_aliases);
|
||||
self.add_in_place_impl(replace_with, seen_aliases, None);
|
||||
return;
|
||||
}
|
||||
found = Some(literals);
|
||||
continue;
|
||||
}
|
||||
UnionElement::Type(existing) => {
|
||||
// Skip redundancy checks against elements from the same union batch.
|
||||
if skip_checks_after.is_some_and(|batch_start| index >= batch_start) {
|
||||
continue;
|
||||
}
|
||||
// e.g. `existing` could be `Literal[""] & Any`,
|
||||
// and `ty` could be `Literal[""]`
|
||||
if ty.is_subtype_of(self.db, *existing) {
|
||||
return;
|
||||
}
|
||||
if existing.is_subtype_of(self.db, ty) {
|
||||
to_remove = Some(index);
|
||||
continue;
|
||||
}
|
||||
if ty_negated.is_subtype_of(self.db, *existing) {
|
||||
let negated = ty_negated.get_or_insert_with(|| ty.negate(self.db));
|
||||
if negated.is_subtype_of(self.db, *existing) {
|
||||
// The type that includes both this new element, and its negation
|
||||
// (or a supertype of its negation), must be simply `object`.
|
||||
self.collapse_to_object();
|
||||
@@ -410,26 +438,32 @@ impl<'db> UnionBuilder<'db> {
|
||||
Type::BytesLiteral(literal) => {
|
||||
let mut found = None;
|
||||
let mut to_remove = None;
|
||||
let ty_negated = ty.negate(self.db);
|
||||
let mut ty_negated = None;
|
||||
for (index, element) in self.elements.iter_mut().enumerate() {
|
||||
match element {
|
||||
UnionElement::BytesLiterals(literals) => {
|
||||
if should_widen(literals.len(), self.recursively_defined) {
|
||||
let replace_with = KnownClass::Bytes.to_instance(self.db);
|
||||
self.add_in_place_impl(replace_with, seen_aliases);
|
||||
self.add_in_place_impl(replace_with, seen_aliases, None);
|
||||
return;
|
||||
}
|
||||
found = Some(literals);
|
||||
continue;
|
||||
}
|
||||
UnionElement::Type(existing) => {
|
||||
// Skip redundancy checks against elements from the same union batch.
|
||||
if skip_checks_after.is_some_and(|batch_start| index >= batch_start) {
|
||||
continue;
|
||||
}
|
||||
if ty.is_subtype_of(self.db, *existing) {
|
||||
return;
|
||||
}
|
||||
if existing.is_subtype_of(self.db, ty) {
|
||||
to_remove = Some(index);
|
||||
continue;
|
||||
}
|
||||
if ty_negated.is_subtype_of(self.db, *existing) {
|
||||
|
||||
let negated = ty_negated.get_or_insert_with(|| ty.negate(self.db));
|
||||
if negated.is_subtype_of(self.db, *existing) {
|
||||
// The type that includes both this new element, and its negation
|
||||
// (or a supertype of its negation), must be simply `object`.
|
||||
self.collapse_to_object();
|
||||
@@ -455,26 +489,32 @@ impl<'db> UnionBuilder<'db> {
|
||||
Type::IntLiteral(literal) => {
|
||||
let mut found = None;
|
||||
let mut to_remove = None;
|
||||
let ty_negated = ty.negate(self.db);
|
||||
let mut ty_negated = None;
|
||||
for (index, element) in self.elements.iter_mut().enumerate() {
|
||||
match element {
|
||||
UnionElement::IntLiterals(literals) => {
|
||||
if should_widen(literals.len(), self.recursively_defined) {
|
||||
let replace_with = KnownClass::Int.to_instance(self.db);
|
||||
self.add_in_place_impl(replace_with, seen_aliases);
|
||||
self.add_in_place_impl(replace_with, seen_aliases, None);
|
||||
return;
|
||||
}
|
||||
found = Some(literals);
|
||||
continue;
|
||||
}
|
||||
UnionElement::Type(existing) => {
|
||||
// Skip redundancy checks against elements from the same union batch.
|
||||
if skip_checks_after.is_some_and(|batch_start| index >= batch_start) {
|
||||
continue;
|
||||
}
|
||||
if ty.is_subtype_of(self.db, *existing) {
|
||||
return;
|
||||
}
|
||||
if existing.is_subtype_of(self.db, ty) {
|
||||
to_remove = Some(index);
|
||||
continue;
|
||||
}
|
||||
if ty_negated.is_subtype_of(self.db, *existing) {
|
||||
|
||||
let negated = ty_negated.get_or_insert_with(|| ty.negate(self.db));
|
||||
if negated.is_subtype_of(self.db, *existing) {
|
||||
// The type that includes both this new element, and its negation
|
||||
// (or a supertype of its negation), must be simply `object`.
|
||||
self.collapse_to_object();
|
||||
@@ -514,9 +554,12 @@ impl<'db> UnionBuilder<'db> {
|
||||
.all(|name| enum_members_in_union.contains(name));
|
||||
|
||||
if all_members_are_in_union {
|
||||
// Don't pass through skip_checks_after when converting to enum class instance,
|
||||
// since this is a transformation and not part of the original union batch.
|
||||
self.add_in_place_impl(
|
||||
enum_member_to_add.enum_class_instance(self.db),
|
||||
seen_aliases,
|
||||
None,
|
||||
);
|
||||
} else if !self
|
||||
.elements
|
||||
@@ -524,7 +567,11 @@ impl<'db> UnionBuilder<'db> {
|
||||
.filter_map(UnionElement::to_type_element)
|
||||
.any(|ty| Type::EnumLiteral(enum_member_to_add).is_subtype_of(self.db, ty))
|
||||
{
|
||||
self.push_type(Type::EnumLiteral(enum_member_to_add), seen_aliases);
|
||||
self.push_type(
|
||||
Type::EnumLiteral(enum_member_to_add),
|
||||
seen_aliases,
|
||||
skip_checks_after,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Adding `object` to a union results in `object`.
|
||||
@@ -532,12 +579,17 @@ impl<'db> UnionBuilder<'db> {
|
||||
self.collapse_to_object();
|
||||
}
|
||||
_ => {
|
||||
self.push_type(ty, seen_aliases);
|
||||
self.push_type(ty, seen_aliases, skip_checks_after);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_type(&mut self, ty: Type<'db>, seen_aliases: &mut Vec<Type<'db>>) {
|
||||
fn push_type(
|
||||
&mut self,
|
||||
ty: Type<'db>,
|
||||
seen_aliases: &mut Vec<Type<'db>>,
|
||||
skip_checks_after: Option<usize>,
|
||||
) {
|
||||
let bool_pair = if let Type::BooleanLiteral(b) = ty {
|
||||
Some(Type::BooleanLiteral(!b))
|
||||
} else {
|
||||
@@ -549,18 +601,14 @@ impl<'db> UnionBuilder<'db> {
|
||||
// unpacking them.
|
||||
let should_simplify_full = !matches!(ty, Type::TypeAlias(_)) && !self.cycle_recovery;
|
||||
|
||||
let mut ty_negated: Option<Type> = None;
|
||||
let mut to_remove = SmallVec::<[usize; 2]>::new();
|
||||
let ty_negated = if should_simplify_full {
|
||||
ty.negate(self.db)
|
||||
} else {
|
||||
Type::Never // won't be used
|
||||
};
|
||||
|
||||
for (index, element) in self.elements.iter_mut().enumerate() {
|
||||
for (i, element) in self.elements.iter_mut().enumerate() {
|
||||
let element_type = match element.try_reduce(self.db, ty) {
|
||||
ReduceResult::KeepIf(keep) => {
|
||||
if !keep {
|
||||
to_remove.push(index);
|
||||
to_remove.push(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -579,27 +627,39 @@ impl<'db> UnionBuilder<'db> {
|
||||
}
|
||||
|
||||
if Some(element_type) == bool_pair {
|
||||
self.add_in_place_impl(KnownClass::Bool.to_instance(self.db), seen_aliases);
|
||||
self.add_in_place_impl(KnownClass::Bool.to_instance(self.db), seen_aliases, None);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip redundancy checks when comparing elements from the same union batch.
|
||||
// The union has already been simplified, so its elements don't need to be checked
|
||||
// against each other.
|
||||
let skip_redundancy_checks =
|
||||
skip_checks_after.is_some_and(|batch_start| i >= batch_start);
|
||||
|
||||
// Comparing `TypedDict`s for redundancy requires iterating over their fields, which is
|
||||
// problematic if some of those fields point to recursive `Union`s. To avoid cycles,
|
||||
// compare `TypedDict`s by name/identity instead of using the `has_relation_to`
|
||||
// machinery.
|
||||
if let (Type::TypedDict(element_td), Type::TypedDict(ty_td)) = (element_type, ty) {
|
||||
if element_td == ty_td {
|
||||
return;
|
||||
}
|
||||
if element_type.is_typed_dict() && ty.is_typed_dict() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if should_simplify_full && !matches!(element_type, Type::TypeAlias(_)) {
|
||||
if should_simplify_full
|
||||
&& !skip_redundancy_checks
|
||||
&& !matches!(element_type, Type::TypeAlias(_))
|
||||
{
|
||||
if ty.is_redundant_with(self.db, element_type) {
|
||||
return;
|
||||
} else if element_type.is_redundant_with(self.db, ty) {
|
||||
to_remove.push(index);
|
||||
} else if ty_negated.is_subtype_of(self.db, element_type) {
|
||||
}
|
||||
|
||||
if element_type.is_redundant_with(self.db, ty) {
|
||||
to_remove.push(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
let negated = ty_negated.get_or_insert_with(|| ty.negate(self.db));
|
||||
if negated.is_subtype_of(self.db, element_type) {
|
||||
// We add `ty` to the union. We just checked that `~ty` is a subtype of an
|
||||
// existing `element`. This also means that `~ty | ty` is a subtype of
|
||||
// `element | ty`, because both elements in the first union are subtypes of
|
||||
@@ -614,10 +674,12 @@ impl<'db> UnionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((&first, rest)) = to_remove.split_first() {
|
||||
|
||||
let mut to_remove = to_remove.into_iter();
|
||||
if let Some(first) = to_remove.next() {
|
||||
self.elements[first] = UnionElement::Type(ty);
|
||||
// We iterate in descending order to keep remaining indices valid after `swap_remove`.
|
||||
for &index in rest.iter().rev() {
|
||||
for index in to_remove.rev() {
|
||||
self.elements.swap_remove(index);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -20,7 +20,9 @@ use crate::types::bound_super::BoundSuperError;
|
||||
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||
use crate::types::context::InferContext;
|
||||
use crate::types::diagnostic::{INVALID_TYPE_ALIAS_TYPE, SUPER_CALL_IN_NAMED_TUPLE_METHOD};
|
||||
use crate::types::enums::enum_metadata;
|
||||
use crate::types::enums::{
|
||||
enum_metadata, is_enum_class_by_inheritance, try_unwrap_nonmember_value,
|
||||
};
|
||||
use crate::types::function::{
|
||||
DataclassTransformerFlags, DataclassTransformerParams, KnownFunction,
|
||||
};
|
||||
@@ -2355,6 +2357,18 @@ impl<'db> ClassLiteral<'db> {
|
||||
// The symbol was not found in the class scope. It might still be implicitly defined in `@classmethod`s.
|
||||
return Self::implicit_attribute(db, body_scope, name, MethodDecorator::ClassMethod);
|
||||
}
|
||||
|
||||
// For enum classes, `nonmember(value)` creates a non-member attribute.
|
||||
// At runtime, the enum metaclass unwraps the value, so accessing the attribute
|
||||
// returns the inner value, not the `nonmember` wrapper.
|
||||
if let Some(ty) = member.inner.place.ignore_possibly_undefined() {
|
||||
if let Some(value_ty) = try_unwrap_nonmember_value(db, ty) {
|
||||
if is_enum_class_by_inheritance(db, self) {
|
||||
return Member::definitely_declared(value_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
member
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ use crate::types::{
|
||||
BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeVarBoundOrConstraints,
|
||||
UnionType, walk_bound_type_var_type,
|
||||
};
|
||||
use crate::{Db, FxOrderMap};
|
||||
use crate::{Db, FxOrderMap, FxOrderSet};
|
||||
|
||||
/// An extension trait for building constraint sets from [`Option`] values.
|
||||
pub(crate) trait OptionConstraintsExtension<T> {
|
||||
@@ -2222,10 +2222,21 @@ impl<'db> InteriorNode<'db> {
|
||||
constraints = %Node::Interior(self).display(db),
|
||||
"create sequent map",
|
||||
);
|
||||
let mut map = SequentMap::default();
|
||||
Node::Interior(self).for_each_constraint(db, &mut |constraint, _| {
|
||||
map.add(db, constraint);
|
||||
|
||||
// Sort the constraints in this BDD by their `source_order`s before adding them to the
|
||||
// sequent map. This ensures that constraints appear in the sequent map in a stable order.
|
||||
// The constraints mentioned in a BDD should all have distinct `source_order`s, so an
|
||||
// unstable sort is fine.
|
||||
let mut constraints = Vec::new();
|
||||
Node::Interior(self).for_each_constraint(db, &mut |constraint, source_order| {
|
||||
constraints.push((constraint, source_order));
|
||||
});
|
||||
constraints.sort_unstable_by_key(|(_, source_order)| *source_order);
|
||||
|
||||
let mut map = SequentMap::default();
|
||||
for (constraint, _) in constraints {
|
||||
map.add(db, constraint);
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
@@ -2781,10 +2792,10 @@ struct SequentMap<'db> {
|
||||
/// Sequents of the form `C₁ ∧ C₂ → D`
|
||||
pair_implications: FxHashMap<
|
||||
(ConstrainedTypeVar<'db>, ConstrainedTypeVar<'db>),
|
||||
FxHashSet<ConstrainedTypeVar<'db>>,
|
||||
FxOrderSet<ConstrainedTypeVar<'db>>,
|
||||
>,
|
||||
/// Sequents of the form `C → D`
|
||||
single_implications: FxHashMap<ConstrainedTypeVar<'db>, FxHashSet<ConstrainedTypeVar<'db>>>,
|
||||
single_implications: FxHashMap<ConstrainedTypeVar<'db>, FxOrderSet<ConstrainedTypeVar<'db>>>,
|
||||
/// Constraints that we have already processed
|
||||
processed: FxHashSet<ConstrainedTypeVar<'db>>,
|
||||
/// Constraints that enqueued to be processed
|
||||
|
||||
@@ -1594,7 +1594,7 @@ declare_lint! {
|
||||
pub(crate) static POSSIBLY_MISSING_IMPORT = {
|
||||
summary: "detects possibly missing imports",
|
||||
status: LintStatus::stable("0.0.1-alpha.22"),
|
||||
default_level: Level::Warn,
|
||||
default_level: Level::Ignore,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
return None;
|
||||
}
|
||||
|
||||
if !Type::ClassLiteral(class).is_subtype_of(db, KnownClass::Enum.to_subclass_of(db))
|
||||
&& !class
|
||||
.metaclass(db)
|
||||
.is_subtype_of(db, KnownClass::EnumType.to_subclass_of(db))
|
||||
{
|
||||
if !is_enum_class_by_inheritance(db, class) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -294,3 +290,63 @@ pub(crate) fn is_enum_class<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a class is an enum class by inheritance (either a subtype of `Enum`
|
||||
/// or has a metaclass that is a subtype of `EnumType`).
|
||||
///
|
||||
/// This is a lighter-weight check than `enum_metadata`, which additionally
|
||||
/// verifies that the class has members.
|
||||
pub(crate) fn is_enum_class_by_inheritance<'db>(db: &'db dyn Db, class: ClassLiteral<'db>) -> bool {
|
||||
Type::ClassLiteral(class).is_subtype_of(db, KnownClass::Enum.to_subclass_of(db))
|
||||
|| class
|
||||
.metaclass(db)
|
||||
.is_subtype_of(db, KnownClass::EnumType.to_subclass_of(db))
|
||||
}
|
||||
|
||||
/// Extracts the inner value type from an `enum.nonmember()` wrapper.
|
||||
///
|
||||
/// At runtime, the enum metaclass unwraps `nonmember(value)`, so accessing the attribute
|
||||
/// returns the inner value, not the `nonmember` wrapper.
|
||||
///
|
||||
/// Returns `Some(value_type)` if the type is a `nonmember[T]`, otherwise `None`.
|
||||
pub(crate) fn try_unwrap_nonmember_value<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
|
||||
match ty {
|
||||
Type::NominalInstance(instance) if instance.has_known_class(db, KnownClass::Nonmember) => {
|
||||
Some(
|
||||
ty.member(db, "value")
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.unwrap_or(Type::unknown()),
|
||||
)
|
||||
}
|
||||
Type::Union(union) => {
|
||||
// TODO: This is a hack. The proper fix is to avoid unioning Unknown from
|
||||
// declarations into Place when we have concrete bindings.
|
||||
//
|
||||
// For now, we filter out Unknown and expect exactly one nonmember type
|
||||
// to remain. If there are other non-Unknown types mixed in, we bail out.
|
||||
let mut non_unknown = union.elements(db).iter().filter(|elem| !elem.is_unknown());
|
||||
|
||||
let first = non_unknown.next()?;
|
||||
|
||||
// Ensure there's exactly one non-Unknown element.
|
||||
if non_unknown.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Type::NominalInstance(instance) = first {
|
||||
if instance.has_known_class(db, KnownClass::Nonmember) {
|
||||
return Some(
|
||||
first
|
||||
.member(db, "value")
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.unwrap_or(Type::unknown()),
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1590,14 +1590,19 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
upper: FxOrderSet<Type<'db>>,
|
||||
}
|
||||
|
||||
// Sort the constraints in each path by their `source_order`s, to ensure that we construct
|
||||
// any unions or intersections in our type mappings in a stable order. Constraints might
|
||||
// come out of `PathAssignment`s with identical `source_order`s, but if they do, those
|
||||
// "tied" constraints will still be ordered in a stable way. So we need a stable sort to
|
||||
// retain that stable per-tie ordering.
|
||||
let constraints = constraints.limit_to_valid_specializations(self.db);
|
||||
let mut sorted_paths = Vec::new();
|
||||
constraints.for_each_path(self.db, |path| {
|
||||
let mut path: Vec<_> = path.positive_constraints().collect();
|
||||
path.sort_unstable_by_key(|(_, source_order)| *source_order);
|
||||
path.sort_by_key(|(_, source_order)| *source_order);
|
||||
sorted_paths.push(path);
|
||||
});
|
||||
sorted_paths.sort_unstable_by(|path1, path2| {
|
||||
sorted_paths.sort_by(|path1, path2| {
|
||||
let source_orders1 = path1.iter().map(|(_, source_order)| *source_order);
|
||||
let source_orders2 = path2.iter().map(|(_, source_order)| *source_order);
|
||||
source_orders1.cmp(source_orders2)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
@@ -341,6 +342,148 @@ impl<'db> TypedDictType<'db> {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Two `TypedDict`s `A` and `B` are disjoint if it's impossible to come up with a third
|
||||
/// `TypedDict` `C` that's fully-static and assignable to both of them.
|
||||
///
|
||||
/// `TypedDict` assignability is determined field-by-field, so we determine disjointness
|
||||
/// similarly. For any field that's only in `A`, it's always possible for our hypothetical `C`
|
||||
/// to copy/paste that field without losing assignability to `B` (and vice versa), so we only
|
||||
/// need to consider fields that are present in both `A` and `B`.
|
||||
///
|
||||
/// There are three properties of each field to consider: the declared type, whether it's
|
||||
/// mutable ("mut" vs "imm" below), and whether it's required ("req" vs "opt" below). Here's a
|
||||
/// table summary of the restrictions on the declared type of a source field (for us that means
|
||||
/// in `C`, which we want to be assignable to both `A` and `B`) given a destination field (for
|
||||
/// us that means in either `A` or `B`). For completeness we'll also include the possibility
|
||||
/// that the source field is missing entirely, though we'll soon see that we can ignore that
|
||||
/// case. This table is essentially what `has_relation_to_impl` implements above. Here
|
||||
/// "equivalent" means the source and destination types must be equivalent/compatible,
|
||||
/// "assignable" means the source must be assignable to the destination, and "-" means the
|
||||
/// assignment is never allowed:
|
||||
///
|
||||
/// | dest ↓ source → | mut + req | mut + opt | imm + req | imm + opt | \[missing] |
|
||||
/// |------------------|------------|------------|------------|------------|---------------|
|
||||
/// | mut + req | equivalent | - | - | - | - |
|
||||
/// | mut + opt | - | equivalent | - | - | - |
|
||||
/// | imm + req | assignable | - | assignable | - | - |
|
||||
/// | imm + opt | assignable | assignable | assignable | assignable | \[dest is obj]|
|
||||
///
|
||||
/// We can cut that table down substantially by noticing two things:
|
||||
///
|
||||
/// - We don't need to consider the cases where the source field (in `C`) is `ReadOnly`/"imm",
|
||||
/// because the mutable version of the same field is always "strictly more assignable". In
|
||||
/// other words, nothing in the `TypedDict` assignability rules ever requires a source field
|
||||
/// to be immutable.
|
||||
/// - We don't need to consider the special case where the source field is missing, because
|
||||
/// that's only allowed when the destination is `ReadOnly[NotRequired[object]]`, which is
|
||||
/// compatible with *any* choice of source field.
|
||||
///
|
||||
/// The cases we actually need to reason about are this smaller table:
|
||||
///
|
||||
/// | dest ↓ source → | mut + req | mut + opt |
|
||||
/// |------------------|------------|------------|
|
||||
/// | mut + req | equivalent | - |
|
||||
/// | mut + opt | - | equivalent |
|
||||
/// | imm + req | assignable | - |
|
||||
/// | imm + opt | assignable | assignable |
|
||||
///
|
||||
/// So, given a field name that's in both `A` and `B`, here are the conditions where it's
|
||||
/// *impossible* to choose a source field for `C` that's compatible with both destinations,
|
||||
/// which tells us that `A` and `B` are disjoint:
|
||||
///
|
||||
/// 1. If one side is "mut+opt" (which forces the field in `C` to be "opt") and the other side
|
||||
/// is "req" (which forces the field in `C` to be "req").
|
||||
/// 2. If both sides are mutable, and their types are not equivalent/compatible. (Because the
|
||||
/// type in `C` must be compatible with both of them.)
|
||||
/// 3. If one sides is mutable, and its type is not assignable to the immutable side's type.
|
||||
/// (Because the type in `C` must be compatible with the mutable side.)
|
||||
/// 4. If both sides are immutable, and their types are disjoint. (Because the type in `C` must
|
||||
/// be assignable to both.)
|
||||
///
|
||||
/// TODO: Adding support for `closed` and `extra_items` will complicate this.
|
||||
pub(crate) fn is_disjoint_from_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: TypedDictType<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
let fields_in_common = btreemap_values_with_same_key(self.items(db), other.items(db));
|
||||
fields_in_common.when_any(db, |(self_field, other_field)| {
|
||||
// Condition 1 above.
|
||||
if self_field.is_required() || other_field.is_required() {
|
||||
if (!self_field.is_required() && !self_field.is_read_only())
|
||||
|| (!other_field.is_required() && !other_field.is_read_only())
|
||||
{
|
||||
// One side demands a `Required` source field, while the other side demands a
|
||||
// `NotRequired` one. They must be disjoint.
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
}
|
||||
if !self_field.is_read_only() && !other_field.is_read_only() {
|
||||
// Condition 2 above. This field is mutable on both sides, so the so the types must
|
||||
// be compatible, i.e. mutually assignable.
|
||||
self_field
|
||||
.declared_ty
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_field.declared_ty,
|
||||
inferable,
|
||||
TypeRelation::Assignability,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.and(db, || {
|
||||
other_field.declared_ty.has_relation_to_impl(
|
||||
db,
|
||||
self_field.declared_ty,
|
||||
inferable,
|
||||
TypeRelation::Assignability,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
.negate(db)
|
||||
} else if !self_field.is_read_only() {
|
||||
// Half of condition 3 above.
|
||||
self_field
|
||||
.declared_ty
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_field.declared_ty,
|
||||
inferable,
|
||||
TypeRelation::Assignability,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.negate(db)
|
||||
} else if !other_field.is_read_only() {
|
||||
// The other half of condition 3 above.
|
||||
other_field
|
||||
.declared_ty
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
self_field.declared_ty,
|
||||
inferable,
|
||||
TypeRelation::Assignability,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.negate(db)
|
||||
} else {
|
||||
// Condition 4 above.
|
||||
self_field.declared_ty.is_disjoint_from_impl(
|
||||
db,
|
||||
other_field.declared_ty,
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn walk_typed_dict_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
@@ -905,3 +1048,71 @@ bitflags! {
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for TypedDictFieldFlags {}
|
||||
|
||||
/// Yield all the key/val pairs where the same key is present in both `BTreeMap`s. Take advantage
|
||||
/// of the fact that keys are sorted to walk through each map once without doing any lookups. It
|
||||
/// would be nice if `BTreeMap` had something like `BTreeSet::intersection` that did this for us,
|
||||
/// but as far as I know we have to do it ourselves. Life is hard.
|
||||
fn btreemap_values_with_same_key<'a, K, V1, V2>(
|
||||
left: &'a BTreeMap<K, V1>,
|
||||
right: &'a BTreeMap<K, V2>,
|
||||
) -> impl Iterator<Item = (&'a V1, &'a V2)>
|
||||
where
|
||||
K: Ord,
|
||||
{
|
||||
let mut left_items = left.iter().peekable();
|
||||
let mut right_items = right.iter().peekable();
|
||||
std::iter::from_fn(move || {
|
||||
while let (Some((left_key, left_val)), Some((right_key, right_val))) =
|
||||
(left_items.peek().copied(), right_items.peek().copied())
|
||||
{
|
||||
match left_key.cmp(right_key) {
|
||||
Ordering::Equal => {
|
||||
// Matching keys. Yield this pair of values and advance both iterators.
|
||||
left_items.next();
|
||||
right_items.next();
|
||||
return Some((left_val, right_val));
|
||||
}
|
||||
Ordering::Less => {
|
||||
// `left_items` is behind `right_items` in key order. Advance `left_items`.
|
||||
left_items.next();
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// The opposite.
|
||||
right_items.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
// We've exhausted one or both of the maps, so there can be no more matching keys.
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_btreemap_overlapping_items() {
|
||||
// A case with partial overlap and gaps.
|
||||
let left = BTreeMap::from_iter([("a", 1), ("b", 2), ("c", 3), ("d", 4), ("e", 5)]);
|
||||
let right = BTreeMap::from_iter([("b", 2.0), ("d", 4.0), ("f", 6.0)]);
|
||||
assert_eq!(
|
||||
btreemap_values_with_same_key(&left, &right).collect::<Vec<_>>(),
|
||||
vec![(&2, &2.0), (&4, &4.0)],
|
||||
);
|
||||
assert_eq!(
|
||||
btreemap_values_with_same_key(&right, &left).collect::<Vec<_>>(),
|
||||
vec![(&2.0, &2), (&4.0, &4)],
|
||||
);
|
||||
|
||||
// A case where one side is empty.
|
||||
let left = BTreeMap::<i32, i32>::new();
|
||||
let right = BTreeMap::<i32, i32>::from_iter([(1, 1), (2, 2)]);
|
||||
assert!(
|
||||
btreemap_values_with_same_key(&left, &right)
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
btreemap_values_with_same_key(&right, &left)
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ Settings: Settings {
|
||||
"positional-only-parameter-as-kwarg": Error (Default),
|
||||
"possibly-missing-attribute": Warning (Default),
|
||||
"possibly-missing-implicit-call": Warning (Default),
|
||||
"possibly-missing-import": Warning (Default),
|
||||
"raw-string-type-annotation": Error (Default),
|
||||
"redundant-cast": Warning (Default),
|
||||
"static-assert-error": Error (Default),
|
||||
|
||||
@@ -384,6 +384,9 @@ foo()
|
||||
|
||||
It is strongly suggested to use explicit range suppressions, in order to prevent
|
||||
accidental suppressions of violations, especially at global module scope.
|
||||
For this reason, a `RUF104` diagnostic will also be produced for any implicit range.
|
||||
If implicit range suppressions are desired, the `RUF104` rule can be disabled,
|
||||
or an inline `noqa` suppression can be added to the end of the "disable" comment.
|
||||
|
||||
Range suppressions cannot be used to enable or select rules that aren't already
|
||||
selected by the project configuration or runtime flags. An "enable" comment can only
|
||||
|
||||
2
ruff.schema.json
generated
2
ruff.schema.json
generated
@@ -4051,6 +4051,8 @@
|
||||
"RUF100",
|
||||
"RUF101",
|
||||
"RUF102",
|
||||
"RUF103",
|
||||
"RUF104",
|
||||
"RUF2",
|
||||
"RUF20",
|
||||
"RUF200",
|
||||
|
||||
2
ty.schema.json
generated
2
ty.schema.json
generated
@@ -926,7 +926,7 @@
|
||||
"possibly-missing-import": {
|
||||
"title": "detects possibly missing imports",
|
||||
"description": "## What it does\nChecks for imports of symbols that may be missing.\n\n## Why is this bad?\nImporting a missing module or name will raise a `ModuleNotFoundError`\nor `ImportError` at runtime.\n\n## Examples\n```python\n# module.py\nimport datetime\n\nif datetime.date.today().weekday() != 6:\n a = 1\n\n# main.py\nfrom module import a # ImportError: cannot import name 'a' from 'module'\n```",
|
||||
"default": "warn",
|
||||
"default": "ignore",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Level"
|
||||
|
||||
Reference in New Issue
Block a user