Skip to content

Commit 248e1fd

Browse files
committed
Add config option to disable typing_extensions imports
Summary -- This PR resolves #9761 by adding a linter configuration option to disable `typing_extensions` imports. As mentioned [here], it would be ideal if we could detect whether or not `typing_extensions` is available as a dependency automatically, but this seems like a much easier fix in the meantime. The default for the new option `disable-typing-extensions` is `false`, preserving the current behavior. Setting it to `true` will bail out of the new `Checker::import_from_typing` method (#17340) with a new `TypingImportError::TypingExtensionsDisabled` error, which should log an explanatory message whenever `Diagnostic::try_set_fix` is used. [here]: #9761 (comment) Test Plan -- New linter tests exercising several combinations of Python versions and the new config option for PYI019. We could also test other rules, but any rule using `import_from_typing` should behave the same way.
1 parent 3872d57 commit 248e1fd

12 files changed

Lines changed: 230 additions & 34 deletions

crates/ruff/tests/lint.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ fn value_given_to_table_key_is_not_inline_table_2() {
994994
- `lint.extend-per-file-ignores`
995995
- `lint.exclude`
996996
- `lint.preview`
997+
- `lint.disable-typing-extensions`
997998
998999
For more information, try '--help'.
9991000
");
@@ -2117,7 +2118,7 @@ requires-python = ">= 3.11"
21172118
.arg("test.py")
21182119
.arg("-")
21192120
.current_dir(project_dir)
2120-
, @r###"
2121+
, @r#"
21212122
success: true
21222123
exit_code: 0
21232124
----- stdout -----
@@ -2207,6 +2208,7 @@ requires-python = ">= 3.11"
22072208
XXX,
22082209
]
22092210
linter.typing_modules = []
2211+
linter.disable_typing_extensions = false
22102212
22112213
# Linter Plugins
22122214
linter.flake8_annotations.mypy_init_return = false
@@ -2390,7 +2392,7 @@ requires-python = ">= 3.11"
23902392
analyze.include_dependencies = {}
23912393
23922394
----- stderr -----
2393-
"###);
2395+
"#);
23942396
});
23952397
Ok(())
23962398
}
@@ -2428,7 +2430,7 @@ requires-python = ">= 3.11"
24282430
.arg("test.py")
24292431
.arg("-")
24302432
.current_dir(project_dir)
2431-
, @r###"
2433+
, @r#"
24322434
success: true
24332435
exit_code: 0
24342436
----- stdout -----
@@ -2518,6 +2520,7 @@ requires-python = ">= 3.11"
25182520
XXX,
25192521
]
25202522
linter.typing_modules = []
2523+
linter.disable_typing_extensions = false
25212524
25222525
# Linter Plugins
25232526
linter.flake8_annotations.mypy_init_return = false
@@ -2701,7 +2704,7 @@ requires-python = ">= 3.11"
27012704
analyze.include_dependencies = {}
27022705
27032706
----- stderr -----
2704-
"###);
2707+
"#);
27052708
});
27062709
Ok(())
27072710
}
@@ -2790,7 +2793,7 @@ from typing import Union;foo: Union[int, str] = 1
27902793
.args(STDIN_BASE_OPTIONS)
27912794
.arg("test.py")
27922795
.arg("--show-settings")
2793-
.current_dir(project_dir), @r###"
2796+
.current_dir(project_dir), @r#"
27942797
success: true
27952798
exit_code: 0
27962799
----- stdout -----
@@ -2881,6 +2884,7 @@ from typing import Union;foo: Union[int, str] = 1
28812884
XXX,
28822885
]
28832886
linter.typing_modules = []
2887+
linter.disable_typing_extensions = false
28842888
28852889
# Linter Plugins
28862890
linter.flake8_annotations.mypy_init_return = false
@@ -3064,7 +3068,7 @@ from typing import Union;foo: Union[int, str] = 1
30643068
analyze.include_dependencies = {}
30653069
30663070
----- stderr -----
3067-
"###);
3071+
"#);
30683072
});
30693073
Ok(())
30703074
}
@@ -3170,7 +3174,7 @@ from typing import Union;foo: Union[int, str] = 1
31703174
.arg("--show-settings")
31713175
.args(["--select","UP007"])
31723176
.arg("foo/test.py")
3173-
.current_dir(&project_dir), @r###"
3177+
.current_dir(&project_dir), @r#"
31743178
success: true
31753179
exit_code: 0
31763180
----- stdout -----
@@ -3260,6 +3264,7 @@ from typing import Union;foo: Union[int, str] = 1
32603264
XXX,
32613265
]
32623266
linter.typing_modules = []
3267+
linter.disable_typing_extensions = false
32633268
32643269
# Linter Plugins
32653270
linter.flake8_annotations.mypy_init_return = false
@@ -3443,7 +3448,7 @@ from typing import Union;foo: Union[int, str] = 1
34433448
analyze.include_dependencies = {}
34443449
34453450
----- stderr -----
3446-
"###);
3451+
"#);
34473452
});
34483453
Ok(())
34493454
}
@@ -3497,7 +3502,7 @@ from typing import Union;foo: Union[int, str] = 1
34973502
.arg("--show-settings")
34983503
.args(["--select","UP007"])
34993504
.arg("foo/test.py")
3500-
.current_dir(&project_dir), @r###"
3505+
.current_dir(&project_dir), @r#"
35013506
success: true
35023507
exit_code: 0
35033508
----- stdout -----
@@ -3587,6 +3592,7 @@ from typing import Union;foo: Union[int, str] = 1
35873592
XXX,
35883593
]
35893594
linter.typing_modules = []
3595+
linter.disable_typing_extensions = false
35903596
35913597
# Linter Plugins
35923598
linter.flake8_annotations.mypy_init_return = false
@@ -3770,7 +3776,7 @@ from typing import Union;foo: Union[int, str] = 1
37703776
analyze.include_dependencies = {}
37713777
37723778
----- stderr -----
3773-
"###);
3779+
"#);
37743780
});
37753781
Ok(())
37763782
}
@@ -3823,7 +3829,7 @@ from typing import Union;foo: Union[int, str] = 1
38233829
.args(STDIN_BASE_OPTIONS)
38243830
.arg("--show-settings")
38253831
.arg("foo/test.py")
3826-
.current_dir(&project_dir), @r###"
3832+
.current_dir(&project_dir), @r#"
38273833
success: true
38283834
exit_code: 0
38293835
----- stdout -----
@@ -3914,6 +3920,7 @@ from typing import Union;foo: Union[int, str] = 1
39143920
XXX,
39153921
]
39163922
linter.typing_modules = []
3923+
linter.disable_typing_extensions = false
39173924
39183925
# Linter Plugins
39193926
linter.flake8_annotations.mypy_init_return = false
@@ -4097,7 +4104,7 @@ from typing import Union;foo: Union[int, str] = 1
40974104
analyze.include_dependencies = {}
40984105
40994106
----- stderr -----
4100-
"###);
4107+
"#);
41014108
});
41024109

41034110
insta::with_settings!({
@@ -4107,7 +4114,7 @@ from typing import Union;foo: Union[int, str] = 1
41074114
.args(STDIN_BASE_OPTIONS)
41084115
.arg("--show-settings")
41094116
.arg("test.py")
4110-
.current_dir(project_dir.join("foo")), @r###"
4117+
.current_dir(project_dir.join("foo")), @r#"
41114118
success: true
41124119
exit_code: 0
41134120
----- stdout -----
@@ -4198,6 +4205,7 @@ from typing import Union;foo: Union[int, str] = 1
41984205
XXX,
41994206
]
42004207
linter.typing_modules = []
4208+
linter.disable_typing_extensions = false
42014209
42024210
# Linter Plugins
42034211
linter.flake8_annotations.mypy_init_return = false
@@ -4381,7 +4389,7 @@ from typing import Union;foo: Union[int, str] = 1
43814389
analyze.include_dependencies = {}
43824390
43834391
----- stderr -----
4384-
"###);
4392+
"#);
43854393
});
43864394
Ok(())
43874395
}
@@ -4444,7 +4452,7 @@ from typing import Union;foo: Union[int, str] = 1
44444452
.args(STDIN_BASE_OPTIONS)
44454453
.arg("--show-settings")
44464454
.arg("test.py")
4447-
.current_dir(&project_dir), @r###"
4455+
.current_dir(&project_dir), @r#"
44484456
success: true
44494457
exit_code: 0
44504458
----- stdout -----
@@ -4535,6 +4543,7 @@ from typing import Union;foo: Union[int, str] = 1
45354543
XXX,
45364544
]
45374545
linter.typing_modules = []
4546+
linter.disable_typing_extensions = false
45384547
45394548
# Linter Plugins
45404549
linter.flake8_annotations.mypy_init_return = false
@@ -4718,7 +4727,7 @@ from typing import Union;foo: Union[int, str] = 1
47184727
analyze.include_dependencies = {}
47194728
47204729
----- stderr -----
4721-
"###);
4730+
"#);
47224731
});
47234732

47244733
Ok(())

crates/ruff/tests/snapshots/show_settings__display_default_settings.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ linter.task_tags = [
213213
XXX,
214214
]
215215
linter.typing_modules = []
216+
linter.disable_typing_extensions = false
216217

217218
# Linter Plugins
218219
linter.flake8_annotations.mypy_init_return = false

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,18 +544,41 @@ impl<'a> Checker<'a> {
544544
member: &str,
545545
position: TextSize,
546546
version_added_to_typing: PythonVersion,
547-
) -> Result<(Edit, String), ResolutionError> {
547+
) -> Result<(Edit, String), TypingImportError> {
548548
let source_module = if self.target_version() >= version_added_to_typing {
549549
"typing"
550+
} else if self.settings.disable_typing_extensions {
551+
return Err(TypingImportError::TypingExtensionsDisabled);
550552
} else {
551553
"typing_extensions"
552554
};
553555
let request = ImportRequest::import_from(source_module, member);
554556
self.importer()
555557
.get_or_import_symbol(&request, position, self.semantic())
558+
.map_err(TypingImportError::ResolutionError)
556559
}
557560
}
558561

562+
/// The result of a [`Checker::import_from_typing`] call.
563+
#[derive(Debug)]
564+
pub(crate) enum TypingImportError {
565+
ResolutionError(ResolutionError),
566+
TypingExtensionsDisabled,
567+
}
568+
569+
impl std::fmt::Display for TypingImportError {
570+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571+
match self {
572+
TypingImportError::ResolutionError(resolution_error) => resolution_error.fmt(f),
573+
TypingImportError::TypingExtensionsDisabled => f.write_str(
574+
"`typing_extensions` disabled by `lint.disable_typing_extensions` config option",
575+
),
576+
}
577+
}
578+
}
579+
580+
impl std::error::Error for TypingImportError {}
581+
559582
impl SemanticSyntaxContext for Checker<'_> {
560583
fn seen_docstring_boundary(&self) -> bool {
561584
self.semantic.seen_module_docstring_boundary()

0 commit comments

Comments
 (0)