mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-18 17:40:37 +00:00
[flake8-pyi
] Implement PYI062
(duplicate-literal-member
) (#11269)
This commit is contained in:
parent
1a392d34e1
commit
56b4c47d74
12 changed files with 451 additions and 0 deletions
21
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py
vendored
Normal file
21
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
from typing import Literal
|
||||
import typing as t
|
||||
import typing_extensions
|
||||
|
||||
x: Literal[True, False, True, False] # PYI062 twice here
|
||||
|
||||
y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
|
||||
z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
|
||||
Literal[1, Literal[1]] # once
|
||||
Literal[1, 2, Literal[1, 2]] # twice
|
||||
Literal[1, Literal[1], Literal[1]] # twice
|
||||
Literal[1, Literal[2], Literal[2]] # once
|
||||
t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
||||
# Ensure issue is only raised once, even on nested literals
|
||||
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
n: Literal["No", "duplicates", "here", 1, "1"]
|
21
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.pyi
vendored
Normal file
21
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.pyi
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
from typing import Literal
|
||||
import typing as t
|
||||
import typing_extensions
|
||||
|
||||
x: Literal[True, False, True, False] # PY062 twice here
|
||||
|
||||
y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
|
||||
z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
|
||||
Literal[1, Literal[1]] # once
|
||||
Literal[1, 2, Literal[1, 2]] # twice
|
||||
Literal[1, Literal[1], Literal[1]] # twice
|
||||
Literal[1, Literal[2], Literal[2]] # once
|
||||
t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
||||
# Ensure issue is only raised once, even on nested literals
|
||||
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
n: Literal["No", "duplicates", "here", 1, "1"]
|
|
@ -97,6 +97,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||
}
|
||||
}
|
||||
|
||||
// Ex) Literal[...]
|
||||
if checker.enabled(Rule::DuplicateLiteralMember) {
|
||||
if !checker.semantic.in_nested_literal() {
|
||||
flake8_pyi::rules::duplicate_literal_member(checker, expr);
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::NeverUnion) {
|
||||
ruff::rules::never_union(checker, expr);
|
||||
}
|
||||
|
|
|
@ -809,6 +809,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Flake8Pyi, "056") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
|
||||
(Flake8Pyi, "058") => (RuleGroup::Stable, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod),
|
||||
(Flake8Pyi, "059") => (RuleGroup::Preview, rules::flake8_pyi::rules::GenericNotLastBaseClass),
|
||||
(Flake8Pyi, "062") => (RuleGroup::Preview, rules::flake8_pyi::rules::DuplicateLiteralMember),
|
||||
|
||||
// flake8-pytest-style
|
||||
(Flake8PytestStyle, "001") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle),
|
||||
|
|
|
@ -33,6 +33,8 @@ mod tests {
|
|||
#[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::DuplicateLiteralMember, Path::new("PYI062.py"))]
|
||||
#[test_case(Rule::DuplicateLiteralMember, Path::new("PYI062.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"))]
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::analyze::typing::traverse_literal;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for duplicate members in a `typing.Literal[]` slice.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Duplicate literal members are redundant and should be removed.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo: Literal["a", "b", "a"]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// foo: Literal["a", "b"]
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal)
|
||||
#[violation]
|
||||
pub struct DuplicateLiteralMember {
|
||||
duplicate_name: String,
|
||||
}
|
||||
|
||||
impl Violation for DuplicateLiteralMember {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Duplicate literal member `{}`", self.duplicate_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI062
|
||||
pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr) {
|
||||
let mut seen_nodes: HashSet<ComparableExpr<'_>, _> = FxHashSet::default();
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
// Adds a member to `literal_exprs` if it is a `Literal` annotation
|
||||
let mut check_for_duplicate_members = |expr: &'a Expr, _: &'a Expr| {
|
||||
// If we've already seen this literal member, raise a violation.
|
||||
if !seen_nodes.insert(expr.into()) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
DuplicateLiteralMember {
|
||||
duplicate_name: checker.generator().expr(expr),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Traverse the literal, collect all diagnostic members
|
||||
traverse_literal(&mut check_for_duplicate_members, checker.semantic(), expr);
|
||||
checker.diagnostics.append(&mut diagnostics);
|
||||
}
|
|
@ -6,6 +6,7 @@ pub(crate) use complex_assignment_in_stub::*;
|
|||
pub(crate) use complex_if_statement_in_stub::*;
|
||||
pub(crate) use custom_type_var_return_type::*;
|
||||
pub(crate) use docstring_in_stubs::*;
|
||||
pub(crate) use duplicate_literal_member::*;
|
||||
pub(crate) use duplicate_union_member::*;
|
||||
pub(crate) use ellipsis_in_non_empty_class_body::*;
|
||||
pub(crate) use exit_annotations::*;
|
||||
|
@ -45,6 +46,7 @@ mod complex_assignment_in_stub;
|
|||
mod complex_if_statement_in_stub;
|
||||
mod custom_type_var_return_type;
|
||||
mod docstring_in_stubs;
|
||||
mod duplicate_literal_member;
|
||||
mod duplicate_union_member;
|
||||
mod ellipsis_in_non_empty_class_body;
|
||||
mod exit_annotations;
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI062.py:5:25: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
5 | x: Literal[True, False, True, False] # PYI062 twice here
|
||||
| ^^^^ PYI062
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
|
|
||||
|
||||
PYI062.py:5:31: PYI062 Duplicate literal member `False`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
5 | x: Literal[True, False, True, False] # PYI062 twice here
|
||||
| ^^^^^ PYI062
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
|
|
||||
|
||||
PYI062.py:7:45: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
5 | x: Literal[True, False, True, False] # PYI062 twice here
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
| ^ PYI062
|
||||
8 |
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
|
|
||||
|
||||
PYI062.py:9:33: PYI062 Duplicate literal member `{1, 3, 5}`
|
||||
|
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
8 |
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
| ^^^^^^^ PYI062
|
||||
10 |
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
|
|
||||
|
||||
PYI062.py:11:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
10 |
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
| ^ PYI062
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
|
|
||||
|
||||
PYI062.py:12:23: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
| ^ PYI062
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
|
||||
PYI062.py:12:26: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
| ^ PYI062
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
|
||||
PYI062.py:13:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
| ^ PYI062
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
|
||||
PYI062.py:13:32: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
| ^ PYI062
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
|
||||
PYI062.py:14:32: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
| ^ PYI062
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
|
||||
PYI062.py:15:37: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
| ^ PYI062
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
|
||||
PYI062.py:16:30: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
| ^ PYI062
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
|
||||
PYI062.py:16:33: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
| ^ PYI062
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
|
||||
PYI062.py:19:46: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
| ^^^^ PYI062
|
||||
20 |
|
||||
21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI062.pyi:5:25: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
5 | x: Literal[True, False, True, False] # PY062 twice here
|
||||
| ^^^^ PYI062
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
|
|
||||
|
||||
PYI062.pyi:5:31: PYI062 Duplicate literal member `False`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
5 | x: Literal[True, False, True, False] # PY062 twice here
|
||||
| ^^^^^ PYI062
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
|
|
||||
|
||||
PYI062.pyi:7:45: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
5 | x: Literal[True, False, True, False] # PY062 twice here
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
| ^ PYI062
|
||||
8 |
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
|
|
||||
|
||||
PYI062.pyi:9:33: PYI062 Duplicate literal member `{1, 3, 5}`
|
||||
|
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
8 |
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
| ^^^^^^^ PYI062
|
||||
10 |
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
|
|
||||
|
||||
PYI062.pyi:11:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
10 |
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
| ^ PYI062
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
|
|
||||
|
||||
PYI062.pyi:12:23: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
| ^ PYI062
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
|
||||
PYI062.pyi:12:26: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
| ^ PYI062
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
|
||||
PYI062.pyi:13:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
| ^ PYI062
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
|
||||
PYI062.pyi:13:32: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
| ^ PYI062
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
|
||||
PYI062.pyi:14:32: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
| ^ PYI062
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
|
||||
PYI062.pyi:15:37: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
| ^ PYI062
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
|
||||
PYI062.pyi:16:30: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
| ^ PYI062
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
|
||||
PYI062.pyi:16:33: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
| ^ PYI062
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
|
||||
PYI062.pyi:19:46: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
| ^^^^ PYI062
|
||||
20 |
|
||||
21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
|
|
@ -418,6 +418,49 @@ where
|
|||
inner(func, semantic, expr, None);
|
||||
}
|
||||
|
||||
/// Traverse a "literal" type annotation, applying `func` to each literal member.
|
||||
///
|
||||
/// The function is called with each expression in the literal (excluding declarations of nested
|
||||
/// literals) and the parent expression.
|
||||
pub fn traverse_literal<'a, F>(func: &mut F, semantic: &SemanticModel, expr: &'a Expr)
|
||||
where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
fn inner<'a, F>(
|
||||
func: &mut F,
|
||||
semantic: &SemanticModel,
|
||||
expr: &'a Expr,
|
||||
parent: Option<&'a Expr>,
|
||||
) where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
// Ex) Literal[x, y]
|
||||
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||
if semantic.match_typing_expr(value, "Literal") {
|
||||
match &**slice {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
// Traverse each element of the tuple within the literal recursively to handle cases
|
||||
// such as `Literal[..., Literal[...]]
|
||||
for elt in elts {
|
||||
inner(func, semantic, elt, Some(expr));
|
||||
}
|
||||
}
|
||||
other => {
|
||||
inner(func, semantic, other, Some(expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Otherwise, call the function on expression, if it's not the top-level expression.
|
||||
if let Some(parent) = parent {
|
||||
func(expr, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner(func, semantic, expr, None);
|
||||
}
|
||||
|
||||
/// Abstraction for a type checker, conservatively checks for the intended type(s).
|
||||
pub trait TypeChecker {
|
||||
/// Check annotation expression to match the intended type(s).
|
||||
|
|
|
@ -1350,6 +1350,15 @@ impl<'a> SemanticModel<'a> {
|
|||
false
|
||||
}
|
||||
|
||||
/// Return `true` if the model is in a nested literal expression (e.g., the inner `Literal` in
|
||||
/// `Literal[Literal[int, str], float]`).
|
||||
pub fn in_nested_literal(&self) -> bool {
|
||||
// Ex) `Literal[Literal[int, str], float]`
|
||||
self.current_expression_grandparent()
|
||||
.and_then(Expr::as_subscript_expr)
|
||||
.is_some_and(|parent| self.match_typing_expr(&parent.value, "Literal"))
|
||||
}
|
||||
|
||||
/// Returns `true` if `left` and `right` are in the same branches of an `if`, `match`, or
|
||||
/// `try` statement.
|
||||
///
|
||||
|
|
2
ruff.schema.json
generated
2
ruff.schema.json
generated
|
@ -3574,6 +3574,8 @@
|
|||
"PYI056",
|
||||
"PYI058",
|
||||
"PYI059",
|
||||
"PYI06",
|
||||
"PYI062",
|
||||
"Q",
|
||||
"Q0",
|
||||
"Q00",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue