From 7e9023b6f85c28bcfdc7fe82a507d7752e84edcf Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 19 Aug 2023 18:16:44 -0400 Subject: [PATCH] Use `typing_extensions.TypeAlias` for PYI026 fixes on pre-3.10 (#6696) Closes https://github.com/astral-sh/ruff/issues/6695. --- crates/ruff/src/rules/flake8_pyi/mod.rs | 62 +++++----- crates/ruff/src/rules/flake8_pyi/rules/mod.rs | 24 ++++ .../rules/flake8_pyi/rules/simple_defaults.rs | 31 +++-- ..._flake8_pyi__tests__PYI026_PYI026.pyi.snap | 20 +-- ...ke8_pyi__tests__py38_PYI026_PYI026.py.snap | 4 + ...e8_pyi__tests__py38_PYI026_PYI026.pyi.snap | 117 ++++++++++++++++++ 6 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.py.snap create mode 100644 crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.pyi.snap diff --git a/crates/ruff/src/rules/flake8_pyi/mod.rs b/crates/ruff/src/rules/flake8_pyi/mod.rs index d565839f6186d..c5c545a55bab0 100644 --- a/crates/ruff/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff/src/rules/flake8_pyi/mod.rs @@ -30,46 +30,50 @@ mod tests { #[test_case(Rule::ComplexAssignmentInStub, Path::new("PYI017.pyi"))] #[test_case(Rule::ComplexIfStatementInStub, Path::new("PYI002.py"))] #[test_case(Rule::ComplexIfStatementInStub, Path::new("PYI002.pyi"))] + #[test_case(Rule::CustomTypeVarReturnType, Path::new("PYI019.py"))] + #[test_case(Rule::CustomTypeVarReturnType, Path::new("PYI019.pyi"))] #[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))] #[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))] #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))] #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))] #[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.py"))] #[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.pyi"))] - #[test_case(Rule::NonSelfReturnType, Path::new("PYI034.py"))] - #[test_case(Rule::NonSelfReturnType, Path::new("PYI034.pyi"))] + #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.py"))] + #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))] #[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.py"))] #[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.pyi"))] #[test_case(Rule::NoReturnArgumentAnnotationInStub, Path::new("PYI050.py"))] #[test_case(Rule::NoReturnArgumentAnnotationInStub, Path::new("PYI050.pyi"))] - #[test_case(Rule::NumericLiteralTooLong, Path::new("PYI054.py"))] - #[test_case(Rule::NumericLiteralTooLong, Path::new("PYI054.pyi"))] #[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.py"))] #[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.pyi"))] + #[test_case(Rule::NonSelfReturnType, Path::new("PYI034.py"))] + #[test_case(Rule::NonSelfReturnType, Path::new("PYI034.pyi"))] + #[test_case(Rule::NumericLiteralTooLong, Path::new("PYI054.py"))] + #[test_case(Rule::NumericLiteralTooLong, Path::new("PYI054.pyi"))] #[test_case(Rule::PassInClassBody, Path::new("PYI012.py"))] #[test_case(Rule::PassInClassBody, Path::new("PYI012.pyi"))] #[test_case(Rule::PassStatementStubBody, Path::new("PYI009.py"))] #[test_case(Rule::PassStatementStubBody, Path::new("PYI009.pyi"))] + #[test_case(Rule::PatchVersionComparison, Path::new("PYI004.py"))] + #[test_case(Rule::PatchVersionComparison, Path::new("PYI004.pyi"))] #[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.py"))] #[test_case(Rule::QuotedAnnotationInStub, Path::new("PYI020.pyi"))] + #[test_case(Rule::RedundantLiteralUnion, Path::new("PYI051.py"))] + #[test_case(Rule::RedundantLiteralUnion, Path::new("PYI051.pyi"))] #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041.py"))] #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041.pyi"))] #[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))] #[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))] - #[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.py"))] - #[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.pyi"))] #[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.py"))] #[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.pyi"))] - #[test_case(Rule::UnnecessaryLiteralUnion, Path::new("PYI030.py"))] - #[test_case(Rule::UnnecessaryLiteralUnion, Path::new("PYI030.pyi"))] + #[test_case(Rule::StringOrBytesTooLong, Path::new("PYI053.py"))] + #[test_case(Rule::StringOrBytesTooLong, Path::new("PYI053.pyi"))] #[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.py"))] #[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.pyi"))] #[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))] #[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.pyi"))] - #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.py"))] - #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))] - #[test_case(Rule::PatchVersionComparison, Path::new("PYI004.py"))] - #[test_case(Rule::PatchVersionComparison, Path::new("PYI004.pyi"))] + #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.py"))] + #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.pyi"))] #[test_case(Rule::TypeCommentInStub, Path::new("PYI033.py"))] #[test_case(Rule::TypeCommentInStub, Path::new("PYI033.pyi"))] #[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.py"))] @@ -78,8 +82,12 @@ mod tests { #[test_case(Rule::UnaliasedCollectionsAbcSetImport, Path::new("PYI025.pyi"))] #[test_case(Rule::UnannotatedAssignmentInStub, Path::new("PYI052.py"))] #[test_case(Rule::UnannotatedAssignmentInStub, Path::new("PYI052.pyi"))] - #[test_case(Rule::StringOrBytesTooLong, Path::new("PYI053.py"))] - #[test_case(Rule::StringOrBytesTooLong, Path::new("PYI053.pyi"))] + #[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.py"))] + #[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.pyi"))] + #[test_case(Rule::UnnecessaryLiteralUnion, Path::new("PYI030.py"))] + #[test_case(Rule::UnnecessaryLiteralUnion, Path::new("PYI030.pyi"))] + #[test_case(Rule::UnnecessaryTypeUnion, Path::new("PYI055.py"))] + #[test_case(Rule::UnnecessaryTypeUnion, Path::new("PYI055.pyi"))] #[test_case(Rule::UnprefixedTypeParam, Path::new("PYI001.py"))] #[test_case(Rule::UnprefixedTypeParam, Path::new("PYI001.pyi"))] #[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.py"))] @@ -88,24 +96,18 @@ mod tests { #[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.pyi"))] #[test_case(Rule::UnrecognizedVersionInfoCheck, Path::new("PYI003.py"))] #[test_case(Rule::UnrecognizedVersionInfoCheck, Path::new("PYI003.pyi"))] - #[test_case(Rule::WrongTupleLengthVersionComparison, Path::new("PYI005.py"))] - #[test_case(Rule::WrongTupleLengthVersionComparison, Path::new("PYI005.pyi"))] - #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.py"))] - #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.pyi"))] #[test_case(Rule::UnsupportedMethodCallOnAll, Path::new("PYI056.py"))] #[test_case(Rule::UnsupportedMethodCallOnAll, Path::new("PYI056.pyi"))] - #[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.py"))] - #[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.pyi"))] #[test_case(Rule::UnusedPrivateProtocol, Path::new("PYI046.py"))] #[test_case(Rule::UnusedPrivateProtocol, Path::new("PYI046.pyi"))] #[test_case(Rule::UnusedPrivateTypeAlias, Path::new("PYI047.py"))] #[test_case(Rule::UnusedPrivateTypeAlias, Path::new("PYI047.pyi"))] + #[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.py"))] + #[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.pyi"))] #[test_case(Rule::UnusedPrivateTypedDict, Path::new("PYI049.py"))] #[test_case(Rule::UnusedPrivateTypedDict, Path::new("PYI049.pyi"))] - #[test_case(Rule::RedundantLiteralUnion, Path::new("PYI051.py"))] - #[test_case(Rule::RedundantLiteralUnion, Path::new("PYI051.pyi"))] - #[test_case(Rule::UnnecessaryTypeUnion, Path::new("PYI055.py"))] - #[test_case(Rule::UnnecessaryTypeUnion, Path::new("PYI055.pyi"))] + #[test_case(Rule::WrongTupleLengthVersionComparison, Path::new("PYI005.py"))] + #[test_case(Rule::WrongTupleLengthVersionComparison, Path::new("PYI005.pyi"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( @@ -116,15 +118,15 @@ mod tests { Ok(()) } - #[test_case(Path::new("PYI019.py"))] - #[test_case(Path::new("PYI019.pyi"))] - fn custom_type_var_return_type(path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", "PYI019", path.to_string_lossy()); + #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.py"))] + #[test_case(Rule::TypeAliasWithoutAnnotation, Path::new("PYI026.pyi"))] + fn py38(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("py38_{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( Path::new("flake8_pyi").join(path).as_path(), &settings::Settings { - target_version: PythonVersion::Py312, - ..settings::Settings::for_rules(vec![Rule::CustomTypeVarReturnType]) + target_version: PythonVersion::Py38, + ..settings::Settings::for_rule(rule_code) }, )?; assert_messages!(snapshot, diagnostics); diff --git a/crates/ruff/src/rules/flake8_pyi/rules/mod.rs b/crates/ruff/src/rules/flake8_pyi/rules/mod.rs index 98ed9c01d58a1..3fae99e33d7b2 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/mod.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/mod.rs @@ -21,6 +21,7 @@ pub(crate) use quoted_annotation_in_stub::*; pub(crate) use redundant_literal_union::*; pub(crate) use redundant_numeric_union::*; pub(crate) use simple_defaults::*; +use std::fmt; pub(crate) use str_or_repr_defined_in_stub::*; pub(crate) use string_or_bytes_too_long::*; pub(crate) use stub_body_multiple_statements::*; @@ -69,3 +70,26 @@ mod unrecognized_platform; mod unrecognized_version_info; mod unsupported_method_call_on_all; mod unused_private_type_definition; + +// TODO(charlie): Replace this with a common utility for selecting the appropriate source +// module for a given `typing` member. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum TypingModule { + Typing, + TypingExtensions, +} + +impl TypingModule { + fn as_str(self) -> &'static str { + match self { + TypingModule::Typing => "typing", + TypingModule::TypingExtensions => "typing_extensions", + } + } +} + +impl fmt::Display for TypingModule { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str(self.as_str()) + } +} diff --git a/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs index 4426760cd1ad6..355930bfa7311 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -1,17 +1,18 @@ +use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::{ self as ast, Arguments, Constant, Expr, Operator, ParameterWithDefault, Parameters, Ranged, Stmt, UnaryOp, }; - -use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix, Violation}; -use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::call_path::CallPath; use ruff_python_semantic::{ScopeKind, SemanticModel}; use ruff_source_file::Locator; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::registry::AsRule; +use crate::rules::flake8_pyi::rules::TypingModule; +use crate::settings::types::PythonVersion; #[violation] pub struct TypedArgumentDefaultInStub; @@ -124,6 +125,7 @@ impl Violation for UnassignedSpecialVariableInStub { /// ``` #[violation] pub struct TypeAliasWithoutAnnotation { + module: TypingModule, name: String, value: String, } @@ -131,12 +133,16 @@ pub struct TypeAliasWithoutAnnotation { impl AlwaysAutofixableViolation for TypeAliasWithoutAnnotation { #[derive_message_formats] fn message(&self) -> String { - let TypeAliasWithoutAnnotation { name, value } = self; - format!("Use `typing.TypeAlias` for type alias, e.g., `{name}: typing.TypeAlias = {value}`") + let TypeAliasWithoutAnnotation { + module, + name, + value, + } = self; + format!("Use `{module}.TypeAlias` for type alias, e.g., `{name}: TypeAlias = {value}`") } fn autofix_title(&self) -> String { - "Add `typing.TypeAlias` annotation".to_string() + "Add `TypeAlias` annotation".to_string() } } @@ -606,7 +612,7 @@ pub(crate) fn unassigned_special_variable_in_stub( )); } -/// PIY026 +/// PYI026 pub(crate) fn type_alias_without_annotation(checker: &mut Checker, value: &Expr, targets: &[Expr]) { let [target] = targets else { return; @@ -620,8 +626,15 @@ pub(crate) fn type_alias_without_annotation(checker: &mut Checker, value: &Expr, return; } + let module = if checker.settings.target_version >= PythonVersion::Py310 { + TypingModule::Typing + } else { + TypingModule::TypingExtensions + }; + let mut diagnostic = Diagnostic::new( TypeAliasWithoutAnnotation { + module, name: id.to_string(), value: checker.generator().expr(value), }, @@ -630,7 +643,7 @@ pub(crate) fn type_alias_without_annotation(checker: &mut Checker, value: &Expr, if checker.patch(diagnostic.kind.rule()) { diagnostic.try_set_fix(|| { let (import_edit, binding) = checker.importer().get_or_import_symbol( - &ImportRequest::import("typing", "TypeAlias"), + &ImportRequest::import(module.as_str(), "TypeAlias"), target.start(), checker.semantic(), )?; diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI026_PYI026.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI026_PYI026.pyi.snap index 075d94d02c900..b0399539580e3 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI026_PYI026.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI026_PYI026.pyi.snap @@ -1,7 +1,7 @@ --- source: crates/ruff/src/rules/flake8_pyi/mod.rs --- -PYI026.pyi:3:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `NewAny: typing.TypeAlias = Any` +PYI026.pyi:3:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `NewAny: TypeAlias = Any` | 1 | from typing import Literal, Any 2 | @@ -10,7 +10,7 @@ PYI026.pyi:3:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `NewAny: 4 | OptionalStr = typing.Optional[str] 5 | Foo = Literal["foo"] | - = help: Add `typing.TypeAlias` annotation + = help: Add `TypeAlias` annotation ℹ Suggested fix 1 |-from typing import Literal, Any @@ -22,7 +22,7 @@ PYI026.pyi:3:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `NewAny: 5 5 | Foo = Literal["foo"] 6 6 | IntOrStr = int | str -PYI026.pyi:4:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `OptionalStr: typing.TypeAlias = typing.Optional[str]` +PYI026.pyi:4:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `OptionalStr: TypeAlias = typing.Optional[str]` | 3 | NewAny = Any 4 | OptionalStr = typing.Optional[str] @@ -30,7 +30,7 @@ PYI026.pyi:4:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `Optiona 5 | Foo = Literal["foo"] 6 | IntOrStr = int | str | - = help: Add `typing.TypeAlias` annotation + = help: Add `TypeAlias` annotation ℹ Suggested fix 1 |-from typing import Literal, Any @@ -43,7 +43,7 @@ PYI026.pyi:4:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `Optiona 6 6 | IntOrStr = int | str 7 7 | AliasNone = None -PYI026.pyi:5:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `Foo: typing.TypeAlias = Literal["foo"]` +PYI026.pyi:5:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `Foo: TypeAlias = Literal["foo"]` | 3 | NewAny = Any 4 | OptionalStr = typing.Optional[str] @@ -52,7 +52,7 @@ PYI026.pyi:5:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `Foo: ty 6 | IntOrStr = int | str 7 | AliasNone = None | - = help: Add `typing.TypeAlias` annotation + = help: Add `TypeAlias` annotation ℹ Suggested fix 1 |-from typing import Literal, Any @@ -66,7 +66,7 @@ PYI026.pyi:5:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `Foo: ty 7 7 | AliasNone = None 8 8 | -PYI026.pyi:6:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `IntOrStr: typing.TypeAlias = int | str` +PYI026.pyi:6:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `IntOrStr: TypeAlias = int | str` | 4 | OptionalStr = typing.Optional[str] 5 | Foo = Literal["foo"] @@ -74,7 +74,7 @@ PYI026.pyi:6:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `IntOrSt | ^^^^^^^^ PYI026 7 | AliasNone = None | - = help: Add `typing.TypeAlias` annotation + = help: Add `TypeAlias` annotation ℹ Suggested fix 1 |-from typing import Literal, Any @@ -89,7 +89,7 @@ PYI026.pyi:6:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `IntOrSt 8 8 | 9 9 | NewAny: typing.TypeAlias = Any -PYI026.pyi:7:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `AliasNone: typing.TypeAlias = None` +PYI026.pyi:7:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `AliasNone: TypeAlias = None` | 5 | Foo = Literal["foo"] 6 | IntOrStr = int | str @@ -98,7 +98,7 @@ PYI026.pyi:7:1: PYI026 [*] Use `typing.TypeAlias` for type alias, e.g., `AliasNo 8 | 9 | NewAny: typing.TypeAlias = Any | - = help: Add `typing.TypeAlias` annotation + = help: Add `TypeAlias` annotation ℹ Suggested fix 1 |-from typing import Literal, Any diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.py.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.py.snap new file mode 100644 index 0000000000000..d1aa2e9116558 --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/flake8_pyi/mod.rs +--- + diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.pyi.snap new file mode 100644 index 0000000000000..a78ca465b9180 --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__py38_PYI026_PYI026.pyi.snap @@ -0,0 +1,117 @@ +--- +source: crates/ruff/src/rules/flake8_pyi/mod.rs +--- +PYI026.pyi:3:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g., `NewAny: TypeAlias = Any` + | +1 | from typing import Literal, Any +2 | +3 | NewAny = Any + | ^^^^^^ PYI026 +4 | OptionalStr = typing.Optional[str] +5 | Foo = Literal["foo"] + | + = help: Add `TypeAlias` annotation + +ℹ Suggested fix +1 1 | from typing import Literal, Any + 2 |+import typing_extensions +2 3 | +3 |-NewAny = Any + 4 |+NewAny: typing_extensions.TypeAlias = Any +4 5 | OptionalStr = typing.Optional[str] +5 6 | Foo = Literal["foo"] +6 7 | IntOrStr = int | str + +PYI026.pyi:4:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g., `OptionalStr: TypeAlias = typing.Optional[str]` + | +3 | NewAny = Any +4 | OptionalStr = typing.Optional[str] + | ^^^^^^^^^^^ PYI026 +5 | Foo = Literal["foo"] +6 | IntOrStr = int | str + | + = help: Add `TypeAlias` annotation + +ℹ Suggested fix +1 1 | from typing import Literal, Any + 2 |+import typing_extensions +2 3 | +3 4 | NewAny = Any +4 |-OptionalStr = typing.Optional[str] + 5 |+OptionalStr: typing_extensions.TypeAlias = typing.Optional[str] +5 6 | Foo = Literal["foo"] +6 7 | IntOrStr = int | str +7 8 | AliasNone = None + +PYI026.pyi:5:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g., `Foo: TypeAlias = Literal["foo"]` + | +3 | NewAny = Any +4 | OptionalStr = typing.Optional[str] +5 | Foo = Literal["foo"] + | ^^^ PYI026 +6 | IntOrStr = int | str +7 | AliasNone = None + | + = help: Add `TypeAlias` annotation + +ℹ Suggested fix +1 1 | from typing import Literal, Any + 2 |+import typing_extensions +2 3 | +3 4 | NewAny = Any +4 5 | OptionalStr = typing.Optional[str] +5 |-Foo = Literal["foo"] + 6 |+Foo: typing_extensions.TypeAlias = Literal["foo"] +6 7 | IntOrStr = int | str +7 8 | AliasNone = None +8 9 | + +PYI026.pyi:6:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g., `IntOrStr: TypeAlias = int | str` + | +4 | OptionalStr = typing.Optional[str] +5 | Foo = Literal["foo"] +6 | IntOrStr = int | str + | ^^^^^^^^ PYI026 +7 | AliasNone = None + | + = help: Add `TypeAlias` annotation + +ℹ Suggested fix +1 1 | from typing import Literal, Any + 2 |+import typing_extensions +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 8 | AliasNone = None +8 9 | +9 10 | NewAny: typing.TypeAlias = Any + +PYI026.pyi:7:1: PYI026 [*] Use `typing_extensions.TypeAlias` for type alias, e.g., `AliasNone: TypeAlias = None` + | +5 | Foo = Literal["foo"] +6 | IntOrStr = int | str +7 | AliasNone = None + | ^^^^^^^^^ PYI026 +8 | +9 | NewAny: typing.TypeAlias = Any + | + = help: Add `TypeAlias` annotation + +ℹ Suggested fix +1 1 | from typing import Literal, Any + 2 |+import typing_extensions +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 9 | +9 10 | NewAny: typing.TypeAlias = Any +10 11 | OptionalStr: TypeAlias = typing.Optional[str] + +