mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 22:31:23 +00:00
Add Checker::import_from_typing
(#17340)
Summary -- This PR replaces uses of version-dependent imports from `typing` or `typing_extensions` with a centralized `Checker::import_from_typing` method. The idea here is to make the fix for #9761 (whatever it ends up being) applicable to all of the rules performing similar checks. Test Plan -- Existing tests for the affected rules.
This commit is contained in:
parent
1aad180aae
commit
8e11c53310
13 changed files with 87 additions and 140 deletions
|
@ -31,7 +31,7 @@ use ruff_python_parser::semantic_errors::{
|
|||
};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, IsolationLevel};
|
||||
use ruff_notebook::{CellOffsets, NotebookIndex};
|
||||
use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
|
@ -62,7 +62,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
|||
|
||||
use crate::checkers::ast::annotation::AnnotationContext;
|
||||
use crate::docstrings::extraction::ExtractionTarget;
|
||||
use crate::importer::Importer;
|
||||
use crate::importer::{ImportRequest, Importer, ResolutionError};
|
||||
use crate::noqa::NoqaMapping;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::registry::Rule;
|
||||
|
@ -530,6 +530,28 @@ impl<'a> Checker<'a> {
|
|||
f(&mut checker, self);
|
||||
self.semantic_checker = checker;
|
||||
}
|
||||
|
||||
/// Attempt to create an [`Edit`] that imports `member`.
|
||||
///
|
||||
/// On Python <`version_added_to_typing`, `member` is imported from `typing_extensions`, while
|
||||
/// on Python >=`version_added_to_typing`, it is imported from `typing`.
|
||||
///
|
||||
/// See [`Importer::get_or_import_symbol`] for more details on the returned values.
|
||||
pub(crate) fn import_from_typing(
|
||||
&self,
|
||||
member: &str,
|
||||
position: TextSize,
|
||||
version_added_to_typing: PythonVersion,
|
||||
) -> Result<(Edit, String), ResolutionError> {
|
||||
let source_module = if self.target_version() >= version_added_to_typing {
|
||||
"typing"
|
||||
} else {
|
||||
"typing_extensions"
|
||||
};
|
||||
let request = ImportRequest::import_from(source_module, member);
|
||||
self.importer()
|
||||
.get_or_import_symbol(&request, position, self.semantic())
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticSyntaxContext for Checker<'_> {
|
||||
|
|
|
@ -6,7 +6,6 @@ use ruff_python_semantic::Modules;
|
|||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::rules::fastapi::rules::is_fastapi_route;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
|
@ -232,15 +231,10 @@ fn create_diagnostic(
|
|||
);
|
||||
|
||||
let try_generate_fix = || {
|
||||
let module = if checker.target_version() >= PythonVersion::PY39 {
|
||||
"typing"
|
||||
} else {
|
||||
"typing_extensions"
|
||||
};
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from(module, "Annotated"),
|
||||
let (import_edit, binding) = checker.import_from_typing(
|
||||
"Annotated",
|
||||
parameter.range.start(),
|
||||
checker.semantic(),
|
||||
PythonVersion::PY39,
|
||||
)?;
|
||||
|
||||
// Each of these classes takes a single, optional default
|
||||
|
|
|
@ -14,7 +14,7 @@ use ruff_python_semantic::analyze::visibility;
|
|||
use ruff_python_semantic::{Definition, SemanticModel};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::importer::{ImportRequest, Importer};
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
/// Return the name of the function, if it's overloaded.
|
||||
|
@ -119,26 +119,19 @@ impl AutoPythonType {
|
|||
/// additional edits.
|
||||
pub(crate) fn into_expression(
|
||||
self,
|
||||
importer: &Importer,
|
||||
checker: &Checker,
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
target_version: PythonVersion,
|
||||
) -> Option<(Expr, Vec<Edit>)> {
|
||||
let target_version = checker.target_version();
|
||||
match self {
|
||||
AutoPythonType::Never => {
|
||||
let (no_return_edit, binding) = importer
|
||||
.get_or_import_symbol(
|
||||
&ImportRequest::import_from(
|
||||
"typing",
|
||||
if target_version >= PythonVersion::PY311 {
|
||||
let member = if target_version >= PythonVersion::PY311 {
|
||||
"Never"
|
||||
} else {
|
||||
"NoReturn"
|
||||
},
|
||||
),
|
||||
at,
|
||||
semantic,
|
||||
)
|
||||
};
|
||||
let (no_return_edit, binding) = checker
|
||||
.import_from_typing(member, at, PythonVersion::lowest())
|
||||
.ok()?;
|
||||
let expr = Expr::Name(ast::ExprName {
|
||||
id: Name::from(binding),
|
||||
|
@ -175,12 +168,8 @@ impl AutoPythonType {
|
|||
let element = type_expr(*python_type)?;
|
||||
|
||||
// Ex) `Optional[int]`
|
||||
let (optional_edit, binding) = importer
|
||||
.get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "Optional"),
|
||||
at,
|
||||
semantic,
|
||||
)
|
||||
let (optional_edit, binding) = checker
|
||||
.import_from_typing("Optional", at, PythonVersion::lowest())
|
||||
.ok()?;
|
||||
let expr = typing_optional(element, Name::from(binding));
|
||||
Some((expr, vec![optional_edit]))
|
||||
|
@ -192,12 +181,8 @@ impl AutoPythonType {
|
|||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
// Ex) `Union[int, str]`
|
||||
let (union_edit, binding) = importer
|
||||
.get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "Union"),
|
||||
at,
|
||||
semantic,
|
||||
)
|
||||
let (union_edit, binding) = checker
|
||||
.import_from_typing("Union", at, PythonVersion::lowest())
|
||||
.ok()?;
|
||||
let expr = typing_union(&elements, Name::from(binding));
|
||||
Some((expr, vec![union_edit]))
|
||||
|
|
|
@ -721,12 +721,7 @@ pub(crate) fn definition(
|
|||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.target_version(),
|
||||
)
|
||||
return_type.into_expression(checker, function.parameters.start())
|
||||
})
|
||||
.map(|(return_type, edits)| (checker.generator().expr(&return_type), edits))
|
||||
};
|
||||
|
@ -752,12 +747,7 @@ pub(crate) fn definition(
|
|||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.target_version(),
|
||||
)
|
||||
return_type.into_expression(checker, function.parameters.start())
|
||||
})
|
||||
.map(|(return_type, edits)| (checker.generator().expr(&return_type), edits))
|
||||
};
|
||||
|
@ -822,12 +812,8 @@ pub(crate) fn definition(
|
|||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.target_version(),
|
||||
)
|
||||
return_type
|
||||
.into_expression(checker, function.parameters.start())
|
||||
})
|
||||
.map(|(return_type, edits)| {
|
||||
(checker.generator().expr(&return_type), edits)
|
||||
|
@ -861,12 +847,8 @@ pub(crate) fn definition(
|
|||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.target_version(),
|
||||
)
|
||||
return_type
|
||||
.into_expression(checker, function.parameters.start())
|
||||
})
|
||||
.map(|(return_type, edits)| {
|
||||
(checker.generator().expr(&return_type), edits)
|
||||
|
|
|
@ -8,10 +8,9 @@ use ruff_python_semantic::analyze::class::is_metaclass;
|
|||
use ruff_python_semantic::analyze::function_type::{self, FunctionType};
|
||||
use ruff_python_semantic::analyze::visibility::{is_abstract, is_overload};
|
||||
use ruff_python_semantic::{Binding, ResolvedReference, ScopeId, SemanticModel};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::{ImportRequest, ResolutionError};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
/// ## What it does
|
||||
|
@ -317,7 +316,8 @@ fn replace_custom_typevar_with_self(
|
|||
self_or_cls_annotation: &ast::Expr,
|
||||
) -> anyhow::Result<Fix> {
|
||||
// (1) Import `Self` (if necessary)
|
||||
let (import_edit, self_symbol_binding) = import_self(checker, function_def.start())?;
|
||||
let (import_edit, self_symbol_binding) =
|
||||
checker.import_from_typing("Self", function_def.start(), PythonVersion::PY311)?;
|
||||
|
||||
// (2) Remove the first parameter's annotation
|
||||
let mut other_edits = vec![Edit::deletion(
|
||||
|
@ -367,24 +367,6 @@ fn replace_custom_typevar_with_self(
|
|||
))
|
||||
}
|
||||
|
||||
/// Attempt to create an [`Edit`] that imports `Self`.
|
||||
///
|
||||
/// On Python <3.11, `Self` is imported from `typing_extensions`;
|
||||
/// on Python >=3.11, it is imported from `typing`.
|
||||
/// This is because it was added to the `typing` module on Python 3.11,
|
||||
/// but is available from the backport package `typing_extensions` on all versions.
|
||||
fn import_self(checker: &Checker, position: TextSize) -> Result<(Edit, String), ResolutionError> {
|
||||
let source_module = if checker.target_version() >= PythonVersion::PY311 {
|
||||
"typing"
|
||||
} else {
|
||||
"typing_extensions"
|
||||
};
|
||||
let request = ImportRequest::import_from(source_module, "Self");
|
||||
checker
|
||||
.importer()
|
||||
.get_or_import_symbol(&request, position, checker.semantic())
|
||||
}
|
||||
|
||||
/// Returns a series of [`Edit`]s that modify all references to the given `typevar`.
|
||||
///
|
||||
/// Only references within `editable_range` will be modified.
|
||||
|
|
|
@ -2,18 +2,19 @@ use std::collections::HashSet;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_python_ast::name::Name;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::{Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple, Operator};
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{
|
||||
Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple, Operator, PythonVersion,
|
||||
};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for duplicate union members.
|
||||
|
@ -181,11 +182,8 @@ fn generate_union_fix(
|
|||
debug_assert!(nodes.len() >= 2, "At least two nodes required");
|
||||
|
||||
// Request `typing.Union`
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "Union"),
|
||||
annotation.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("Union", annotation.start(), PythonVersion::lowest())?;
|
||||
|
||||
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
|
||||
let new_expr = Expr::Subscript(ExprSubscript {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
|
@ -214,17 +213,8 @@ fn replace_with_self_fix(
|
|||
) -> anyhow::Result<Fix> {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
let (self_import, self_binding) = {
|
||||
let source_module = if checker.target_version() >= PythonVersion::PY311 {
|
||||
"typing"
|
||||
} else {
|
||||
"typing_extensions"
|
||||
};
|
||||
|
||||
let (importer, semantic) = (checker.importer(), checker.semantic());
|
||||
let request = ImportRequest::import_from(source_module, "Self");
|
||||
importer.get_or_import_symbol(&request, returns.start(), semantic)?
|
||||
};
|
||||
let (self_import, self_binding) =
|
||||
checker.import_from_typing("Self", returns.start(), PythonVersion::PY311)?;
|
||||
|
||||
let mut others = Vec::with_capacity(2);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ use ruff_text_size::{Ranged, TextRange};
|
|||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{checkers::ast::Checker, importer::ImportRequest};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for redundant `Literal[None]` annotations.
|
||||
|
@ -225,10 +225,10 @@ fn create_fix(
|
|||
|
||||
let fix = match union_kind {
|
||||
UnionKind::TypingOptional => {
|
||||
let (import_edit, bound_name) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "Optional"),
|
||||
let (import_edit, bound_name) = checker.import_from_typing(
|
||||
"Optional",
|
||||
literal_expr.start(),
|
||||
checker.semantic(),
|
||||
PythonVersion::lowest(),
|
||||
)?;
|
||||
let optional_expr = typing_optional(new_literal_expr, Name::from(bound_name));
|
||||
let content = checker.generator().expr(&optional_expr);
|
||||
|
|
|
@ -6,12 +6,12 @@ use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Vi
|
|||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{
|
||||
name::Name, AnyParameterRef, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple,
|
||||
Operator, Parameters,
|
||||
Operator, Parameters, PythonVersion,
|
||||
};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::{checkers::ast::Checker, importer::ImportRequest};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for parameter annotations that contain redundant unions between
|
||||
|
@ -268,11 +268,8 @@ fn generate_union_fix(
|
|||
debug_assert!(nodes.len() >= 2, "At least two nodes required");
|
||||
|
||||
// Request `typing.Union`
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "Union"),
|
||||
annotation.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("Optional", annotation.start(), PythonVersion::lowest())?;
|
||||
|
||||
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
|
||||
let new_expr = Expr::Subscript(ExprSubscript {
|
||||
|
|
|
@ -6,7 +6,6 @@ use ruff_python_semantic::{analyze::class::is_enumeration, ScopeKind, SemanticMo
|
|||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::rules::flake8_pyi::rules::TypingModule;
|
||||
use crate::Locator;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
@ -682,11 +681,8 @@ pub(crate) fn type_alias_without_annotation(checker: &Checker, value: &Expr, tar
|
|||
target.range(),
|
||||
);
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import(module.as_str(), "TypeAlias"),
|
||||
target.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("TypeAlias", target.start(), PythonVersion::PY310)?;
|
||||
Ok(Fix::safe_edits(
|
||||
Edit::range_replacement(format!("{id}: {binding}"), target.range()),
|
||||
[import_edit],
|
||||
|
|
|
@ -14,10 +14,10 @@ PYI026.pyi:3:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g
|
|||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Literal, Any
|
||||
2 |+import typing_extensions
|
||||
2 |+from typing_extensions import TypeAlias
|
||||
2 3 |
|
||||
3 |-NewAny = Any
|
||||
4 |+NewAny: typing_extensions.TypeAlias = Any
|
||||
4 |+NewAny: TypeAlias = Any
|
||||
4 5 | OptionalStr = typing.Optional[str]
|
||||
5 6 | Foo = Literal["foo"]
|
||||
6 7 | IntOrStr = int | str
|
||||
|
@ -34,11 +34,11 @@ PYI026.pyi:4:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g
|
|||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Literal, Any
|
||||
2 |+import typing_extensions
|
||||
2 |+from typing_extensions import TypeAlias
|
||||
2 3 |
|
||||
3 4 | NewAny = Any
|
||||
4 |-OptionalStr = typing.Optional[str]
|
||||
5 |+OptionalStr: typing_extensions.TypeAlias = typing.Optional[str]
|
||||
5 |+OptionalStr: TypeAlias = typing.Optional[str]
|
||||
5 6 | Foo = Literal["foo"]
|
||||
6 7 | IntOrStr = int | str
|
||||
7 8 | AliasNone = None
|
||||
|
@ -56,12 +56,12 @@ PYI026.pyi:5:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g
|
|||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Literal, Any
|
||||
2 |+import typing_extensions
|
||||
2 |+from typing_extensions import TypeAlias
|
||||
2 3 |
|
||||
3 4 | NewAny = Any
|
||||
4 5 | OptionalStr = typing.Optional[str]
|
||||
5 |-Foo = Literal["foo"]
|
||||
6 |+Foo: typing_extensions.TypeAlias = Literal["foo"]
|
||||
6 |+Foo: TypeAlias = Literal["foo"]
|
||||
6 7 | IntOrStr = int | str
|
||||
7 8 | AliasNone = None
|
||||
8 9 |
|
||||
|
@ -78,13 +78,13 @@ PYI026.pyi:6:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g
|
|||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Literal, Any
|
||||
2 |+import typing_extensions
|
||||
2 |+from typing_extensions import TypeAlias
|
||||
2 3 |
|
||||
3 4 | NewAny = Any
|
||||
4 5 | OptionalStr = typing.Optional[str]
|
||||
5 6 | Foo = Literal["foo"]
|
||||
6 |-IntOrStr = int | str
|
||||
7 |+IntOrStr: typing_extensions.TypeAlias = int | str
|
||||
7 |+IntOrStr: TypeAlias = int | str
|
||||
7 8 | AliasNone = None
|
||||
8 9 |
|
||||
9 10 | NewAny: typing.TypeAlias = Any
|
||||
|
@ -102,14 +102,14 @@ PYI026.pyi:7:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g
|
|||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Literal, Any
|
||||
2 |+import typing_extensions
|
||||
2 |+from typing_extensions import TypeAlias
|
||||
2 3 |
|
||||
3 4 | NewAny = Any
|
||||
4 5 | OptionalStr = typing.Optional[str]
|
||||
5 6 | Foo = Literal["foo"]
|
||||
6 7 | IntOrStr = int | str
|
||||
7 |-AliasNone = None
|
||||
8 |+AliasNone: typing_extensions.TypeAlias = None
|
||||
8 |+AliasNone: TypeAlias = None
|
||||
8 9 |
|
||||
9 10 | NewAny: typing.TypeAlias = Any
|
||||
10 11 | OptionalStr: TypeAlias = typing.Optional[str]
|
||||
|
@ -126,7 +126,7 @@ PYI026.pyi:17:5: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.
|
|||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Literal, Any
|
||||
2 |+import typing_extensions
|
||||
2 |+from typing_extensions import TypeAlias
|
||||
2 3 |
|
||||
3 4 | NewAny = Any
|
||||
4 5 | OptionalStr = typing.Optional[str]
|
||||
|
@ -135,7 +135,7 @@ PYI026.pyi:17:5: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.
|
|||
15 16 |
|
||||
16 17 | class NotAnEnum:
|
||||
17 |- FLAG_THIS = None
|
||||
18 |+ FLAG_THIS: typing_extensions.TypeAlias = None
|
||||
18 |+ FLAG_THIS: TypeAlias = None
|
||||
18 19 |
|
||||
19 20 | # these are ok
|
||||
20 21 | from enum import Enum
|
||||
|
|
|
@ -10,7 +10,6 @@ use ruff_python_ast::{self as ast, Expr, Operator, Parameters};
|
|||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
|
@ -137,11 +136,8 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
|
|||
)))
|
||||
}
|
||||
ConversionType::Optional => {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "Optional"),
|
||||
expr.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("Optional", expr.start(), PythonVersion::lowest())?;
|
||||
let new_expr = Expr::Subscript(ast::ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
value: Box::new(Expr::Name(ast::ExprName {
|
||||
|
|
|
@ -44,6 +44,11 @@ impl PythonVersion {
|
|||
.into_iter()
|
||||
}
|
||||
|
||||
/// The minimum supported Python version.
|
||||
pub const fn lowest() -> Self {
|
||||
Self::PY37
|
||||
}
|
||||
|
||||
pub const fn latest() -> Self {
|
||||
Self::PY313
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue