Implement iter(), len() and is_empty() for all display-literal AST nodes (#12807)

This commit is contained in:
Alex Waygood 2024-08-12 11:39:28 +01:00 committed by GitHub
parent a99a45868c
commit aa0db338d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 304 additions and 240 deletions

View file

@ -1290,8 +1290,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
let Keyword { arg, value, .. } = keyword;
match (arg.as_ref(), value) {
// Ex) NamedTuple("a", **{"a": int})
(None, Expr::Dict(ast::ExprDict { items, .. })) => {
for ast::DictItem { key, value } in items {
(None, Expr::Dict(dict)) => {
for ast::DictItem { key, value } in dict {
if let Some(key) = key.as_ref() {
self.visit_non_type_definition(key);
self.visit_type_definition(value);

View file

@ -151,16 +151,15 @@ pub(crate) fn add_to_dunder_all<'a>(
stylist: &Stylist,
) -> Vec<Edit> {
let (insertion_point, export_prefix_length) = match expr {
Expr::List(ExprList { elts, range, .. }) => (
elts.last()
.map_or(range.end() - "]".text_len(), Ranged::end),
Expr::List(ExprList { elts, .. }) => (
elts.last().map_or(expr.end() - "]".text_len(), Ranged::end),
elts.len(),
),
Expr::Tuple(tup) if tup.parenthesized => (
tup.elts
.last()
.map_or(tup.end() - ")".text_len(), Ranged::end),
tup.elts.len(),
tup.len(),
),
Expr::Tuple(tup) if !tup.parenthesized => (
tup.elts
@ -168,7 +167,7 @@ pub(crate) fn add_to_dunder_all<'a>(
.expect("unparenthesized empty tuple is not possible")
.range()
.end(),
tup.elts.len(),
tup.len(),
),
_ => {
// we don't know how to insert into this expression

View file

@ -122,17 +122,15 @@ fn is_identical_types(
return_value: &Expr,
semantic: &SemanticModel,
) -> bool {
if let (Some(response_mode_name_expr), Some(return_value_name_expr)) = (
response_model_arg.as_name_expr(),
return_value.as_name_expr(),
) {
if let (Expr::Name(response_mode_name_expr), Expr::Name(return_value_name_expr)) =
(response_model_arg, return_value)
{
return semantic.resolve_name(response_mode_name_expr)
== semantic.resolve_name(return_value_name_expr);
}
if let (Some(response_mode_subscript), Some(return_value_subscript)) = (
response_model_arg.as_subscript_expr(),
return_value.as_subscript_expr(),
) {
if let (Expr::Subscript(response_mode_subscript), Expr::Subscript(return_value_subscript)) =
(response_model_arg, return_value)
{
return is_identical_types(
&response_mode_subscript.value,
&return_value_subscript.value,
@ -143,15 +141,13 @@ fn is_identical_types(
semantic,
);
}
if let (Some(response_mode_tuple), Some(return_value_tuple)) = (
response_model_arg.as_tuple_expr(),
return_value.as_tuple_expr(),
) {
return response_mode_tuple.elts.len() == return_value_tuple.elts.len()
if let (Expr::Tuple(response_mode_tuple), Expr::Tuple(return_value_tuple)) =
(response_model_arg, return_value)
{
return response_mode_tuple.len() == return_value_tuple.len()
&& response_mode_tuple
.elts
.iter()
.zip(return_value_tuple.elts.iter())
.zip(return_value_tuple)
.all(|(x, y)| is_identical_types(x, y, semantic));
}
false

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprDict, ExprList};
use ruff_python_ast::{self as ast, Expr, ExprAttribute};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@ -65,8 +65,8 @@ fn is_call_insecure(call: &ast::ExprCall) -> bool {
if let Some(argument) = call.arguments.find_argument(argument_name, position) {
match argument_name {
"select" => match argument {
Expr::Dict(ExprDict { items, .. }) => {
if items.iter().any(|ast::DictItem { key, value }| {
Expr::Dict(dict) => {
if dict.iter().any(|ast::DictItem { key, value }| {
key.as_ref()
.is_some_and(|key| !key.is_string_literal_expr())
|| !value.is_string_literal_expr()
@ -77,8 +77,8 @@ fn is_call_insecure(call: &ast::ExprCall) -> bool {
_ => return true,
},
"where" | "tables" => match argument {
Expr::List(ExprList { elts, .. }) => {
if !elts.iter().all(Expr::is_string_literal_expr) {
Expr::List(list) => {
if !list.iter().all(Expr::is_string_literal_expr) {
return true;
}
}

View file

@ -530,11 +530,11 @@ fn is_partial_path(expr: &Expr) -> bool {
/// subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
/// ```
fn is_wildcard_command(expr: &Expr) -> bool {
if let Expr::List(ast::ExprList { elts, .. }) = expr {
if let Expr::List(list) = expr {
let mut has_star = false;
let mut has_command = false;
for elt in elts {
if let Some(text) = string_literal(elt) {
for item in list {
if let Some(text) = string_literal(item) {
has_star |= text.contains('*');
has_command |= text.contains("chown")
|| text.contains("chmod")

View file

@ -49,16 +49,16 @@ impl Violation for DuplicateValue {
/// B033
pub(crate) fn duplicate_value(checker: &mut Checker, set: &ast::ExprSet) {
let mut seen_values: FxHashSet<ComparableExpr> = FxHashSet::default();
for (index, elt) in set.elts.iter().enumerate() {
if elt.is_literal_expr() {
let comparable_value: ComparableExpr = elt.into();
for (index, value) in set.iter().enumerate() {
if value.is_literal_expr() {
let comparable_value = ComparableExpr::from(value);
if !seen_values.insert(comparable_value) {
let mut diagnostic = Diagnostic::new(
DuplicateValue {
value: checker.generator().expr(elt),
value: checker.generator().expr(value),
},
elt.range(),
value.range(),
);
diagnostic.try_set_fix(|| {
@ -73,7 +73,7 @@ pub(crate) fn duplicate_value(checker: &mut Checker, set: &ast::ExprSet) {
/// Remove the member at the given index from the [`ast::ExprSet`].
fn remove_member(set: &ast::ExprSet, index: usize, source: &str) -> Result<Edit> {
if index < set.elts.len() - 1 {
if index < set.len() - 1 {
// Case 1: the expression is _not_ the last node, so delete from the start of the
// expression to the end of the subsequent comma.
// Ex) Delete `"a"` in `{"a", "b", "c"}`.

View file

@ -315,15 +315,15 @@ pub(crate) fn reuse_of_groupby_generator(
let Expr::Call(ast::ExprCall { func, .. }) = &iter else {
return;
};
let Expr::Tuple(ast::ExprTuple { elts, .. }) = target else {
let Expr::Tuple(tuple) = target else {
// Ignore any `groupby()` invocation that isn't unpacked
return;
};
if elts.len() != 2 {
if tuple.len() != 2 {
return;
}
// We have an invocation of groupby which is a simple unpacking
let Expr::Name(ast::ExprName { id: group_name, .. }) = &elts[1] else {
let Expr::Name(ast::ExprName { id: group_name, .. }) = &tuple.elts[1] else {
return;
};
// Check if the function call is `itertools.groupby`

View file

@ -72,7 +72,7 @@ pub(crate) fn static_key_dict_comprehension(checker: &mut Checker, dict_comp: &a
/// comprehension.
fn is_constant(key: &Expr, names: &FxHashMap<&str, &ast::ExprName>) -> bool {
match key {
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().all(|elt| is_constant(elt, names)),
Expr::Tuple(tuple) => tuple.iter().all(|elem| is_constant(elem, names)),
Expr::Name(ast::ExprName { id, .. }) => !names.contains_key(id.as_str()),
Expr::Attribute(ast::ExprAttribute { value, .. }) => is_constant(value, names),
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {

View file

@ -61,13 +61,13 @@ pub(crate) fn unnecessary_generator_dict(
let Expr::Generator(ast::ExprGenerator { elt, .. }) = argument else {
return;
};
let Expr::Tuple(ast::ExprTuple { elts, .. }) = elt.as_ref() else {
let Expr::Tuple(tuple) = &**elt else {
return;
};
if elts.len() != 2 {
if tuple.len() != 2 {
return;
}
if elts.iter().any(Expr::is_starred_expr) {
if tuple.iter().any(Expr::is_starred_expr) {
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range());

View file

@ -62,10 +62,10 @@ pub(crate) fn unnecessary_list_comprehension_dict(
let Expr::ListComp(ast::ExprListComp { elt, .. }) = argument else {
return;
};
let Expr::Tuple(ast::ExprTuple { elts, .. }) = elt.as_ref() else {
let Expr::Tuple(tuple) = &**elt else {
return;
};
if elts.len() != 2 {
if tuple.len() != 2 {
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionDict, expr.range());

View file

@ -74,7 +74,7 @@ pub(crate) fn unnecessary_literal_dict(
// Accept `dict((1, 2), ...))` `dict([(1, 2), ...])`.
if !elts
.iter()
.all(|elt| matches!(&elt, Expr::Tuple(ast::ExprTuple { elts, .. }) if elts.len() == 2))
.all(|elt| matches!(&elt, Expr::Tuple(tuple) if tuple.len() == 2))
{
return;
}

View file

@ -163,8 +163,8 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
elts: words
.iter()
.flat_map(|value| {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = value {
Left(elts.iter())
if let Expr::Tuple(tuple) = value {
Left(tuple.iter())
} else {
Right(iter::once(*value))
}

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, ExprLambda};
use ruff_python_ast::{Expr, ExprLambda};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_diagnostics::{FixAvailability, Violation};
@ -70,8 +70,8 @@ pub(crate) fn reimplemented_container_builtin(checker: &mut Checker, expr: &Expr
}
let container = match &**body {
Expr::List(ast::ExprList { elts, .. }) if elts.is_empty() => Container::List,
Expr::Dict(ast::ExprDict { items, .. }) if items.is_empty() => Container::Dict,
Expr::List(list) if list.is_empty() => Container::List,
Expr::Dict(dict) if dict.is_empty() => Container::Dict,
_ => return,
};
let mut diagnostic = Diagnostic::new(ReimplementedContainerBuiltin { container }, expr.range());

View file

@ -87,13 +87,13 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal
.iter_keys()
.filter_map(|key| key.and_then(as_kwarg))
.collect();
if kwargs.len() != dict.items.len() {
if kwargs.len() != dict.len() {
continue;
}
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range());
if dict.items.is_empty() {
if dict.is_empty() {
diagnostic.try_set_fix(|| {
remove_argument(
keyword,

View file

@ -49,7 +49,7 @@ impl Violation for UnnecessarySpread {
pub(crate) fn unnecessary_spread(checker: &mut Checker, dict: &ast::ExprDict) {
// The first "end" is the start of the dictionary, immediately following the open bracket.
let mut prev_end = dict.start() + TextSize::from(1);
for ast::DictItem { key, value } in &dict.items {
for ast::DictItem { key, value } in dict {
if key.is_none() {
// We only care about when the key is None which indicates a spread `**`
// inside a dict.

View file

@ -162,12 +162,11 @@ pub(crate) fn bad_generator_return_type(
// - if not, don't emit the diagnostic
let yield_type_info = match returns {
ast::Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() {
ast::Expr::Tuple(slice_tuple @ ast::ExprTuple { .. }) => {
ast::Expr::Tuple(slice_tuple) => {
if !slice_tuple
.elts
.iter()
.skip(1)
.all(|elt| is_any_or_none(elt, semantic))
.all(|element| is_any_or_none(element, semantic))
{
return;
}

View file

@ -67,8 +67,8 @@ pub(crate) fn redundant_literal_union<'a>(checker: &mut Checker, union: &'a Expr
let mut func = |expr: &'a Expr, _parent: &'a Expr| {
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if checker.semantic().match_typing_expr(value, "Literal") {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
typing_literal_exprs.extend(elts.iter());
if let Expr::Tuple(tuple) = &**slice {
typing_literal_exprs.extend(tuple);
} else {
typing_literal_exprs.push(slice);
}

View file

@ -298,10 +298,10 @@ fn is_valid_default_value_with_annotation(
.iter()
.all(|e| is_valid_default_value_with_annotation(e, false, locator, semantic));
}
Expr::Dict(ast::ExprDict { items, range: _ }) => {
Expr::Dict(dict) => {
return allow_container
&& items.len() <= 10
&& items.iter().all(|ast::DictItem { key, value }| {
&& dict.len() <= 10
&& dict.iter().all(|ast::DictItem { key, value }| {
key.as_ref().is_some_and(|key| {
is_valid_default_value_with_annotation(key, false, locator, semantic)
}) && is_valid_default_value_with_annotation(value, false, locator, semantic)

View file

@ -70,19 +70,15 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
literal_subscript = Some(value.as_ref());
}
let slice = &**slice;
// flatten already-unioned literals to later union again
if let Expr::Tuple(ast::ExprTuple {
elts,
range: _,
ctx: _,
parenthesized: _,
}) = slice.as_ref()
{
for expr in elts {
literal_exprs.push(expr);
if let Expr::Tuple(tuple) = slice {
for item in tuple {
literal_exprs.push(item);
}
} else {
literal_exprs.push(slice.as_ref());
literal_exprs.push(slice);
}
}
} else {

View file

@ -181,7 +181,7 @@ fn version_check(
}
// Tuple comparison, e.g., `sys.version_info == (3, 4)`.
let Expr::Tuple(ast::ExprTuple { elts, .. }) = comparator else {
let Expr::Tuple(tuple) = comparator else {
if checker.enabled(Rule::UnrecognizedVersionInfoCheck) {
checker
.diagnostics
@ -190,7 +190,7 @@ fn version_check(
return;
};
if !elts.iter().all(is_int_constant) {
if !tuple.iter().all(is_int_constant) {
// All tuple elements must be integers, e.g., `sys.version_info == (3, 4)` instead of
// `sys.version_info == (3.0, 4)`.
if checker.enabled(Rule::UnrecognizedVersionInfoCheck) {
@ -198,7 +198,7 @@ fn version_check(
.diagnostics
.push(Diagnostic::new(UnrecognizedVersionInfoCheck, test.range()));
}
} else if elts.len() > 2 {
} else if tuple.len() > 2 {
// Must compare against major and minor version only, e.g., `sys.version_info == (3, 4)`
// instead of `sys.version_info == (3, 4, 0)`.
if checker.enabled(Rule::PatchVersionComparison) {
@ -216,7 +216,7 @@ fn version_check(
_ => return,
};
if elts.len() != expected_length {
if tuple.len() != expected_length {
checker.diagnostics.push(Diagnostic::new(
WrongTupleLengthVersionComparison { expected_length },
test.range(),

View file

@ -416,9 +416,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
if elts.len() == 1 {
if let Some(first) = elts.first() {
handle_single_name(checker, expr, first);
}
handle_single_name(checker, expr, &elts[0]);
} else {
match names_type {
types::ParametrizeNameType::Tuple => {}
@ -462,9 +460,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
}
Expr::List(ast::ExprList { elts, .. }) => {
if elts.len() == 1 {
if let Some(first) = elts.first() {
handle_single_name(checker, expr, first);
}
handle_single_name(checker, expr, &elts[0]);
} else {
match names_type {
types::ParametrizeNameType::List => {}

View file

@ -411,8 +411,8 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
elts: types
.iter()
.flat_map(|value| {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = value {
Left(elts.iter())
if let Expr::Tuple(tuple) = value {
Left(tuple.iter())
} else {
Right(iter::once(*value))
}
@ -722,8 +722,7 @@ fn get_short_circuit_edit(
generator.expr(expr)
};
Edit::range_replacement(
if matches!(expr, Expr::Tuple(ast::ExprTuple { elts, ctx: _, range: _, parenthesized: _}) if !elts.is_empty())
{
if matches!(expr, Expr::Tuple(tuple) if !tuple.is_empty()) {
format!("({content})")
} else {
content

View file

@ -91,18 +91,18 @@ impl From<&Expr> for ConstantLikelihood {
ConstantLikelihood::from_identifier(attr)
}
Expr::Name(ast::ExprName { id, .. }) => ConstantLikelihood::from_identifier(id),
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts
Expr::Tuple(tuple) => tuple
.iter()
.map(ConstantLikelihood::from)
.min()
.unwrap_or(ConstantLikelihood::Definitely),
Expr::List(ast::ExprList { elts, .. }) => elts
Expr::List(list) => list
.iter()
.map(ConstantLikelihood::from)
.min()
.unwrap_or(ConstantLikelihood::Definitely),
Expr::Dict(ast::ExprDict { items, .. }) => {
if items.is_empty() {
Expr::Dict(dict) => {
if dict.is_empty() {
ConstantLikelihood::Definitely
} else {
ConstantLikelihood::Probably

View file

@ -102,16 +102,16 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
};
match target {
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
if !elts
Expr::Tuple(tuple) => {
if !tuple
.iter()
.any(|elt| ComparableExpr::from(slice) == ComparableExpr::from(elt))
.any(|element| ComparableExpr::from(slice) == ComparableExpr::from(element))
{
return;
}
if !elts
if !tuple
.iter()
.any(|elt| ComparableExpr::from(value) == ComparableExpr::from(elt))
.any(|element| ComparableExpr::from(value) == ComparableExpr::from(element))
{
return;
}
@ -128,7 +128,7 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
}
// Exclude non-dictionary value.
let Some(name) = subscript_value.as_name_expr() else {
let Expr::Name(name) = &**subscript_value else {
return;
};
let Some(binding) = checker

View file

@ -613,7 +613,7 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> {
};
if let ast::Expr::Tuple(tuple) = exceptions {
for exception in &tuple.elts {
for exception in tuple {
maybe_store_exception(exception);
}
} else {

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_ast::{Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -39,8 +39,8 @@ impl Violation for AssertTuple {
/// F631
pub(crate) fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &test {
if !elts.is_empty() {
if let Expr::Tuple(tuple) = &test {
if !tuple.is_empty() {
checker
.diagnostics
.push(Diagnostic::new(AssertTuple, stmt.range()));

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, StmtIf};
use ruff_python_ast::{Expr, StmtIf};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -41,10 +41,10 @@ impl Violation for IfTuple {
/// F634
pub(crate) fn if_tuple(checker: &mut Checker, stmt_if: &StmtIf) {
for branch in if_elif_branches(stmt_if) {
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &branch.test else {
let Expr::Tuple(tuple) = &branch.test else {
continue;
};
if elts.is_empty() {
if tuple.is_empty() {
continue;
}
checker

View file

@ -130,10 +130,10 @@ impl Violation for MultiValueRepeatedKeyVariable {
pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) {
// Generate a map from key to (index, value).
let mut seen: FxHashMap<ComparableExpr, FxHashSet<ComparableExpr>> =
FxHashMap::with_capacity_and_hasher(dict.items.len(), FxBuildHasher);
FxHashMap::with_capacity_and_hasher(dict.len(), FxBuildHasher);
// Detect duplicate keys.
for (i, ast::DictItem { key, value }) in dict.items.iter().enumerate() {
for (i, ast::DictItem { key, value }) in dict.iter().enumerate() {
let Some(key) = key else {
continue;
};

View file

@ -690,10 +690,10 @@ pub(crate) fn percent_format_positional_count_mismatch(
return;
}
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = right {
if let Expr::Tuple(tuple) = right {
let mut found = 0;
for elt in elts {
if elt.is_starred_expr() {
for element in tuple {
if element.is_starred_expr() {
return;
}
found += 1;

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Expr, ExprTuple};
use ruff_python_ast::{Expr, Stmt};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@ -49,15 +49,15 @@ impl AlwaysFixableViolation for DictIterMissingItems {
}
pub(crate) fn dict_iter_missing_items(checker: &mut Checker, target: &Expr, iter: &Expr) {
let Expr::Tuple(ExprTuple { elts, .. }) = target else {
let Expr::Tuple(tuple) = target else {
return;
};
if elts.len() != 2 {
if tuple.len() != 2 {
return;
};
let Some(name) = iter.as_name_expr() else {
let Expr::Name(name) = iter else {
return;
};
@ -91,20 +91,15 @@ fn is_dict_key_tuple_with_two_elements(binding: &Binding, semantic: &SemanticMod
return false;
};
let Some(assign_stmt) = statement.as_assign_stmt() else {
let Stmt::Assign(assign_stmt) = statement else {
return false;
};
let Some(dict_expr) = assign_stmt.value.as_dict_expr() else {
let Expr::Dict(dict_expr) = &*assign_stmt.value else {
return false;
};
dict_expr.iter_keys().all(|elt| {
elt.is_some_and(|x| {
if let Expr::Tuple(ExprTuple { elts, .. }) = x {
return elts.len() == 2;
}
false
})
})
dict_expr
.iter_keys()
.all(|key| matches!(key, Some(Expr::Tuple(tuple)) if tuple.len() == 2))
}

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::Expr;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@ -42,17 +42,17 @@ impl AlwaysFixableViolation for IterationOverSet {
/// PLC0208
pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) {
let Expr::Set(ast::ExprSet { elts, .. }) = expr else {
let Expr::Set(set) = expr else {
return;
};
if elts.iter().any(Expr::is_starred_expr) {
if set.iter().any(Expr::is_starred_expr) {
return;
}
let mut diagnostic = Diagnostic::new(IterationOverSet, expr.range());
let tuple = if let [elt] = elts.as_slice() {
let tuple = if let [elt] = set.elts.as_slice() {
let elt = checker.locator().slice(elt);
format!("({elt},)")
} else {

View file

@ -52,8 +52,8 @@ pub(crate) fn redeclared_assigned_name(checker: &mut Checker, targets: &Vec<Expr
fn check_expr(checker: &mut Checker, expr: &Expr, names: &mut Vec<Name>) {
match expr {
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
for target in elts {
Expr::Tuple(tuple) => {
for target in tuple {
check_expr(checker, target, names);
}
}

View file

@ -71,13 +71,10 @@ pub(crate) fn self_annotated_assignment(checker: &mut Checker, assign: &ast::Stm
fn visit_assignments(left: &Expr, right: &Expr, diagnostics: &mut Vec<Diagnostic>) {
match (left, right) {
(
Expr::Tuple(ast::ExprTuple { elts: lhs_elts, .. }),
Expr::Tuple(ast::ExprTuple { elts: rhs_elts, .. }),
) if lhs_elts.len() == rhs_elts.len() => lhs_elts
(Expr::Tuple(lhs), Expr::Tuple(rhs)) if lhs.len() == rhs.len() => lhs
.iter()
.zip(rhs_elts.iter())
.for_each(|(lhs, rhs)| visit_assignments(lhs, rhs, diagnostics)),
.zip(rhs)
.for_each(|(lhs_elem, rhs_elem)| visit_assignments(lhs_elem, rhs_elem, diagnostics)),
(
Expr::Name(ast::ExprName { id: lhs_name, .. }),
Expr::Name(ast::ExprName { id: rhs_name, .. }),

View file

@ -169,14 +169,15 @@ fn create_field_assignment_stmt(field: Name, annotation: &Expr) -> Stmt {
/// Create a list of field assignments from the `NamedTuple` fields argument.
fn create_fields_from_fields_arg(fields: &Expr) -> Option<Vec<Stmt>> {
let ast::ExprList { elts, .. } = fields.as_list_expr()?;
if elts.is_empty() {
let fields = fields.as_list_expr()?;
if fields.is_empty() {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
});
Some(vec![node])
} else {
elts.iter()
fields
.iter()
.map(|field| {
let ast::ExprTuple { elts, .. } = field.as_tuple_expr()?;
let [field, annotation] = elts.as_slice() else {

View file

@ -98,22 +98,20 @@ fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&E
if semantic.has_builtin_binding("OSError") {
// Filter out any `OSErrors` aliases.
let mut remaining: Vec<Expr> = tuple
.elts
.iter()
.filter_map(|elt| {
if aliases.contains(&elt) {
.filter_map(|element| {
if aliases.contains(&element) {
None
} else {
Some(elt.clone())
Some(element.clone())
}
})
.collect();
// If `OSError` itself isn't already in the tuple, add it.
if tuple
.elts
.iter()
.all(|elt| !semantic.match_builtin_expr(elt, "OSError"))
.all(|elem| !semantic.match_builtin_expr(elem, "OSError"))
{
let node = ast::ExprName {
id: Name::new_static("OSError"),
@ -159,9 +157,9 @@ pub(crate) fn os_error_alias_handlers(checker: &mut Checker, handlers: &[ExceptH
Expr::Tuple(tuple) => {
// List of aliases to replace with `OSError`.
let mut aliases: Vec<&Expr> = vec![];
for elt in &tuple.elts {
if is_alias(elt, checker.semantic()) {
aliases.push(elt);
for element in tuple {
if is_alias(element, checker.semantic()) {
aliases.push(element);
}
}
if !aliases.is_empty() {

View file

@ -198,8 +198,8 @@ fn percent_to_format(format_string: &CFormatString) -> String {
/// If a tuple has one argument, remove the comma; otherwise, return it as-is.
fn clean_params_tuple<'a>(right: &Expr, locator: &Locator<'a>) -> Cow<'a, str> {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &right {
if elts.len() == 1 {
if let Expr::Tuple(tuple) = &right {
if tuple.len() == 1 {
if !locator.contains_line_break(right.range()) {
let mut contents = locator.slice(right).to_string();
for (i, character) in contents.chars().rev().enumerate() {

View file

@ -110,22 +110,20 @@ fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&E
if semantic.has_builtin_binding("TimeoutError") {
// Filter out any `TimeoutErrors` aliases.
let mut remaining: Vec<Expr> = tuple
.elts
.iter()
.filter_map(|elt| {
if aliases.contains(&elt) {
.filter_map(|element| {
if aliases.contains(&element) {
None
} else {
Some(elt.clone())
Some(element.clone())
}
})
.collect();
// If `TimeoutError` itself isn't already in the tuple, add it.
if tuple
.elts
.iter()
.all(|elt| !semantic.match_builtin_expr(elt, "TimeoutError"))
.all(|element| !semantic.match_builtin_expr(element, "TimeoutError"))
{
let node = ast::ExprName {
id: Name::new_static("TimeoutError"),
@ -171,9 +169,9 @@ pub(crate) fn timeout_error_alias_handlers(checker: &mut Checker, handlers: &[Ex
Expr::Tuple(tuple) => {
// List of aliases to replace with `TimeoutError`.
let mut aliases: Vec<&Expr> = vec![];
for elt in &tuple.elts {
if is_alias(elt, checker.semantic(), checker.settings.target_version) {
aliases.push(elt);
for element in tuple {
if is_alias(element, checker.semantic(), checker.settings.target_version) {
aliases.push(element);
}
}
if !aliases.is_empty() {

View file

@ -189,7 +189,7 @@ fn is_allowed_value(expr: &Expr) -> bool {
| Expr::Subscript(_)
| Expr::Name(_)
| Expr::List(_) => true,
Expr::Tuple(tuple) => tuple.elts.iter().all(is_allowed_value),
Expr::Tuple(tuple) => tuple.iter().all(is_allowed_value),
// Maybe require parentheses.
Expr::Named(_) => false,
// Invalid in binary expressions.

View file

@ -3,7 +3,7 @@ use std::fmt;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::pep_604_union;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::Expr;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@ -90,15 +90,15 @@ pub(crate) fn use_pep604_isinstance(
let Some(types) = args.get(1) else {
return;
};
let Expr::Tuple(ast::ExprTuple { elts, .. }) = types else {
let Expr::Tuple(tuple) = types else {
return;
};
// Ex) `()`
if elts.is_empty() {
if tuple.is_empty() {
return;
}
// Ex) `(*args,)`
if elts.iter().any(Expr::is_starred_expr) {
if tuple.iter().any(Expr::is_starred_expr) {
return;
}
let Some(builtin_function_name) = checker.semantic().resolve_builtin_symbol(func) else {
@ -110,7 +110,7 @@ pub(crate) fn use_pep604_isinstance(
checker.diagnostics.push(
Diagnostic::new(NonPEP604Isinstance { kind }, expr.range()).with_fix(Fix::unsafe_edit(
Edit::range_replacement(
checker.generator().expr(&pep_604_union(elts)),
checker.generator().expr(&pep_604_union(&tuple.elts)),
types.range(),
),
)),

View file

@ -135,11 +135,10 @@ fn is_same_expr(left: &Expr, right: &Expr) -> bool {
match (&left, &right) {
(Expr::Name(left), Expr::Name(right)) => left.id == right.id,
(Expr::Tuple(left), Expr::Tuple(right)) => {
left.elts.len() == right.elts.len()
left.len() == right.len()
&& left
.elts
.iter()
.zip(right.elts.iter())
.zip(right)
.all(|(left, right)| is_same_expr(left, right))
}
_ => false,
@ -153,7 +152,7 @@ fn collect_names<'a>(expr: &'a Expr) -> Box<dyn Iterator<Item = &ast::ExprName>
expr.as_name_expr().into_iter().chain(
expr.as_tuple_expr()
.into_iter()
.flat_map(|tuple| tuple.elts.iter().flat_map(collect_names)),
.flat_map(|tuple| tuple.iter().flat_map(collect_names)),
),
)
}

View file

@ -98,7 +98,7 @@ fn is_none(expr: &Expr) -> bool {
}
// Ex) `(type(None),)`
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().all(|elt| inner(elt, false)),
Expr::Tuple(tuple) => tuple.iter().all(|element| inner(element, false)),
// Ex) `type(None) | type(None)`
Expr::BinOp(ast::ExprBinOp {

View file

@ -254,13 +254,12 @@ fn itemgetter_op_tuple(
let [arg] = params.args.as_slice() else {
return None;
};
if expr.elts.is_empty() || expr.elts.len() == 1 {
if expr.len() <= 1 {
return None;
}
Some(Operator {
name: "itemgetter",
args: expr
.elts
.iter()
.map(|expr| {
expr.as_subscript_expr()

View file

@ -134,9 +134,9 @@ pub(crate) fn reimplemented_starmap(checker: &mut Checker, target: &StarmapCandi
}
// Ex) `f(x, y, z) for x, y, z in iter`
ComprehensionTarget::Tuple(tuple) => {
if tuple.elts.len() != args.len()
|| !std::iter::zip(&tuple.elts, args)
.all(|(x, y)| ComparableExpr::from(x) == ComparableExpr::from(y))
if tuple.len() != args.len()
|| std::iter::zip(tuple, args)
.any(|(x, y)| ComparableExpr::from(x) != ComparableExpr::from(y))
{
return;
}
@ -144,9 +144,8 @@ pub(crate) fn reimplemented_starmap(checker: &mut Checker, target: &StarmapCandi
// If any of the members are used outside the function call, we can't replace it.
if any_over_expr(func, &|expr| {
tuple
.elts
.iter()
.any(|elt| ComparableExpr::from(expr) == ComparableExpr::from(elt))
.any(|elem| ComparableExpr::from(expr) == ComparableExpr::from(elem))
}) {
return;
}

View file

@ -63,16 +63,16 @@ impl AlwaysFixableViolation for IncorrectlyParenthesizedTupleInSubscript {
pub(crate) fn subscript_with_parenthesized_tuple(checker: &mut Checker, subscript: &ExprSubscript) {
let prefer_parentheses = checker.settings.ruff.parenthesize_tuple_in_subscript;
let Some(tuple_subscript) = subscript.slice.as_tuple_expr() else {
let Expr::Tuple(tuple_subscript) = &*subscript.slice else {
return;
};
if tuple_subscript.parenthesized == prefer_parentheses || tuple_subscript.elts.is_empty() {
if tuple_subscript.parenthesized == prefer_parentheses || tuple_subscript.is_empty() {
return;
}
// Adding parentheses in the presence of a slice leads to a syntax error.
if prefer_parentheses && tuple_subscript.elts.iter().any(Expr::is_slice_expr) {
if prefer_parentheses && tuple_subscript.iter().any(Expr::is_slice_expr) {
return;
}
@ -82,7 +82,7 @@ pub(crate) fn subscript_with_parenthesized_tuple(checker: &mut Checker, subscrip
// see https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes
if checker.settings.target_version <= PythonVersion::Py310
&& !prefer_parentheses
&& tuple_subscript.elts.iter().any(Expr::is_starred_expr)
&& tuple_subscript.iter().any(Expr::is_starred_expr)
{
return;
}

View file

@ -112,25 +112,19 @@ pub(crate) fn never_union(checker: &mut Checker, expr: &Expr) {
ctx: _,
range: _,
}) if checker.semantic().match_typing_expr(value, "Union") => {
let Expr::Tuple(ast::ExprTuple {
elts,
ctx: _,
range: _,
parenthesized: _,
}) = slice.as_ref()
else {
let Expr::Tuple(tuple_slice) = &**slice else {
return;
};
// Analyze each element of the `Union`.
for elt in elts {
for elt in tuple_slice {
if let Some(never_like) = NeverLike::from_expr(elt, checker.semantic()) {
// Collect the other elements of the `Union`.
let rest = elts
let rest: Vec<Expr> = tuple_slice
.iter()
.filter(|other| *other != elt)
.cloned()
.collect::<Vec<_>>();
.collect();
// Ignore, e.g., `typing.Union[typing.NoReturn]`.
if rest.is_empty() {

View file

@ -136,7 +136,7 @@ fn start_is_empty_list(arguments: &Arguments, semantic: &SemanticModel) -> bool
Expr::Call(ast::ExprCall {
func, arguments, ..
}) => arguments.is_empty() && semantic.match_builtin_expr(func, "list"),
Expr::List(ast::ExprList { elts, ctx, .. }) => elts.is_empty() && ctx.is_load(),
Expr::List(list) => list.is_empty() && list.ctx.is_load(),
_ => false,
}
}

View file

@ -157,11 +157,16 @@ fn sort_dunder_all(checker: &mut Checker, target: &ast::Expr, node: &ast::Expr)
let (elts, range, kind) = match node {
ast::Expr::List(ast::ExprList { elts, range, .. }) => (elts, *range, SequenceKind::List),
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => (
ast::Expr::Tuple(ast::ExprTuple {
elts,
range,
parenthesized,
..
}) => (
elts,
*range,
SequenceKind::Tuple {
parenthesized: tuple_node.parenthesized,
parenthesized: *parenthesized,
},
),
_ => return,

View file

@ -168,9 +168,14 @@ impl<'a> StringLiteralDisplay<'a> {
kind,
}
}
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => {
ast::Expr::Tuple(ast::ExprTuple {
elts,
range,
parenthesized,
..
}) => {
let kind = DisplayKind::Sequence(SequenceKind::Tuple {
parenthesized: tuple_node.parenthesized,
parenthesized: *parenthesized,
});
Self {
elts: Cow::Borrowed(elts),
@ -186,8 +191,8 @@ impl<'a> StringLiteralDisplay<'a> {
kind,
}
}
ast::Expr::Dict(dict @ ast::ExprDict { items, range }) => {
let mut narrowed_keys = Vec::with_capacity(items.len());
ast::Expr::Dict(dict) => {
let mut narrowed_keys = Vec::with_capacity(dict.len());
for key in dict.iter_keys() {
if let Some(key) = key {
// This is somewhat unfortunate,
@ -201,11 +206,11 @@ impl<'a> StringLiteralDisplay<'a> {
// `__slots__ = {"foo": "bar", **other_dict}`
// If `None` wasn't present in the keys,
// the length of the keys should always equal the length of the values
assert_eq!(narrowed_keys.len(), items.len());
assert_eq!(narrowed_keys.len(), dict.len());
Self {
elts: Cow::Owned(narrowed_keys),
range: *range,
kind: DisplayKind::Dict { items },
range: dict.range(),
kind: DisplayKind::Dict { items: &dict.items },
}
}
_ => return None,

View file

@ -23,7 +23,7 @@ fn is_known_type(qualified_name: &QualifiedName, minor_version: u8) -> bool {
/// tuple, the iterator will only yield the slice.
fn resolve_slice_value(slice: &Expr) -> impl Iterator<Item = &Expr> {
match slice {
Expr::Tuple(ast::ExprTuple { elts: elements, .. }) => Left(elements.iter()),
Expr::Tuple(tuple) => Left(tuple.iter()),
_ => Right(std::iter::once(slice)),
}
}

View file

@ -582,8 +582,8 @@ pub const fn is_singleton(expr: &Expr) -> bool {
/// Return `true` if the [`Expr`] is a literal or tuple of literals.
pub fn is_constant(expr: &Expr) -> bool {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = expr {
elts.iter().all(is_constant)
if let Expr::Tuple(tuple) = expr {
tuple.iter().all(is_constant)
} else {
expr.is_literal_expr()
}
@ -630,8 +630,8 @@ pub fn extract_handled_exceptions(handlers: &[ExceptHandler]) -> Vec<&Expr> {
match handler {
ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, .. }) => {
if let Some(type_) = type_ {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_.as_ref() {
for type_ in elts {
if let Expr::Tuple(tuple) = &**type_ {
for type_ in tuple {
handled_exceptions.push(type_);
}
} else {
@ -1185,8 +1185,8 @@ impl Truthiness {
Self::Truthy
}
}
Expr::Dict(ast::ExprDict { items, .. }) => {
if items.is_empty() {
Expr::Dict(dict) => {
if dict.is_empty() {
Self::Falsey
} else {
Self::Truthy

View file

@ -856,6 +856,27 @@ impl ExprDict {
pub fn value(&self, n: usize) -> &Expr {
self.items[n].value()
}
pub fn iter(&self) -> std::slice::Iter<'_, DictItem> {
self.items.iter()
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl<'a> IntoIterator for &'a ExprDict {
type IntoIter = std::slice::Iter<'a, DictItem>;
type Item = &'a DictItem;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl From<ExprDict> for Expr {
@ -955,6 +976,29 @@ pub struct ExprSet {
pub elts: Vec<Expr>,
}
impl ExprSet {
pub fn iter(&self) -> std::slice::Iter<'_, Expr> {
self.elts.iter()
}
pub fn len(&self) -> usize {
self.elts.len()
}
pub fn is_empty(&self) -> bool {
self.elts.is_empty()
}
}
impl<'a> IntoIterator for &'a ExprSet {
type IntoIter = std::slice::Iter<'a, Expr>;
type Item = &'a Expr;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl From<ExprSet> for Expr {
fn from(payload: ExprSet) -> Self {
Expr::Set(payload)
@ -2759,6 +2803,29 @@ pub struct ExprList {
pub ctx: ExprContext,
}
impl ExprList {
pub fn iter(&self) -> std::slice::Iter<'_, Expr> {
self.elts.iter()
}
pub fn len(&self) -> usize {
self.elts.len()
}
pub fn is_empty(&self) -> bool {
self.elts.is_empty()
}
}
impl<'a> IntoIterator for &'a ExprList {
type IntoIter = std::slice::Iter<'a, Expr>;
type Item = &'a Expr;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl From<ExprList> for Expr {
fn from(payload: ExprList) -> Self {
Expr::List(payload)
@ -2776,6 +2843,29 @@ pub struct ExprTuple {
pub parenthesized: bool,
}
impl ExprTuple {
pub fn iter(&self) -> std::slice::Iter<'_, Expr> {
self.elts.iter()
}
pub fn len(&self) -> usize {
self.elts.len()
}
pub fn is_empty(&self) -> bool {
self.elts.is_empty()
}
}
impl<'a> IntoIterator for &'a ExprTuple {
type IntoIter = std::slice::Iter<'a, Expr>;
type Item = &'a Expr;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl From<ExprTuple> for Expr {
fn from(payload: ExprTuple) -> Self {
Expr::Tuple(payload)

View file

@ -921,10 +921,10 @@ impl<'a> Generator<'a> {
self.unparse_expr(orelse, precedence::IF_EXP);
});
}
Expr::Dict(ast::ExprDict { items, range: _ }) => {
Expr::Dict(dict) => {
self.p("{");
let mut first = true;
for ast::DictItem { key, value } in items {
for ast::DictItem { key, value } in dict {
self.p_delim(&mut first, ", ");
if let Some(key) = key {
self.unparse_expr(key, precedence::COMMA);
@ -937,15 +937,15 @@ impl<'a> Generator<'a> {
}
self.p("}");
}
Expr::Set(ast::ExprSet { elts, range: _ }) => {
if elts.is_empty() {
Expr::Set(set) => {
if set.is_empty() {
self.p("set()");
} else {
self.p("{");
let mut first = true;
for v in elts {
for item in set {
self.p_delim(&mut first, ", ");
self.unparse_expr(v, precedence::COMMA);
self.unparse_expr(item, precedence::COMMA);
}
self.p("}");
}
@ -1164,26 +1164,26 @@ impl<'a> Generator<'a> {
self.unparse_expr(value, precedence::MAX);
}
Expr::Name(ast::ExprName { id, .. }) => self.p(id.as_str()),
Expr::List(ast::ExprList { elts, .. }) => {
Expr::List(list) => {
self.p("[");
let mut first = true;
for elt in elts {
for item in list {
self.p_delim(&mut first, ", ");
self.unparse_expr(elt, precedence::COMMA);
self.unparse_expr(item, precedence::COMMA);
}
self.p("]");
}
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
if elts.is_empty() {
Expr::Tuple(tuple) => {
if tuple.is_empty() {
self.p("()");
} else {
group_if!(precedence::TUPLE, {
let mut first = true;
for elt in elts {
for item in tuple {
self.p_delim(&mut first, ", ");
self.unparse_expr(elt, precedence::COMMA);
self.unparse_expr(item, precedence::COMMA);
}
self.p_if(elts.len() == 1, ",");
self.p_if(tuple.len() == 1, ",");
});
}
}

View file

@ -193,7 +193,7 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
TupleParentheses::NeverPreserve => {
optional_parentheses(&ExprSequence::new(item)).fmt(f)
}
TupleParentheses::OptionalParentheses if item.elts.len() == 2 => {
TupleParentheses::OptionalParentheses if item.len() == 2 => {
optional_parentheses(&ExprSequence::new(item)).fmt(f)
}
TupleParentheses::Default | TupleParentheses::OptionalParentheses => {

View file

@ -1052,7 +1052,7 @@ pub(crate) fn has_own_parentheses(
..
},
) => {
if !tuple.elts.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) {
if !tuple.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) {
Some(OwnParentheses::NonEmpty)
} else {
Some(OwnParentheses::Empty)
@ -1216,10 +1216,10 @@ pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) -
| Expr::YieldFrom(_) => true,
// Sequence types can split if they contain at least one element.
Expr::Tuple(tuple) => !tuple.elts.is_empty(),
Expr::Dict(dict) => !dict.items.is_empty(),
Expr::Set(set) => !set.elts.is_empty(),
Expr::List(list) => !list.elts.is_empty(),
Expr::Tuple(tuple) => !tuple.is_empty(),
Expr::Dict(dict) => !dict.is_empty(),
Expr::Set(set) => !set.is_empty(),
Expr::List(list) => !list.is_empty(),
Expr::UnaryOp(unary) => is_splittable_expression(unary.operand.as_ref(), context),
Expr::Yield(ast::ExprYield { value, .. }) => value.is_some(),

View file

@ -151,7 +151,7 @@ pub fn to_pep604_operator(
fn quoted_annotation(slice: &Expr) -> bool {
match slice {
Expr::StringLiteral(_) => true,
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().any(quoted_annotation),
Expr::Tuple(tuple) => tuple.iter().any(quoted_annotation),
_ => false,
}
}
@ -160,7 +160,7 @@ pub fn to_pep604_operator(
fn starred_annotation(slice: &Expr) -> bool {
match slice {
Expr::Starred(_) => true,
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().any(starred_annotation),
Expr::Tuple(tuple) => tuple.iter().any(starred_annotation),
_ => false,
}
}
@ -237,9 +237,9 @@ pub fn is_immutable_annotation(
if is_immutable_generic_type(qualified_name.segments()) {
true
} else if matches!(qualified_name.segments(), ["typing", "Union"]) {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
elts.iter().all(|elt| {
is_immutable_annotation(elt, semantic, extend_immutable_calls)
if let Expr::Tuple(tuple) = &**slice {
tuple.iter().all(|element| {
is_immutable_annotation(element, semantic, extend_immutable_calls)
})
} else {
false
@ -399,11 +399,12 @@ where
// 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() {
if let Expr::Tuple(tuple) = &**slice {
// Traverse each element of the tuple within the union recursively to handle cases
// such as `Union[..., Union[...]]
elts.iter()
.for_each(|elt| inner(func, semantic, elt, Some(expr)));
tuple
.iter()
.for_each(|elem| inner(func, semantic, elem, Some(expr)));
return;
}
}
@ -438,11 +439,11 @@ where
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if semantic.match_typing_expr(value, "Literal") {
match &**slice {
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
Expr::Tuple(tuple) => {
// 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));
for element in tuple {
inner(func, semantic, element, Some(expr));
}
}
other => {

View file

@ -1432,9 +1432,7 @@ impl<'a> SemanticModel<'a> {
/// variable to be "used" if it's shadowed by another variable with usages.
pub fn is_unused(&self, expr: &Expr) -> bool {
match expr {
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
elts.iter().all(|expr| self.is_unused(expr))
}
Expr::Tuple(tuple) => tuple.iter().all(|expr| self.is_unused(expr)),
Expr::Name(ast::ExprName { id, .. }) => {
// Treat a variable as used if it has any usages, _or_ it's shadowed by another variable
// with usages.