mirror of https://github.com/astral-sh/ruff
[`flake8-future-annotations`] Implement `FA102` (#4702)
This commit is contained in:
parent
2695d0561a
commit
0106bce02f
|
|
@ -2184,19 +2184,19 @@ where
|
||||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||||
// Ex) Optional[...], Union[...]
|
// Ex) Optional[...], Union[...]
|
||||||
if self.any_enabled(&[
|
if self.any_enabled(&[
|
||||||
Rule::MissingFutureAnnotationsImport,
|
Rule::MissingFutureAnnotationsImportOldStyle,
|
||||||
Rule::NonPEP604Annotation,
|
Rule::NonPEP604Annotation,
|
||||||
]) {
|
]) {
|
||||||
if let Some(operator) =
|
if let Some(operator) =
|
||||||
analyze::typing::to_pep604_operator(value, slice, &self.semantic_model)
|
analyze::typing::to_pep604_operator(value, slice, &self.semantic_model)
|
||||||
{
|
{
|
||||||
if self.enabled(Rule::MissingFutureAnnotationsImport) {
|
if self.enabled(Rule::MissingFutureAnnotationsImportOldStyle) {
|
||||||
if self.settings.target_version < PythonVersion::Py310
|
if self.settings.target_version < PythonVersion::Py310
|
||||||
&& self.settings.target_version >= PythonVersion::Py37
|
&& self.settings.target_version >= PythonVersion::Py37
|
||||||
&& !self.semantic_model.future_annotations()
|
&& !self.semantic_model.future_annotations()
|
||||||
&& self.semantic_model.in_annotation()
|
&& self.semantic_model.in_annotation()
|
||||||
{
|
{
|
||||||
flake8_future_annotations::rules::missing_future_annotations(
|
flake8_future_annotations::rules::missing_future_annotations_old_style(
|
||||||
self, value,
|
self, value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2215,6 +2215,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ex) list[...]
|
||||||
|
if self.enabled(Rule::MissingFutureAnnotationsImportNewStyle) {
|
||||||
|
if self.settings.target_version < PythonVersion::Py39
|
||||||
|
&& !self.semantic_model.future_annotations()
|
||||||
|
&& self.semantic_model.in_annotation()
|
||||||
|
&& analyze::typing::is_pep585_generic(value, &self.semantic_model)
|
||||||
|
{
|
||||||
|
flake8_future_annotations::rules::missing_future_annotations_new_style(
|
||||||
|
self,
|
||||||
|
expr,
|
||||||
|
flake8_future_annotations::rules::Reason::PEP585,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.semantic_model.match_typing_expr(value, "Literal") {
|
if self.semantic_model.match_typing_expr(value, "Literal") {
|
||||||
self.semantic_model.flags |= SemanticModelFlags::LITERAL;
|
self.semantic_model.flags |= SemanticModelFlags::LITERAL;
|
||||||
}
|
}
|
||||||
|
|
@ -2271,19 +2286,19 @@ where
|
||||||
|
|
||||||
// Ex) List[...]
|
// Ex) List[...]
|
||||||
if self.any_enabled(&[
|
if self.any_enabled(&[
|
||||||
Rule::MissingFutureAnnotationsImport,
|
Rule::MissingFutureAnnotationsImportOldStyle,
|
||||||
Rule::NonPEP585Annotation,
|
Rule::NonPEP585Annotation,
|
||||||
]) {
|
]) {
|
||||||
if let Some(replacement) =
|
if let Some(replacement) =
|
||||||
analyze::typing::to_pep585_generic(expr, &self.semantic_model)
|
analyze::typing::to_pep585_generic(expr, &self.semantic_model)
|
||||||
{
|
{
|
||||||
if self.enabled(Rule::MissingFutureAnnotationsImport) {
|
if self.enabled(Rule::MissingFutureAnnotationsImportOldStyle) {
|
||||||
if self.settings.target_version < PythonVersion::Py39
|
if self.settings.target_version < PythonVersion::Py39
|
||||||
&& self.settings.target_version >= PythonVersion::Py37
|
&& self.settings.target_version >= PythonVersion::Py37
|
||||||
&& !self.semantic_model.future_annotations()
|
&& !self.semantic_model.future_annotations()
|
||||||
&& self.semantic_model.in_annotation()
|
&& self.semantic_model.in_annotation()
|
||||||
{
|
{
|
||||||
flake8_future_annotations::rules::missing_future_annotations(
|
flake8_future_annotations::rules::missing_future_annotations_old_style(
|
||||||
self, expr,
|
self, expr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2349,19 +2364,19 @@ where
|
||||||
Expr::Attribute(ast::ExprAttribute { attr, value, .. }) => {
|
Expr::Attribute(ast::ExprAttribute { attr, value, .. }) => {
|
||||||
// Ex) typing.List[...]
|
// Ex) typing.List[...]
|
||||||
if self.any_enabled(&[
|
if self.any_enabled(&[
|
||||||
Rule::MissingFutureAnnotationsImport,
|
Rule::MissingFutureAnnotationsImportOldStyle,
|
||||||
Rule::NonPEP585Annotation,
|
Rule::NonPEP585Annotation,
|
||||||
]) {
|
]) {
|
||||||
if let Some(replacement) =
|
if let Some(replacement) =
|
||||||
analyze::typing::to_pep585_generic(expr, &self.semantic_model)
|
analyze::typing::to_pep585_generic(expr, &self.semantic_model)
|
||||||
{
|
{
|
||||||
if self.enabled(Rule::MissingFutureAnnotationsImport) {
|
if self.enabled(Rule::MissingFutureAnnotationsImportOldStyle) {
|
||||||
if self.settings.target_version < PythonVersion::Py39
|
if self.settings.target_version < PythonVersion::Py39
|
||||||
&& self.settings.target_version >= PythonVersion::Py37
|
&& self.settings.target_version >= PythonVersion::Py37
|
||||||
&& !self.semantic_model.future_annotations()
|
&& !self.semantic_model.future_annotations()
|
||||||
&& self.semantic_model.in_annotation()
|
&& self.semantic_model.in_annotation()
|
||||||
{
|
{
|
||||||
flake8_future_annotations::rules::missing_future_annotations(
|
flake8_future_annotations::rules::missing_future_annotations_old_style(
|
||||||
self, expr,
|
self, expr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -3198,6 +3213,20 @@ where
|
||||||
op: Operator::BitOr,
|
op: Operator::BitOr,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
|
// Ex) `str | None`
|
||||||
|
if self.enabled(Rule::MissingFutureAnnotationsImportNewStyle) {
|
||||||
|
if self.settings.target_version < PythonVersion::Py310
|
||||||
|
&& !self.semantic_model.future_annotations()
|
||||||
|
&& self.semantic_model.in_annotation()
|
||||||
|
{
|
||||||
|
flake8_future_annotations::rules::missing_future_annotations_new_style(
|
||||||
|
self,
|
||||||
|
expr,
|
||||||
|
flake8_future_annotations::rules::Reason::PEP604,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.is_stub {
|
if self.is_stub {
|
||||||
if self.enabled(Rule::DuplicateUnionMember)
|
if self.enabled(Rule::DuplicateUnionMember)
|
||||||
&& self.semantic_model.in_type_definition()
|
&& self.semantic_model.in_type_definition()
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8Annotations, "401") => (RuleGroup::Unspecified, Rule::AnyType),
|
(Flake8Annotations, "401") => (RuleGroup::Unspecified, Rule::AnyType),
|
||||||
|
|
||||||
// flake8-future-annotations
|
// flake8-future-annotations
|
||||||
(Flake8FutureAnnotations, "100") => (RuleGroup::Unspecified, Rule::MissingFutureAnnotationsImport),
|
(Flake8FutureAnnotations, "100") => (RuleGroup::Unspecified, Rule::MissingFutureAnnotationsImportOldStyle),
|
||||||
|
(Flake8FutureAnnotations, "102") => (RuleGroup::Unspecified, Rule::MissingFutureAnnotationsImportNewStyle),
|
||||||
|
|
||||||
// flake8-2020
|
// flake8-2020
|
||||||
(Flake82020, "101") => (RuleGroup::Unspecified, Rule::SysVersionSlice3),
|
(Flake82020, "101") => (RuleGroup::Unspecified, Rule::SysVersionSlice3),
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,8 @@ ruff_macros::register_rules!(
|
||||||
rules::flake8_annotations::rules::MissingReturnTypeClassMethod,
|
rules::flake8_annotations::rules::MissingReturnTypeClassMethod,
|
||||||
rules::flake8_annotations::rules::AnyType,
|
rules::flake8_annotations::rules::AnyType,
|
||||||
// flake8-future-annotations
|
// flake8-future-annotations
|
||||||
rules::flake8_future_annotations::rules::MissingFutureAnnotationsImport,
|
rules::flake8_future_annotations::rules::MissingFutureAnnotationsImportOldStyle,
|
||||||
|
rules::flake8_future_annotations::rules::MissingFutureAnnotationsImportNewStyle,
|
||||||
// flake8-2020
|
// flake8-2020
|
||||||
rules::flake8_2020::rules::SysVersionSlice3,
|
rules::flake8_2020::rules::SysVersionSlice3,
|
||||||
rules::flake8_2020::rules::SysVersion2,
|
rules::flake8_2020::rules::SysVersion2,
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,31 @@ mod tests {
|
||||||
#[test_case(Path::new("ok_non_simplifiable_types.py"))]
|
#[test_case(Path::new("ok_non_simplifiable_types.py"))]
|
||||||
#[test_case(Path::new("ok_uses_future.py"))]
|
#[test_case(Path::new("ok_uses_future.py"))]
|
||||||
#[test_case(Path::new("ok_variable_name.py"))]
|
#[test_case(Path::new("ok_variable_name.py"))]
|
||||||
fn rules(path: &Path) -> Result<()> {
|
fn fa100(path: &Path) -> Result<()> {
|
||||||
let snapshot = path.to_string_lossy().into_owned();
|
let snapshot = path.to_string_lossy().into_owned();
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
Path::new("flake8_future_annotations").join(path).as_path(),
|
Path::new("flake8_future_annotations").join(path).as_path(),
|
||||||
&settings::Settings {
|
&settings::Settings {
|
||||||
target_version: PythonVersion::Py37,
|
target_version: PythonVersion::Py37,
|
||||||
..settings::Settings::for_rule(Rule::MissingFutureAnnotationsImport)
|
..settings::Settings::for_rule(Rule::MissingFutureAnnotationsImportOldStyle)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(Path::new("no_future_import_uses_lowercase.py"))]
|
||||||
|
#[test_case(Path::new("no_future_import_uses_union.py"))]
|
||||||
|
#[test_case(Path::new("no_future_import_uses_union_inner.py"))]
|
||||||
|
#[test_case(Path::new("ok_no_types.py"))]
|
||||||
|
#[test_case(Path::new("ok_uses_future.py"))]
|
||||||
|
fn fa102(path: &Path) -> Result<()> {
|
||||||
|
let snapshot = format!("fa102_{}", path.to_string_lossy());
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("flake8_future_annotations").join(path).as_path(),
|
||||||
|
&settings::Settings {
|
||||||
|
target_version: PythonVersion::Py37,
|
||||||
|
..settings::Settings::for_rule(Rule::MissingFutureAnnotationsImportNewStyle)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(snapshot, diagnostics);
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
use rustpython_parser::ast::{Expr, Ranged};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for uses of PEP 585- and PEP 604-style type annotations in Python
|
||||||
|
/// modules that lack the required `from __future__ import annotations` import
|
||||||
|
/// for compatibility with older Python versions.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Using PEP 585 and PEP 604 style annotations without a `from __future__ import
|
||||||
|
/// annotations` import will cause runtime errors on Python versions prior to
|
||||||
|
/// 3.9 and 3.10, respectively.
|
||||||
|
///
|
||||||
|
/// By adding the `__future__` import, the interpreter will no longer interpret
|
||||||
|
/// annotations at evaluation time, making the code compatible with both past
|
||||||
|
/// and future Python versions.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// def func(obj: dict[str, int | None]) -> None:
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// from __future__ import annotations
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// def func(obj: dict[str, int | None]) -> None:
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
#[violation]
|
||||||
|
pub struct MissingFutureAnnotationsImportNewStyle {
|
||||||
|
reason: Reason,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) enum Reason {
|
||||||
|
/// The type annotation is written in PEP 585 style (e.g., `list[int]`).
|
||||||
|
PEP585,
|
||||||
|
/// The type annotation is written in PEP 604 style (e.g., `int | None`).
|
||||||
|
PEP604,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Reason {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Reason::PEP585 => fmt.write_str("PEP 585 collection"),
|
||||||
|
Reason::PEP604 => fmt.write_str("PEP 604 union"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for MissingFutureAnnotationsImportNewStyle {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let MissingFutureAnnotationsImportNewStyle { reason } = self;
|
||||||
|
format!("Missing `from __future__ import annotations`, but uses {reason}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FA102
|
||||||
|
pub(crate) fn missing_future_annotations_new_style(
|
||||||
|
checker: &mut Checker,
|
||||||
|
expr: &Expr,
|
||||||
|
reason: Reason,
|
||||||
|
) {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
MissingFutureAnnotationsImportNewStyle { reason },
|
||||||
|
expr.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
@ -26,9 +26,8 @@ use crate::checkers::ast::Checker;
|
||||||
/// from typing import List, Dict, Optional
|
/// from typing import List, Dict, Optional
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// def function(a_dict: Dict[str, Optional[int]]) -> None:
|
/// def func(obj: Dict[str, Optional[int]]) -> None:
|
||||||
/// a_list: List[str] = []
|
/// ...
|
||||||
/// a_list.append("hello")
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Use instead:
|
/// Use instead:
|
||||||
|
|
@ -38,9 +37,8 @@ use crate::checkers::ast::Checker;
|
||||||
/// from typing import List, Dict, Optional
|
/// from typing import List, Dict, Optional
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// def function(a_dict: Dict[str, Optional[int]]) -> None:
|
/// def func(obj: Dict[str, Optional[int]]) -> None:
|
||||||
/// a_list: List[str] = []
|
/// ...
|
||||||
/// a_list.append("hello")
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// After running the additional pyupgrade rules:
|
/// After running the additional pyupgrade rules:
|
||||||
|
|
@ -48,25 +46,24 @@ use crate::checkers::ast::Checker;
|
||||||
/// from __future__ import annotations
|
/// from __future__ import annotations
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// def function(a_dict: dict[str, int | None]) -> None:
|
/// def func(obj: dict[str, int | None]) -> None:
|
||||||
/// a_list: list[str] = []
|
/// ...
|
||||||
/// a_list.append("hello")
|
|
||||||
/// ```
|
/// ```
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct MissingFutureAnnotationsImport {
|
pub struct MissingFutureAnnotationsImportOldStyle {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for MissingFutureAnnotationsImport {
|
impl Violation for MissingFutureAnnotationsImportOldStyle {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let MissingFutureAnnotationsImport { name } = self;
|
let MissingFutureAnnotationsImportOldStyle { name } = self;
|
||||||
format!("Missing `from __future__ import annotations`, but uses `{name}`")
|
format!("Missing `from __future__ import annotations`, but uses `{name}`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FA100
|
/// FA100
|
||||||
pub(crate) fn missing_future_annotations(checker: &mut Checker, expr: &Expr) {
|
pub(crate) fn missing_future_annotations_old_style(checker: &mut Checker, expr: &Expr) {
|
||||||
let name = checker
|
let name = checker
|
||||||
.semantic_model()
|
.semantic_model()
|
||||||
.resolve_call_path(expr)
|
.resolve_call_path(expr)
|
||||||
|
|
@ -74,7 +71,7 @@ pub(crate) fn missing_future_annotations(checker: &mut Checker, expr: &Expr) {
|
||||||
|
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
MissingFutureAnnotationsImport { name },
|
MissingFutureAnnotationsImportOldStyle { name },
|
||||||
expr.range(),
|
expr.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
pub(crate) use missing_future_annotations::{
|
pub(crate) use missing_future_annotations_new_style::{
|
||||||
missing_future_annotations, MissingFutureAnnotationsImport,
|
missing_future_annotations_new_style, MissingFutureAnnotationsImportNewStyle, Reason,
|
||||||
|
};
|
||||||
|
pub(crate) use missing_future_annotations_old_style::{
|
||||||
|
missing_future_annotations_old_style, MissingFutureAnnotationsImportOldStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod missing_future_annotations;
|
mod missing_future_annotations_new_style;
|
||||||
|
mod missing_future_annotations_old_style;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs
|
||||||
|
---
|
||||||
|
no_future_import_uses_lowercase.py:2:13: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
|
||||||
|
|
|
||||||
|
2 | def main() -> None:
|
||||||
|
3 | a_list: list[str] = []
|
||||||
|
| ^^^^^^^^^ FA102
|
||||||
|
4 | a_list.append("hello")
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs
|
||||||
|
---
|
||||||
|
no_future_import_uses_union.py:2:13: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
|
||||||
|
|
|
||||||
|
2 | def main() -> None:
|
||||||
|
3 | a_list: list[str] | None = []
|
||||||
|
| ^^^^^^^^^^^^^^^^ FA102
|
||||||
|
4 | a_list.append("hello")
|
||||||
|
|
|
||||||
|
|
||||||
|
no_future_import_uses_union.py:2:13: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
|
||||||
|
|
|
||||||
|
2 | def main() -> None:
|
||||||
|
3 | a_list: list[str] | None = []
|
||||||
|
| ^^^^^^^^^ FA102
|
||||||
|
4 | a_list.append("hello")
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs
|
||||||
|
---
|
||||||
|
no_future_import_uses_union_inner.py:2:13: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
|
||||||
|
|
|
||||||
|
2 | def main() -> None:
|
||||||
|
3 | a_list: list[str | None] = []
|
||||||
|
| ^^^^^^^^^^^^^^^^ FA102
|
||||||
|
4 | a_list.append("hello")
|
||||||
|
|
|
||||||
|
|
||||||
|
no_future_import_uses_union_inner.py:2:18: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
|
||||||
|
|
|
||||||
|
2 | def main() -> None:
|
||||||
|
3 | a_list: list[str | None] = []
|
||||||
|
| ^^^^^^^^^^ FA102
|
||||||
|
4 | a_list.append("hello")
|
||||||
|
|
|
||||||
|
|
||||||
|
no_future_import_uses_union_inner.py:7:8: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
|
||||||
|
|
|
||||||
|
7 | def hello(y: dict[str | None, int]) -> None:
|
||||||
|
8 | z: tuple[str, str | None, str] = tuple(y)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FA102
|
||||||
|
9 | del z
|
||||||
|
|
|
||||||
|
|
||||||
|
no_future_import_uses_union_inner.py:7:19: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
|
||||||
|
|
|
||||||
|
7 | def hello(y: dict[str | None, int]) -> None:
|
||||||
|
8 | z: tuple[str, str | None, str] = tuple(y)
|
||||||
|
| ^^^^^^^^^^ FA102
|
||||||
|
9 | del z
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs
|
||||||
|
---
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs
|
||||||
|
---
|
||||||
|
|
||||||
|
|
@ -101,6 +101,21 @@ pub fn to_pep585_generic(expr: &Expr, model: &SemanticModel) -> Option<ModuleMem
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return whether a given expression uses a PEP 585 standard library generic.
|
||||||
|
pub fn is_pep585_generic(expr: &Expr, model: &SemanticModel) -> bool {
|
||||||
|
if let Some(call_path) = model.resolve_call_path(expr) {
|
||||||
|
let [module, name] = call_path.as_slice() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for (_, (to_module, to_member)) in PEP_585_GENERICS {
|
||||||
|
if module == to_module && name == to_member {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Pep604Operator {
|
pub enum Pep604Operator {
|
||||||
/// The union operator, e.g., `Union[str, int]`, expressible as `str | int` after PEP 604.
|
/// The union operator, e.g., `Union[str, int]`, expressible as `str | int` after PEP 604.
|
||||||
|
|
|
||||||
|
|
@ -1883,6 +1883,7 @@
|
||||||
"FA1",
|
"FA1",
|
||||||
"FA10",
|
"FA10",
|
||||||
"FA100",
|
"FA100",
|
||||||
|
"FA102",
|
||||||
"FBT",
|
"FBT",
|
||||||
"FBT0",
|
"FBT0",
|
||||||
"FBT00",
|
"FBT00",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue