diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote.py index 041bb3d2b73917..67332e3010f24b 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote.py @@ -40,6 +40,23 @@ def baz() -> pd.DataFrame | int: ... + +def f(): + from pandas import DataFrame + + def baz() -> DataFrame(): + ... + + +def f(): + from typing import Literal + + from pandas import DataFrame + + def baz() -> DataFrame[Literal["int"]]: + ... + + def f(): from typing import TYPE_CHECKING diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs index f502d3519274e2..55b333d3f83e2b 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs @@ -1,5 +1,6 @@ -use crate::rules::flake8_type_checking::settings::Settings; use anyhow::Result; +use rustc_hash::FxHashSet; + use ruff_diagnostics::Edit; use ruff_python_ast::call_path::from_qualified_name; use ruff_python_ast::helpers::{map_callable, map_subscript}; @@ -8,7 +9,8 @@ use ruff_python_codegen::Stylist; use ruff_python_semantic::{Binding, BindingId, BindingKind, NodeId, SemanticModel}; use ruff_source_file::Locator; use ruff_text_size::Ranged; -use rustc_hash::FxHashSet; + +use crate::rules::flake8_type_checking::settings::Settings; pub(crate) fn is_valid_runtime_import( binding: &Binding, @@ -30,7 +32,7 @@ pub(crate) fn is_valid_runtime_import( || reference.in_typing_only_annotation() || reference.in_complex_string_type_definition() || reference.in_simple_string_type_definition() - || (settings.annotation_strategy.is_quote() + || (settings.quote_annotations && reference.in_runtime_evaluated_annotation())) }) } else { @@ -202,6 +204,8 @@ pub(crate) fn is_singledispatch_implementation( /// - When quoting `kubernetes` in `kubernetes.SecurityContext`, we want `"kubernetes.SecurityContext"`. /// - When quoting `Series` in `Series["pd.Timestamp"]`, we want `"Series[pd.Timestamp]"`. /// - When quoting `Series` in `Series[Literal["pd.Timestamp"]]`, we want `"Series[Literal['pd.Timestamp']]"`. +/// +/// In general, when expanding a component of a call chain, we want to quote the entire call chain. pub(crate) fn quote_annotation( node_id: NodeId, semantic: &SemanticModel, @@ -227,6 +231,14 @@ pub(crate) fn quote_annotation( return quote_annotation(parent_id, semantic, locator, stylist); } } + Expr::Call(parent) => { + if expr == parent.func.as_ref() { + // If we're quoting the function of a call, we need to quote the entire + // expression. For example, when quoting `DataFrame` in `DataFrame()`, we + // should generate `"DataFrame()"`. + return quote_annotation(parent_id, semantic, locator, stylist); + } + } Expr::BinOp(parent) => { if parent.op.is_bit_or() { // If we're quoting the left or right side of a binary operation, we need to diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs index 754b7e8ae74ac2..0f97bc708a4464 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs @@ -13,7 +13,6 @@ mod tests { use test_case::test_case; use crate::registry::{Linter, Rule}; - use crate::rules::flake8_type_checking::settings::AnnotationStrategy; use crate::test::{test_path, test_snippet}; use crate::{assert_messages, settings}; @@ -63,7 +62,7 @@ mod tests { Path::new("flake8_type_checking").join(path).as_path(), &settings::LinterSettings { flake8_type_checking: super::settings::Settings { - annotation_strategy: AnnotationStrategy::Quote, + quote_annotations: true, ..Default::default() }, ..settings::LinterSettings::for_rule(rule_code) diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs index 6a766392f1403a..08a639f3a427a0 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs @@ -22,11 +22,10 @@ use crate::rules::flake8_type_checking::imports::ImportBinding; /// The type-checking block is not executed at runtime, so the import will not /// be available at runtime. /// -/// If [`flake8-type-checking.annotation-strategy`] is set to `"quote"`, +/// If [`flake8-type-checking.quote-annotations`] is set to `true`, /// annotations will be wrapped in quotes if doing so would enable the /// corresponding import to remain in the type-checking block. /// -/// /// ## Example /// ```python /// from typing import TYPE_CHECKING @@ -49,7 +48,7 @@ use crate::rules::flake8_type_checking::imports::ImportBinding; /// ``` /// /// ## Options -/// - `flake8-type-checking.annotation-strategy` +/// - `flake8-type-checking.quote-annotations` /// /// ## References /// - [PEP 535](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking) @@ -145,18 +144,13 @@ pub(crate) fn runtime_import_in_type_checking_block( } else { // Determine whether the member should be fixed by moving the import out of the // type-checking block, or by quoting its references. - if checker - .settings - .flake8_type_checking - .annotation_strategy - .is_quote() + if checker.settings.flake8_type_checking.quote_annotations && binding.references().all(|reference_id| { let reference = checker.semantic().reference(reference_id); reference.context().is_typing() || reference.in_runtime_evaluated_annotation() }) { - println!("quote"); quotes_by_statement.entry(node_id).or_default().push(import); } else { moves_by_statement.entry(node_id).or_default().push(import); diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index b0184f4642d2b8..5e854929e5b7b7 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -26,7 +26,7 @@ use crate::rules::isort::{categorize, ImportSection, ImportType}; /// instead be imported conditionally under an `if TYPE_CHECKING:` block to /// minimize runtime overhead. /// -/// If [`flake8-type-checking.annotation-strategy`] is set to `"quote"`, +/// If [`flake8-type-checking.quote-annotations`] is set to `true`, /// annotations will be wrapped in quotes if doing so would enable the /// corresponding import to be moved into an `if TYPE_CHECKING:` block. /// @@ -62,7 +62,7 @@ use crate::rules::isort::{categorize, ImportSection, ImportType}; /// ``` /// /// ## Options -/// - `flake8-type-checking.annotation-strategy` +/// - `flake8-type-checking.quote-annotations` /// - `flake8-type-checking.runtime-evaluated-base-classes` /// - `flake8-type-checking.runtime-evaluated-decorators` /// @@ -99,7 +99,7 @@ impl Violation for TypingOnlyFirstPartyImport { /// instead be imported conditionally under an `if TYPE_CHECKING:` block to /// minimize runtime overhead. /// -/// If [`flake8-type-checking.annotation-strategy`] is set to `"quote"`, +/// If [`flake8-type-checking.quote-annotations`] is set to `true`, /// annotations will be wrapped in quotes if doing so would enable the /// corresponding import to be moved into an `if TYPE_CHECKING:` block. /// @@ -135,7 +135,7 @@ impl Violation for TypingOnlyFirstPartyImport { /// ``` /// /// ## Options -/// - `flake8-type-checking.annotation-strategy` +/// - `flake8-type-checking.quote-annotations` /// - `flake8-type-checking.runtime-evaluated-base-classes` /// - `flake8-type-checking.runtime-evaluated-decorators` /// @@ -172,7 +172,7 @@ impl Violation for TypingOnlyThirdPartyImport { /// instead be imported conditionally under an `if TYPE_CHECKING:` block to /// minimize runtime overhead. /// -/// If [`flake8-type-checking.annotation-strategy`] is set to `"quote"`, +/// If [`flake8-type-checking.quote-annotations`] is set to `true`, /// annotations will be wrapped in quotes if doing so would enable the /// corresponding import to be moved into an `if TYPE_CHECKING:` block. /// @@ -208,7 +208,7 @@ impl Violation for TypingOnlyThirdPartyImport { /// ``` /// /// ## Options -/// - `flake8-type-checking.annotation-strategy` +/// - `flake8-type-checking.quote-annotations` /// - `flake8-type-checking.runtime-evaluated-base-classes` /// - `flake8-type-checking.runtime-evaluated-decorators` /// @@ -281,11 +281,7 @@ pub(crate) fn typing_only_runtime_import( || reference.in_typing_only_annotation() || reference.in_complex_string_type_definition() || reference.in_simple_string_type_definition() - || (checker - .settings - .flake8_type_checking - .annotation_strategy - .is_quote() + || (checker.settings.flake8_type_checking.quote_annotations && reference.in_runtime_evaluated_annotation()) }) { diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs b/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs index 5380d16babb581..26730af895d086 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs @@ -1,7 +1,6 @@ //! Settings for the `flake8-type-checking` plugin. use ruff_macros::CacheKey; -use serde::{Deserialize, Serialize}; #[derive(Debug, CacheKey)] pub struct Settings { @@ -9,7 +8,7 @@ pub struct Settings { pub exempt_modules: Vec, pub runtime_evaluated_base_classes: Vec, pub runtime_evaluated_decorators: Vec, - pub annotation_strategy: AnnotationStrategy, + pub quote_annotations: bool, } impl Default for Settings { @@ -19,26 +18,7 @@ impl Default for Settings { exempt_modules: vec!["typing".to_string(), "typing_extensions".to_string()], runtime_evaluated_base_classes: vec![], runtime_evaluated_decorators: vec![], - annotation_strategy: AnnotationStrategy::Preserve, + quote_annotations: false, } } } - -#[derive( - Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, CacheKey, is_macro::Is, -)] -#[serde(deny_unknown_fields, rename_all = "kebab-case")] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub enum AnnotationStrategy { - /// Avoid changing the semantics of runtime-evaluated annotations (e.g., by quoting them, or - /// inserting `from __future__ import annotations`). Imports will be classified as typing-only - /// or runtime-required based exclusively on the existing type annotations. - #[default] - Preserve, - /// Quote runtime-evaluated annotations, if doing so would enable the corresponding import to - /// be moved into an `if TYPE_CHECKING:` block. - Quote, - /// Insert `from __future__ import annotations` at the top of the file, if doing so would enable - /// an import to be moved into an `if TYPE_CHECKING:` block. - Future, -} diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_runtime-import-in-type-checking-block_quote.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_runtime-import-in-type-checking-block_quote.py.snap index 7d935c57a185eb..5fd78ec8a04976 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_runtime-import-in-type-checking-block_quote.py.snap +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_runtime-import-in-type-checking-block_quote.py.snap @@ -1,22 +1,22 @@ --- source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs --- -quote.py:47:28: TCH004 [*] Quote references to `pandas.DataFrame`. Import is not available at runtime. +quote.py:64:28: TCH004 [*] Quote references to `pandas.DataFrame`. Import is not available at runtime. | -46 | if TYPE_CHECKING: -47 | from pandas import DataFrame +63 | if TYPE_CHECKING: +64 | from pandas import DataFrame | ^^^^^^^^^ TCH004 -48 | -49 | def func(value: DataFrame): +65 | +66 | def func(value: DataFrame): | = help: Quote references ℹ Unsafe fix -46 46 | if TYPE_CHECKING: -47 47 | from pandas import DataFrame -48 48 | -49 |- def func(value: DataFrame): - 49 |+ def func(value: "DataFrame"): -50 50 | ... +63 63 | if TYPE_CHECKING: +64 64 | from pandas import DataFrame +65 65 | +66 |- def func(value: DataFrame): + 66 |+ def func(value: "DataFrame"): +67 67 | ... diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_typing-only-third-party-import_quote.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_typing-only-third-party-import_quote.py.snap index 14df699c48d49e..9c43070c0f5b8c 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_typing-only-third-party-import_quote.py.snap +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__quote_typing-only-third-party-import_quote.py.snap @@ -155,4 +155,45 @@ quote.py:37:22: TCH002 [*] Move third-party import `pandas` into a type-checking 41 44 | 42 45 | +quote.py:45:24: TCH002 [*] Move third-party import `pandas.DataFrame` into a type-checking block + | +44 | def f(): +45 | from pandas import DataFrame + | ^^^^^^^^^ TCH002 +46 | +47 | def baz() -> DataFrame(): + | + = help: Move into type-checking block + +ℹ Unsafe fix + 1 |+from typing import TYPE_CHECKING + 2 |+ + 3 |+if TYPE_CHECKING: + 4 |+ from pandas import DataFrame +1 5 | def f(): +2 6 | from pandas import DataFrame +3 7 | +-------------------------------------------------------------------------------- +42 46 | +43 47 | +44 48 | def f(): +45 |- from pandas import DataFrame +46 49 | +47 |- def baz() -> DataFrame(): + 50 |+ def baz() -> "DataFrame()": +48 51 | ... +49 52 | +50 53 | + +quote.py:54:24: TCH002 Move third-party import `pandas.DataFrame` into a type-checking block + | +52 | from typing import Literal +53 | +54 | from pandas import DataFrame + | ^^^^^^^^^ TCH002 +55 | +56 | def baz() -> DataFrame[Literal["int"]]: + | + = help: Move into type-checking block + diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_quote.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_quote.py.snap index 73d0d948de4a09..c09777853b97a2 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_quote.py.snap +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_quote.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs --- -quote.py:47:28: TCH004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting. +quote.py:64:28: TCH004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting. | -46 | if TYPE_CHECKING: -47 | from pandas import DataFrame +63 | if TYPE_CHECKING: +64 | from pandas import DataFrame | ^^^^^^^^^ TCH004 -48 | -49 | def func(value: DataFrame): +65 | +66 | def func(value: DataFrame): | = help: Move out of type-checking block @@ -17,13 +17,13 @@ quote.py:47:28: TCH004 [*] Move import `pandas.DataFrame` out of type-checking b 2 3 | from pandas import DataFrame 3 4 | -------------------------------------------------------------------------------- -44 45 | from typing import TYPE_CHECKING -45 46 | -46 47 | if TYPE_CHECKING: -47 |- from pandas import DataFrame - 48 |+ pass -48 49 | -49 50 | def func(value: DataFrame): -50 51 | ... +61 62 | from typing import TYPE_CHECKING +62 63 | +63 64 | if TYPE_CHECKING: +64 |- from pandas import DataFrame + 65 |+ pass +65 66 | +66 67 | def func(value: DataFrame): +67 68 | ... diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 8c9d9aec638b6f..eeeb85a47af28a 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -12,7 +12,6 @@ use ruff_linter::rules::flake8_pytest_style::settings::SettingsError; use ruff_linter::rules::flake8_pytest_style::types; use ruff_linter::rules::flake8_quotes::settings::Quote; use ruff_linter::rules::flake8_tidy_imports::settings::{ApiBan, Strictness}; -use ruff_linter::rules::flake8_type_checking::settings::AnnotationStrategy; use ruff_linter::rules::isort::settings::RelativeImportsOrder; use ruff_linter::rules::isort::{ImportSection, ImportType}; use ruff_linter::rules::pydocstyle::settings::Convention; @@ -1642,8 +1641,8 @@ pub struct Flake8TypeCheckingOptions { )] pub runtime_evaluated_decorators: Option>, - /// The strategy to use when analyzing type annotations that, by default, - /// Python would evaluate at runtime. + /// Whether to add quotes around type annotations, if doing so would allow + /// the corresponding import to be moved into a type-checking block. /// /// For example, in the following, Python requires that `Sequence` be /// available at runtime, despite the fact that it's only used in a type @@ -1658,31 +1657,27 @@ pub struct Flake8TypeCheckingOptions { /// In other words, moving `from collections.abc import Sequence` into an /// `if TYPE_CHECKING:` block above would cause a runtime error. /// - /// By default, Ruff will respect such runtime evaluations (`"preserve"`), - /// but supports two alternative strategies for handling them: + /// By default, Ruff will respect such runtime semantics, and avoid moving + /// the import. /// - /// - `"quote"`: Add quotes around the annotation (e.g., `"Sequence[int]"`), to - /// prevent Python from evaluating it at runtime. Adding quotes around - /// `Sequence` above will allow Ruff to move the import into an - /// `if TYPE_CHECKING:` block. - /// - `"future"`: Add a `from __future__ import annotations` import at the - /// top of the file, which will cause Python to treat all annotations as - /// strings, rather than evaluating them at runtime. + /// However, setting `quote-annotations` to `true` will instruct Ruff + /// to add quotes around the annotation (e.g., `"Sequence[int]"`). Adding + /// quotes around `Sequence`, above, will enable Ruff to move the import + /// into an `if TYPE_CHECKING:` block. /// - /// Setting `annotation-strategy` to `"quote"` or `"future"` will allow - /// Ruff to move more imports into type-checking blocks, but may cause - /// issues with other tools that expect annotations to be evaluated at - /// runtime. + /// Note that this setting has no effect when `from __future__ import annotations` + /// is present, as `__future__` annotations are always treated equivalently + /// to quoted annotations. #[option( - default = "[]", - value_type = r#""preserve" | "quote" | "future""#, + default = "false", + value_type = "bool", example = r#" # Add quotes around type annotations, if doing so would allow # an import to be moved into a type-checking block. - annotation-strategy = "quote" + quote-annotations = true "# )] - pub annotation_strategy: Option, + pub quote_annotations: Option, } impl Flake8TypeCheckingOptions { @@ -1694,7 +1689,7 @@ impl Flake8TypeCheckingOptions { .unwrap_or_else(|| vec!["typing".to_string()]), runtime_evaluated_base_classes: self.runtime_evaluated_base_classes.unwrap_or_default(), runtime_evaluated_decorators: self.runtime_evaluated_decorators.unwrap_or_default(), - annotation_strategy: self.annotation_strategy.unwrap_or_default(), + quote_annotations: self.quote_annotations.unwrap_or_default(), } } } diff --git a/pyproject.toml b/pyproject.toml index 2d2b49ebe08074..981dca4ca291b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,3 @@ version_files = [ "crates/ruff_shrinking/Cargo.toml", "scripts/benchmarks/pyproject.toml", ] - -[tool.ruff.flake8-type-checking] -annotation-strategy = "quote" diff --git a/ruff.schema.json b/ruff.schema.json index 7892d0d011551b..366271d6a66091 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -699,31 +699,6 @@ }, "additionalProperties": false, "definitions": { - "AnnotationStrategy": { - "oneOf": [ - { - "description": "Avoid changing the semantics of runtime-evaluated annotations (e.g., by quoting them, or inserting `from __future__ import annotations`). Imports will be classified as typing-only or runtime-required based exclusively on the existing type annotations.", - "type": "string", - "enum": [ - "preserve" - ] - }, - { - "description": "Quote runtime-evaluated annotations, if doing so would enable the corresponding import to be moved into an `if TYPE_CHECKING:` block.", - "type": "string", - "enum": [ - "quote" - ] - }, - { - "description": "Insert `from __future__ import annotations` at the top of the file, if doing so would enable an import to be moved into an `if TYPE_CHECKING:` block.", - "type": "string", - "enum": [ - "future" - ] - } - ] - }, "ApiBan": { "type": "object", "required": [ @@ -1209,17 +1184,6 @@ "Flake8TypeCheckingOptions": { "type": "object", "properties": { - "annotation-strategy": { - "description": "The strategy to use when analyzing type annotations that, by default, Python would evaluate at runtime.\n\nFor example, in the following, Python requires that `Sequence` be available at runtime, despite the fact that it's only used in a type annotation: ```python from collections.abc import Sequence\n\ndef func(value: Sequence[int]) -> None: ... ```\n\nIn other words, moving `from collections.abc import Sequence` into an `if TYPE_CHECKING:` block above would cause a runtime error.\n\nBy default, Ruff will respect such runtime evaluations (`\"preserve\"`), but supports two alternative strategies for handling them:\n\n- `\"quote\"`: Add quotes around the annotation (e.g., `\"Sequence[int]\"`), to prevent Python from evaluating it at runtime. Adding quotes around `Sequence` above will allow Ruff to move the import into an `if TYPE_CHECKING:` block. - `\"future\"`: Add a `from __future__ import annotations` import at the top of the file, which will cause Python to treat all annotations as strings, rather than evaluating them at runtime.\n\nSetting `annotation-strategy` to `\"quote\"` or `\"future\"` will allow Ruff to move more imports into type-checking blocks, but may cause issues with other tools that expect annotations to be evaluated at runtime.", - "anyOf": [ - { - "$ref": "#/definitions/AnnotationStrategy" - }, - { - "type": "null" - } - ] - }, "exempt-modules": { "description": "Exempt certain modules from needing to be moved into type-checking blocks.", "type": [ @@ -1230,6 +1194,13 @@ "type": "string" } }, + "quote-annotations": { + "description": "Whether to add quotes around type annotations, if doing so would allow the corresponding import to be moved into a type-checking block.\n\nFor example, in the following, Python requires that `Sequence` be available at runtime, despite the fact that it's only used in a type annotation: ```python from collections.abc import Sequence\n\ndef func(value: Sequence[int]) -> None: ... ```\n\nIn other words, moving `from collections.abc import Sequence` into an `if TYPE_CHECKING:` block above would cause a runtime error.\n\nBy default, Ruff will respect such runtime semantics, and avoid moving the import.\n\nHowever, setting `quote-annotations` to `true` will instruct Ruff to add quotes around the annotation (e.g., `\"Sequence[int]\"`). Adding quotes around `Sequence`, above, will enable Ruff to move the import into an `if TYPE_CHECKING:` block.\n\nNote that this setting has no effect when `from __future__ import annotations` is present, as `__future__` annotations are always treated equivalently to quoted annotations.", + "type": [ + "boolean", + "null" + ] + }, "runtime-evaluated-base-classes": { "description": "Exempt classes that list any of the enumerated classes as a base class from needing to be moved into type-checking blocks.\n\nCommon examples include Pydantic's `pydantic.BaseModel` and SQLAlchemy's `sqlalchemy.orm.DeclarativeBase`, but can also support user-defined classes that inherit from those base classes. For example, if you define a common `DeclarativeBase` subclass that's used throughout your project (e.g., `class Base(DeclarativeBase) ...` in `base.py`), you can add it to this list (`runtime-evaluated-base-classes = [\"base.Base\"]`) to exempt models from being moved into type-checking blocks.", "type": [