mirror of https://github.com/astral-sh/ruff
Implement PYI030: Unnecessary literal union (#5570)
Implements PYI030 as part of https://github.com/astral-sh/ruff/issues/848 > Union expressions should never have more than one Literal member, as Literal[1] | Literal[2] is semantically identical to Literal[1, 2]. Note we differ slightly from the flake8-pyi implementation: - We detect cases where there are parentheses or nested unions - We detect cases with mixed `Union` and `|` syntax - We use the same error message for all violations; flake8-pyi has two different messages - We retain the user's quoting style when displaying string literals; flake8-pyi uses single quotes - We warn on duplicates of the same literal `Literal[1] | Literal[1]`
This commit is contained in:
parent
60d318ddcf
commit
bb7303f867
|
|
@ -0,0 +1,24 @@
|
||||||
|
from typing import Literal
|
||||||
|
# Shouldn't emit for any cases in the non-stub file for compatibility with flake8-pyi.
|
||||||
|
# Note that this rule could be applied here in the future.
|
||||||
|
|
||||||
|
field1: Literal[1] # OK
|
||||||
|
field2: Literal[1] | Literal[2] # OK
|
||||||
|
|
||||||
|
def func1(arg1: Literal[1] | Literal[2]): # OK
|
||||||
|
print(arg1)
|
||||||
|
|
||||||
|
|
||||||
|
def func2() -> Literal[1] | Literal[2]: # OK
|
||||||
|
return "my Literal[1]ing"
|
||||||
|
|
||||||
|
|
||||||
|
field3: Literal[1] | Literal[2] | str # OK
|
||||||
|
field4: str | Literal[1] | Literal[2] # OK
|
||||||
|
field5: Literal[1] | str | Literal[2] # OK
|
||||||
|
field6: Literal[1] | bool | Literal[2] | str # OK
|
||||||
|
field7 = Literal[1] | Literal[2] # OK
|
||||||
|
field8: Literal[1] | (Literal[2] | str) # OK
|
||||||
|
field9: Literal[1] | (Literal[2] | str) # OK
|
||||||
|
field10: (Literal[1] | str) | Literal[2] # OK
|
||||||
|
field11: dict[Literal[1] | Literal[2], str] # OK
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
import typing
|
||||||
|
import typing_extensions
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# Shouldn't affect non-union field types.
|
||||||
|
field1: Literal[1] # OK
|
||||||
|
|
||||||
|
# Should emit for duplicate field types.
|
||||||
|
field2: Literal[1] | Literal[2] # Error
|
||||||
|
|
||||||
|
# Should emit for union types in arguments.
|
||||||
|
def func1(arg1: Literal[1] | Literal[2]): # Error
|
||||||
|
print(arg1)
|
||||||
|
|
||||||
|
|
||||||
|
# Should emit for unions in return types.
|
||||||
|
def func2() -> Literal[1] | Literal[2]: # Error
|
||||||
|
return "my Literal[1]ing"
|
||||||
|
|
||||||
|
|
||||||
|
# Should emit in longer unions, even if not directly adjacent.
|
||||||
|
field3: Literal[1] | Literal[2] | str # Error
|
||||||
|
field4: str | Literal[1] | Literal[2] # Error
|
||||||
|
field5: Literal[1] | str | Literal[2] # Error
|
||||||
|
field6: Literal[1] | bool | Literal[2] | str # Error
|
||||||
|
|
||||||
|
# Should emit for non-type unions.
|
||||||
|
field7 = Literal[1] | Literal[2] # Error
|
||||||
|
|
||||||
|
# Should emit for parenthesized unions.
|
||||||
|
field8: Literal[1] | (Literal[2] | str) # Error
|
||||||
|
|
||||||
|
# Should handle user parentheses when fixing.
|
||||||
|
field9: Literal[1] | (Literal[2] | str) # Error
|
||||||
|
field10: (Literal[1] | str) | Literal[2] # Error
|
||||||
|
|
||||||
|
# Should emit for union in generic parent type.
|
||||||
|
field11: dict[Literal[1] | Literal[2], str] # Error
|
||||||
|
|
||||||
|
# Should emit for unions with more than two cases
|
||||||
|
field12: Literal[1] | Literal[2] | Literal[3] # Error
|
||||||
|
field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
|
||||||
|
|
||||||
|
# Should emit for unions with more than two cases, even if not directly adjacent
|
||||||
|
field14: Literal[1] | Literal[2] | str | Literal[3] # Error
|
||||||
|
|
||||||
|
# Should emit for unions with mixed literal internal types
|
||||||
|
field15: Literal[1] | Literal["foo"] | Literal[True] # Error
|
||||||
|
|
||||||
|
# Shouldn't emit for duplicate field types with same value; covered by Y016
|
||||||
|
field16: Literal[1] | Literal[1] # OK
|
||||||
|
|
||||||
|
# Shouldn't emit if in new parent type
|
||||||
|
field17: Literal[1] | dict[Literal[2], str] # OK
|
||||||
|
|
||||||
|
# Shouldn't emit if not in a union parent
|
||||||
|
field18: dict[Literal[1], Literal[2]] # OK
|
||||||
|
|
||||||
|
# Should respect name of literal type used
|
||||||
|
field19: typing.Literal[1] | typing.Literal[2] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with newlines
|
||||||
|
field20: typing.Union[
|
||||||
|
Literal[
|
||||||
|
1 # test
|
||||||
|
],
|
||||||
|
Literal[2],
|
||||||
|
] # Error, newline and comment will not be emitted in message
|
||||||
|
|
||||||
|
# Should handle multiple unions with multiple members
|
||||||
|
field21: Literal[1, 2] | Literal[3, 4] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with `typing.Union` instead of `|`
|
||||||
|
field22: typing.Union[Literal[1], Literal[2]] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with `typing_extensions.Literal`
|
||||||
|
field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with nested `typing.Union`
|
||||||
|
field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
|
||||||
|
|
||||||
|
# Should emit in cases with mixed `typing.Union` and `|`
|
||||||
|
field25: typing.Union[Literal[1], Literal[2] | str] # Error
|
||||||
|
|
||||||
|
# Should emit only once in cases with multiple nested `typing.Union`
|
||||||
|
field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
|
||||||
|
|
@ -2189,6 +2189,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ex) Union[...]
|
||||||
|
if self.enabled(Rule::UnnecessaryLiteralUnion) {
|
||||||
|
let mut check = true;
|
||||||
|
|
||||||
|
// Avoid duplicate checks if the parent is an `Union[...]`
|
||||||
|
if let Some(Expr::Subscript(ast::ExprSubscript { value, .. })) =
|
||||||
|
self.semantic.expr_grandparent()
|
||||||
|
{
|
||||||
|
check = !self.semantic.match_typing_expr(value, "Union");
|
||||||
|
}
|
||||||
|
|
||||||
|
if check {
|
||||||
|
flake8_pyi::rules::unnecessary_literal_union(self, expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.semantic.match_typing_expr(value, "Literal") {
|
if self.semantic.match_typing_expr(value, "Literal") {
|
||||||
self.semantic.flags |= SemanticModelFlags::LITERAL;
|
self.semantic.flags |= SemanticModelFlags::LITERAL;
|
||||||
}
|
}
|
||||||
|
|
@ -3136,18 +3152,24 @@ where
|
||||||
if self.is_stub {
|
if self.is_stub {
|
||||||
if self.enabled(Rule::DuplicateUnionMember)
|
if self.enabled(Rule::DuplicateUnionMember)
|
||||||
&& self.semantic.in_type_definition()
|
&& self.semantic.in_type_definition()
|
||||||
&& self.semantic.expr_parent().map_or(true, |parent| {
|
// Avoid duplicate checks if the parent is an `|`
|
||||||
!matches!(
|
&& !matches!(
|
||||||
parent,
|
self.semantic.expr_parent(),
|
||||||
Expr::BinOp(ast::ExprBinOp {
|
Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..}))
|
||||||
op: Operator::BitOr,
|
|
||||||
..
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
})
|
|
||||||
{
|
{
|
||||||
flake8_pyi::rules::duplicate_union_member(self, expr);
|
flake8_pyi::rules::duplicate_union_member(self, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.enabled(Rule::UnnecessaryLiteralUnion)
|
||||||
|
// Avoid duplicate checks if the parent is an `|`
|
||||||
|
&& !matches!(
|
||||||
|
self.semantic.expr_parent(),
|
||||||
|
Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..}))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
flake8_pyi::rules::unnecessary_literal_union(self, expr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::UnaryOp(ast::ExprUnaryOp {
|
Expr::UnaryOp(ast::ExprUnaryOp {
|
||||||
|
|
|
||||||
|
|
@ -630,6 +630,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8Pyi, "024") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CollectionsNamedTuple),
|
(Flake8Pyi, "024") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CollectionsNamedTuple),
|
||||||
(Flake8Pyi, "025") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnaliasedCollectionsAbcSetImport),
|
(Flake8Pyi, "025") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnaliasedCollectionsAbcSetImport),
|
||||||
(Flake8Pyi, "029") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StrOrReprDefinedInStub),
|
(Flake8Pyi, "029") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StrOrReprDefinedInStub),
|
||||||
|
(Flake8Pyi, "030") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnnecessaryLiteralUnion),
|
||||||
(Flake8Pyi, "032") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::AnyEqNeAnnotation),
|
(Flake8Pyi, "032") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::AnyEqNeAnnotation),
|
||||||
(Flake8Pyi, "033") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeCommentInStub),
|
(Flake8Pyi, "033") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeCommentInStub),
|
||||||
(Flake8Pyi, "034") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonSelfReturnType),
|
(Flake8Pyi, "034") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonSelfReturnType),
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ mod tests {
|
||||||
#[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.pyi"))]
|
#[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.pyi"))]
|
||||||
#[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.py"))]
|
#[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.py"))]
|
||||||
#[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.pyi"))]
|
#[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::StubBodyMultipleStatements, Path::new("PYI048.py"))]
|
#[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.py"))]
|
||||||
#[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.pyi"))]
|
#[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.pyi"))]
|
||||||
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))]
|
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))]
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ pub(crate) use stub_body_multiple_statements::*;
|
||||||
pub(crate) use type_alias_naming::*;
|
pub(crate) use type_alias_naming::*;
|
||||||
pub(crate) use type_comment_in_stub::*;
|
pub(crate) use type_comment_in_stub::*;
|
||||||
pub(crate) use unaliased_collections_abc_set_import::*;
|
pub(crate) use unaliased_collections_abc_set_import::*;
|
||||||
|
pub(crate) use unnecessary_literal_union::*;
|
||||||
pub(crate) use unrecognized_platform::*;
|
pub(crate) use unrecognized_platform::*;
|
||||||
pub(crate) use unrecognized_version_info::*;
|
pub(crate) use unrecognized_version_info::*;
|
||||||
|
|
||||||
|
|
@ -49,5 +50,6 @@ mod stub_body_multiple_statements;
|
||||||
mod type_alias_naming;
|
mod type_alias_naming;
|
||||||
mod type_comment_in_stub;
|
mod type_comment_in_stub;
|
||||||
mod unaliased_collections_abc_set_import;
|
mod unaliased_collections_abc_set_import;
|
||||||
|
mod unnecessary_literal_union;
|
||||||
mod unrecognized_platform;
|
mod unrecognized_platform;
|
||||||
mod unrecognized_version_info;
|
mod unrecognized_version_info;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
use ruff_python_semantic::SemanticModel;
|
||||||
|
use rustpython_parser::ast::{self, Expr, Operator, Ranged};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for the presence of multiple literal types in a union.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Literal types accept multiple arguments and it is clearer to specify them
|
||||||
|
/// as a single literal.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// from typing import Literal
|
||||||
|
///
|
||||||
|
/// field: Literal[1] | Literal[2]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// from typing import Literal
|
||||||
|
///
|
||||||
|
/// field: Literal[1, 2]
|
||||||
|
/// ```
|
||||||
|
#[violation]
|
||||||
|
pub struct UnnecessaryLiteralUnion {
|
||||||
|
members: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for UnnecessaryLiteralUnion {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Multiple literal members in a union. Use a single literal, e.g. `Literal[{}]`",
|
||||||
|
self.members.join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PYI030
|
||||||
|
pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Expr) {
|
||||||
|
let mut literal_exprs = SmallVec::<[&Box<Expr>; 1]>::new();
|
||||||
|
|
||||||
|
// Adds a member to `literal_exprs` if it is a `Literal` annotation
|
||||||
|
let mut collect_literal_expr = |expr: &'a Expr| {
|
||||||
|
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||||
|
if checker.semantic().match_typing_expr(value, "Literal") {
|
||||||
|
literal_exprs.push(slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Traverse the union, collect all literal members
|
||||||
|
traverse_union(&mut collect_literal_expr, expr, checker.semantic());
|
||||||
|
|
||||||
|
// Raise a violation if more than one
|
||||||
|
if literal_exprs.len() > 1 {
|
||||||
|
let diagnostic = Diagnostic::new(
|
||||||
|
UnnecessaryLiteralUnion {
|
||||||
|
members: literal_exprs
|
||||||
|
.into_iter()
|
||||||
|
.map(|literal_expr| checker.locator.slice(literal_expr.range()).to_string())
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
expr.range(),
|
||||||
|
);
|
||||||
|
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverse a "union" type annotation, calling `func` on each expression in the union.
|
||||||
|
fn traverse_union<'a, F>(func: &mut F, expr: &'a Expr, semantic: &SemanticModel)
|
||||||
|
where
|
||||||
|
F: FnMut(&'a Expr),
|
||||||
|
{
|
||||||
|
// Ex) x | y
|
||||||
|
if let Expr::BinOp(ast::ExprBinOp {
|
||||||
|
op: Operator::BitOr,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
range: _,
|
||||||
|
}) = expr
|
||||||
|
{
|
||||||
|
// The union data structure usually looks like this:
|
||||||
|
// a | b | c -> (a | b) | c
|
||||||
|
//
|
||||||
|
// However, parenthesized expressions can coerce it into any structure:
|
||||||
|
// a | (b | c)
|
||||||
|
//
|
||||||
|
// So we have to traverse both branches in order (left, then right), to report members
|
||||||
|
// in the order they appear in the source code.
|
||||||
|
|
||||||
|
// Traverse the left then right arms
|
||||||
|
traverse_union(func, left, semantic);
|
||||||
|
traverse_union(func, right, semantic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ex) Union[x, y]
|
||||||
|
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||||
|
if semantic.match_typing_expr(value, "Union") {
|
||||||
|
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||||
|
// Traverse each element of the tuple within the union recursively to handle cases
|
||||||
|
// such as `Union[..., Union[...]]
|
||||||
|
elts.iter()
|
||||||
|
.for_each(|elt| traverse_union(func, elt, semantic));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, call the function on expression
|
||||||
|
func(expr);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
---
|
||||||
|
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||||
|
---
|
||||||
|
PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
8 | # Should emit for duplicate field types.
|
||||||
|
9 | field2: Literal[1] | Literal[2] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
10 |
|
||||||
|
11 | # Should emit for union types in arguments.
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
11 | # Should emit for union types in arguments.
|
||||||
|
12 | def func1(arg1: Literal[1] | Literal[2]): # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
13 | print(arg1)
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
16 | # Should emit for unions in return types.
|
||||||
|
17 | def func2() -> Literal[1] | Literal[2]: # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
18 | return "my Literal[1]ing"
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
21 | # Should emit in longer unions, even if not directly adjacent.
|
||||||
|
22 | field3: Literal[1] | Literal[2] | str # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
23 | field4: str | Literal[1] | Literal[2] # Error
|
||||||
|
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
21 | # Should emit in longer unions, even if not directly adjacent.
|
||||||
|
22 | field3: Literal[1] | Literal[2] | str # Error
|
||||||
|
23 | field4: str | Literal[1] | Literal[2] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||||
|
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
22 | field3: Literal[1] | Literal[2] | str # Error
|
||||||
|
23 | field4: str | Literal[1] | Literal[2] # Error
|
||||||
|
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
23 | field4: str | Literal[1] | Literal[2] # Error
|
||||||
|
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||||
|
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
26 |
|
||||||
|
27 | # Should emit for non-type unions.
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
27 | # Should emit for non-type unions.
|
||||||
|
28 | field7 = Literal[1] | Literal[2] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
29 |
|
||||||
|
30 | # Should emit for parenthesized unions.
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
30 | # Should emit for parenthesized unions.
|
||||||
|
31 | field8: Literal[1] | (Literal[2] | str) # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
32 |
|
||||||
|
33 | # Should handle user parentheses when fixing.
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
33 | # Should handle user parentheses when fixing.
|
||||||
|
34 | field9: Literal[1] | (Literal[2] | str) # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
35 | field10: (Literal[1] | str) | Literal[2] # Error
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
33 | # Should handle user parentheses when fixing.
|
||||||
|
34 | field9: Literal[1] | (Literal[2] | str) # Error
|
||||||
|
35 | field10: (Literal[1] | str) | Literal[2] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
36 |
|
||||||
|
37 | # Should emit for union in generic parent type.
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
37 | # Should emit for union in generic parent type.
|
||||||
|
38 | field11: dict[Literal[1] | Literal[2], str] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
39 |
|
||||||
|
40 | # Should emit for unions with more than two cases
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
||||||
|
|
|
||||||
|
40 | # Should emit for unions with more than two cases
|
||||||
|
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
||||||
|
|
|
||||||
|
40 | # Should emit for unions with more than two cases
|
||||||
|
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
|
||||||
|
42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
43 |
|
||||||
|
44 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
||||||
|
|
|
||||||
|
44 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||||
|
45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
46 |
|
||||||
|
47 | # Should emit for unions with mixed literal internal types
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
|
||||||
|
|
|
||||||
|
47 | # Should emit for unions with mixed literal internal types
|
||||||
|
48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
49 |
|
||||||
|
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
|
||||||
|
|
|
||||||
|
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
|
||||||
|
51 | field16: Literal[1] | Literal[1] # OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
52 |
|
||||||
|
53 | # Shouldn't emit if in new parent type
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
59 | # Should respect name of literal type used
|
||||||
|
60 | field19: typing.Literal[1] | typing.Literal[2] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
61 |
|
||||||
|
62 | # Should emit in cases with newlines
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
62 | # Should emit in cases with newlines
|
||||||
|
63 | field20: typing.Union[
|
||||||
|
| __________^
|
||||||
|
64 | | Literal[
|
||||||
|
65 | | 1 # test
|
||||||
|
66 | | ],
|
||||||
|
67 | | Literal[2],
|
||||||
|
68 | | ] # Error, newline and comment will not be emitted in message
|
||||||
|
| |_^ PYI030
|
||||||
|
69 |
|
||||||
|
70 | # Should handle multiple unions with multiple members
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
||||||
|
|
|
||||||
|
70 | # Should handle multiple unions with multiple members
|
||||||
|
71 | field21: Literal[1, 2] | Literal[3, 4] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
72 |
|
||||||
|
73 | # Should emit in cases with `typing.Union` instead of `|`
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
73 | # Should emit in cases with `typing.Union` instead of `|`
|
||||||
|
74 | field22: typing.Union[Literal[1], Literal[2]] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
75 |
|
||||||
|
76 | # Should emit in cases with `typing_extensions.Literal`
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
76 | # Should emit in cases with `typing_extensions.Literal`
|
||||||
|
77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
78 |
|
||||||
|
79 | # Should emit in cases with nested `typing.Union`
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
79 | # Should emit in cases with nested `typing.Union`
|
||||||
|
80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
81 |
|
||||||
|
82 | # Should emit in cases with mixed `typing.Union` and `|`
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
||||||
|
|
|
||||||
|
82 | # Should emit in cases with mixed `typing.Union` and `|`
|
||||||
|
83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
84 |
|
||||||
|
85 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||||
|
|
|
||||||
|
|
||||||
|
PYI030.pyi:86:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
||||||
|
|
|
||||||
|
85 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||||
|
86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2346,6 +2346,7 @@
|
||||||
"PYI025",
|
"PYI025",
|
||||||
"PYI029",
|
"PYI029",
|
||||||
"PYI03",
|
"PYI03",
|
||||||
|
"PYI030",
|
||||||
"PYI032",
|
"PYI032",
|
||||||
"PYI033",
|
"PYI033",
|
||||||
"PYI034",
|
"PYI034",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue