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; let Keyword { arg, value, .. } = keyword;
match (arg.as_ref(), value) { match (arg.as_ref(), value) {
// Ex) NamedTuple("a", **{"a": int}) // Ex) NamedTuple("a", **{"a": int})
(None, Expr::Dict(ast::ExprDict { items, .. })) => { (None, Expr::Dict(dict)) => {
for ast::DictItem { key, value } in items { for ast::DictItem { key, value } in dict {
if let Some(key) = key.as_ref() { if let Some(key) = key.as_ref() {
self.visit_non_type_definition(key); self.visit_non_type_definition(key);
self.visit_type_definition(value); self.visit_type_definition(value);

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, 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 ruff_text_size::Ranged;
use crate::checkers::ast::Checker; 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) { if let Some(argument) = call.arguments.find_argument(argument_name, position) {
match argument_name { match argument_name {
"select" => match argument { "select" => match argument {
Expr::Dict(ExprDict { items, .. }) => { Expr::Dict(dict) => {
if items.iter().any(|ast::DictItem { key, value }| { if dict.iter().any(|ast::DictItem { key, value }| {
key.as_ref() key.as_ref()
.is_some_and(|key| !key.is_string_literal_expr()) .is_some_and(|key| !key.is_string_literal_expr())
|| !value.is_string_literal_expr() || !value.is_string_literal_expr()
@ -77,8 +77,8 @@ fn is_call_insecure(call: &ast::ExprCall) -> bool {
_ => return true, _ => return true,
}, },
"where" | "tables" => match argument { "where" | "tables" => match argument {
Expr::List(ExprList { elts, .. }) => { Expr::List(list) => {
if !elts.iter().all(Expr::is_string_literal_expr) { if !list.iter().all(Expr::is_string_literal_expr) {
return true; 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) /// subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
/// ``` /// ```
fn is_wildcard_command(expr: &Expr) -> bool { 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_star = false;
let mut has_command = false; let mut has_command = false;
for elt in elts { for item in list {
if let Some(text) = string_literal(elt) { if let Some(text) = string_literal(item) {
has_star |= text.contains('*'); has_star |= text.contains('*');
has_command |= text.contains("chown") has_command |= text.contains("chown")
|| text.contains("chmod") || text.contains("chmod")

View file

@ -49,16 +49,16 @@ impl Violation for DuplicateValue {
/// B033 /// B033
pub(crate) fn duplicate_value(checker: &mut Checker, set: &ast::ExprSet) { pub(crate) fn duplicate_value(checker: &mut Checker, set: &ast::ExprSet) {
let mut seen_values: FxHashSet<ComparableExpr> = FxHashSet::default(); let mut seen_values: FxHashSet<ComparableExpr> = FxHashSet::default();
for (index, elt) in set.elts.iter().enumerate() { for (index, value) in set.iter().enumerate() {
if elt.is_literal_expr() { if value.is_literal_expr() {
let comparable_value: ComparableExpr = elt.into(); let comparable_value = ComparableExpr::from(value);
if !seen_values.insert(comparable_value) { if !seen_values.insert(comparable_value) {
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
DuplicateValue { DuplicateValue {
value: checker.generator().expr(elt), value: checker.generator().expr(value),
}, },
elt.range(), value.range(),
); );
diagnostic.try_set_fix(|| { 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`]. /// Remove the member at the given index from the [`ast::ExprSet`].
fn remove_member(set: &ast::ExprSet, index: usize, source: &str) -> Result<Edit> { 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 // Case 1: the expression is _not_ the last node, so delete from the start of the
// expression to the end of the subsequent comma. // expression to the end of the subsequent comma.
// Ex) Delete `"a"` in `{"a", "b", "c"}`. // 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 { let Expr::Call(ast::ExprCall { func, .. }) = &iter else {
return; return;
}; };
let Expr::Tuple(ast::ExprTuple { elts, .. }) = target else { let Expr::Tuple(tuple) = target else {
// Ignore any `groupby()` invocation that isn't unpacked // Ignore any `groupby()` invocation that isn't unpacked
return; return;
}; };
if elts.len() != 2 { if tuple.len() != 2 {
return; return;
} }
// We have an invocation of groupby which is a simple unpacking // 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; return;
}; };
// Check if the function call is `itertools.groupby` // 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. /// comprehension.
fn is_constant(key: &Expr, names: &FxHashMap<&str, &ast::ExprName>) -> bool { fn is_constant(key: &Expr, names: &FxHashMap<&str, &ast::ExprName>) -> bool {
match key { 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::Name(ast::ExprName { id, .. }) => !names.contains_key(id.as_str()),
Expr::Attribute(ast::ExprAttribute { value, .. }) => is_constant(value, names), Expr::Attribute(ast::ExprAttribute { value, .. }) => is_constant(value, names),
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { 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 { let Expr::Generator(ast::ExprGenerator { elt, .. }) = argument else {
return; return;
}; };
let Expr::Tuple(ast::ExprTuple { elts, .. }) = elt.as_ref() else { let Expr::Tuple(tuple) = &**elt else {
return; return;
}; };
if elts.len() != 2 { if tuple.len() != 2 {
return; return;
} }
if elts.iter().any(Expr::is_starred_expr) { if tuple.iter().any(Expr::is_starred_expr) {
return; return;
} }
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range()); 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 { let Expr::ListComp(ast::ExprListComp { elt, .. }) = argument else {
return; return;
}; };
let Expr::Tuple(ast::ExprTuple { elts, .. }) = elt.as_ref() else { let Expr::Tuple(tuple) = &**elt else {
return; return;
}; };
if elts.len() != 2 { if tuple.len() != 2 {
return; return;
} }
let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionDict, expr.range()); 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), ...])`. // Accept `dict((1, 2), ...))` `dict([(1, 2), ...])`.
if !elts if !elts
.iter() .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; return;
} }

View file

@ -163,8 +163,8 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
elts: words elts: words
.iter() .iter()
.flat_map(|value| { .flat_map(|value| {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = value { if let Expr::Tuple(tuple) = value {
Left(elts.iter()) Left(tuple.iter())
} else { } else {
Right(iter::once(*value)) 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::{Diagnostic, Edit, Fix};
use ruff_diagnostics::{FixAvailability, Violation}; use ruff_diagnostics::{FixAvailability, Violation};
@ -70,8 +70,8 @@ pub(crate) fn reimplemented_container_builtin(checker: &mut Checker, expr: &Expr
} }
let container = match &**body { let container = match &**body {
Expr::List(ast::ExprList { elts, .. }) if elts.is_empty() => Container::List, Expr::List(list) if list.is_empty() => Container::List,
Expr::Dict(ast::ExprDict { items, .. }) if items.is_empty() => Container::Dict, Expr::Dict(dict) if dict.is_empty() => Container::Dict,
_ => return, _ => return,
}; };
let mut diagnostic = Diagnostic::new(ReimplementedContainerBuiltin { container }, expr.range()); 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() .iter_keys()
.filter_map(|key| key.and_then(as_kwarg)) .filter_map(|key| key.and_then(as_kwarg))
.collect(); .collect();
if kwargs.len() != dict.items.len() { if kwargs.len() != dict.len() {
continue; continue;
} }
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range()); let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range());
if dict.items.is_empty() { if dict.is_empty() {
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {
remove_argument( remove_argument(
keyword, keyword,

View file

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

View file

@ -162,12 +162,11 @@ pub(crate) fn bad_generator_return_type(
// - if not, don't emit the diagnostic // - if not, don't emit the diagnostic
let yield_type_info = match returns { let yield_type_info = match returns {
ast::Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() { 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 if !slice_tuple
.elts
.iter() .iter()
.skip(1) .skip(1)
.all(|elt| is_any_or_none(elt, semantic)) .all(|element| is_any_or_none(element, semantic))
{ {
return; 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| { let mut func = |expr: &'a Expr, _parent: &'a Expr| {
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if checker.semantic().match_typing_expr(value, "Literal") { if checker.semantic().match_typing_expr(value, "Literal") {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() { if let Expr::Tuple(tuple) = &**slice {
typing_literal_exprs.extend(elts.iter()); typing_literal_exprs.extend(tuple);
} else { } else {
typing_literal_exprs.push(slice); typing_literal_exprs.push(slice);
} }

View file

@ -298,10 +298,10 @@ fn is_valid_default_value_with_annotation(
.iter() .iter()
.all(|e| is_valid_default_value_with_annotation(e, false, locator, semantic)); .all(|e| is_valid_default_value_with_annotation(e, false, locator, semantic));
} }
Expr::Dict(ast::ExprDict { items, range: _ }) => { Expr::Dict(dict) => {
return allow_container return allow_container
&& items.len() <= 10 && dict.len() <= 10
&& items.iter().all(|ast::DictItem { key, value }| { && dict.iter().all(|ast::DictItem { key, value }| {
key.as_ref().is_some_and(|key| { key.as_ref().is_some_and(|key| {
is_valid_default_value_with_annotation(key, false, locator, semantic) is_valid_default_value_with_annotation(key, false, locator, semantic)
}) && is_valid_default_value_with_annotation(value, 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()); literal_subscript = Some(value.as_ref());
} }
let slice = &**slice;
// flatten already-unioned literals to later union again // flatten already-unioned literals to later union again
if let Expr::Tuple(ast::ExprTuple { if let Expr::Tuple(tuple) = slice {
elts, for item in tuple {
range: _, literal_exprs.push(item);
ctx: _,
parenthesized: _,
}) = slice.as_ref()
{
for expr in elts {
literal_exprs.push(expr);
} }
} else { } else {
literal_exprs.push(slice.as_ref()); literal_exprs.push(slice);
} }
} }
} else { } else {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -613,7 +613,7 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> {
}; };
if let ast::Expr::Tuple(tuple) = exceptions { if let ast::Expr::Tuple(tuple) = exceptions {
for exception in &tuple.elts { for exception in tuple {
maybe_store_exception(exception); maybe_store_exception(exception);
} }
} else { } 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_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -39,8 +39,8 @@ impl Violation for AssertTuple {
/// F631 /// F631
pub(crate) fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) { pub(crate) fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &test { if let Expr::Tuple(tuple) = &test {
if !elts.is_empty() { if !tuple.is_empty() {
checker checker
.diagnostics .diagnostics
.push(Diagnostic::new(AssertTuple, stmt.range())); .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_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -41,10 +41,10 @@ impl Violation for IfTuple {
/// F634 /// F634
pub(crate) fn if_tuple(checker: &mut Checker, stmt_if: &StmtIf) { pub(crate) fn if_tuple(checker: &mut Checker, stmt_if: &StmtIf) {
for branch in if_elif_branches(stmt_if) { 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; continue;
}; };
if elts.is_empty() { if tuple.is_empty() {
continue; continue;
} }
checker checker

View file

@ -130,10 +130,10 @@ impl Violation for MultiValueRepeatedKeyVariable {
pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) { pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) {
// Generate a map from key to (index, value). // Generate a map from key to (index, value).
let mut seen: FxHashMap<ComparableExpr, FxHashSet<ComparableExpr>> = 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. // 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 { let Some(key) = key else {
continue; continue;
}; };

View file

@ -690,10 +690,10 @@ pub(crate) fn percent_format_positional_count_mismatch(
return; return;
} }
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = right { if let Expr::Tuple(tuple) = right {
let mut found = 0; let mut found = 0;
for elt in elts { for element in tuple {
if elt.is_starred_expr() { if element.is_starred_expr() {
return; return;
} }
found += 1; 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_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; 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) { 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; return;
}; };
if elts.len() != 2 { if tuple.len() != 2 {
return; return;
}; };
let Some(name) = iter.as_name_expr() else { let Expr::Name(name) = iter else {
return; return;
}; };
@ -91,20 +91,15 @@ fn is_dict_key_tuple_with_two_elements(binding: &Binding, semantic: &SemanticMod
return false; return false;
}; };
let Some(assign_stmt) = statement.as_assign_stmt() else { let Stmt::Assign(assign_stmt) = statement else {
return false; return false;
}; };
let Some(dict_expr) = assign_stmt.value.as_dict_expr() else { let Expr::Dict(dict_expr) = &*assign_stmt.value else {
return false; return false;
}; };
dict_expr.iter_keys().all(|elt| { dict_expr
elt.is_some_and(|x| { .iter_keys()
if let Expr::Tuple(ExprTuple { elts, .. }) = x { .all(|key| matches!(key, Some(Expr::Tuple(tuple)) if tuple.len() == 2))
return elts.len() == 2;
}
false
})
})
} }

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; 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 ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -42,17 +42,17 @@ impl AlwaysFixableViolation for IterationOverSet {
/// PLC0208 /// PLC0208
pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) { 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; return;
}; };
if elts.iter().any(Expr::is_starred_expr) { if set.iter().any(Expr::is_starred_expr) {
return; return;
} }
let mut diagnostic = Diagnostic::new(IterationOverSet, expr.range()); 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); let elt = checker.locator().slice(elt);
format!("({elt},)") format!("({elt},)")
} else { } 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>) { fn check_expr(checker: &mut Checker, expr: &Expr, names: &mut Vec<Name>) {
match expr { match expr {
Expr::Tuple(ast::ExprTuple { elts, .. }) => { Expr::Tuple(tuple) => {
for target in elts { for target in tuple {
check_expr(checker, target, names); 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>) { fn visit_assignments(left: &Expr, right: &Expr, diagnostics: &mut Vec<Diagnostic>) {
match (left, right) { match (left, right) {
( (Expr::Tuple(lhs), Expr::Tuple(rhs)) if lhs.len() == rhs.len() => lhs
Expr::Tuple(ast::ExprTuple { elts: lhs_elts, .. }),
Expr::Tuple(ast::ExprTuple { elts: rhs_elts, .. }),
) if lhs_elts.len() == rhs_elts.len() => lhs_elts
.iter() .iter()
.zip(rhs_elts.iter()) .zip(rhs)
.for_each(|(lhs, rhs)| visit_assignments(lhs, rhs, diagnostics)), .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: lhs_name, .. }),
Expr::Name(ast::ExprName { id: rhs_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. /// Create a list of field assignments from the `NamedTuple` fields argument.
fn create_fields_from_fields_arg(fields: &Expr) -> Option<Vec<Stmt>> { fn create_fields_from_fields_arg(fields: &Expr) -> Option<Vec<Stmt>> {
let ast::ExprList { elts, .. } = fields.as_list_expr()?; let fields = fields.as_list_expr()?;
if elts.is_empty() { if fields.is_empty() {
let node = Stmt::Pass(ast::StmtPass { let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(), range: TextRange::default(),
}); });
Some(vec![node]) Some(vec![node])
} else { } else {
elts.iter() fields
.iter()
.map(|field| { .map(|field| {
let ast::ExprTuple { elts, .. } = field.as_tuple_expr()?; let ast::ExprTuple { elts, .. } = field.as_tuple_expr()?;
let [field, annotation] = elts.as_slice() else { 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") { if semantic.has_builtin_binding("OSError") {
// Filter out any `OSErrors` aliases. // Filter out any `OSErrors` aliases.
let mut remaining: Vec<Expr> = tuple let mut remaining: Vec<Expr> = tuple
.elts
.iter() .iter()
.filter_map(|elt| { .filter_map(|element| {
if aliases.contains(&elt) { if aliases.contains(&element) {
None None
} else { } else {
Some(elt.clone()) Some(element.clone())
} }
}) })
.collect(); .collect();
// If `OSError` itself isn't already in the tuple, add it. // If `OSError` itself isn't already in the tuple, add it.
if tuple if tuple
.elts
.iter() .iter()
.all(|elt| !semantic.match_builtin_expr(elt, "OSError")) .all(|elem| !semantic.match_builtin_expr(elem, "OSError"))
{ {
let node = ast::ExprName { let node = ast::ExprName {
id: Name::new_static("OSError"), id: Name::new_static("OSError"),
@ -159,9 +157,9 @@ pub(crate) fn os_error_alias_handlers(checker: &mut Checker, handlers: &[ExceptH
Expr::Tuple(tuple) => { Expr::Tuple(tuple) => {
// List of aliases to replace with `OSError`. // List of aliases to replace with `OSError`.
let mut aliases: Vec<&Expr> = vec![]; let mut aliases: Vec<&Expr> = vec![];
for elt in &tuple.elts { for element in tuple {
if is_alias(elt, checker.semantic()) { if is_alias(element, checker.semantic()) {
aliases.push(elt); aliases.push(element);
} }
} }
if !aliases.is_empty() { 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. /// 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> { fn clean_params_tuple<'a>(right: &Expr, locator: &Locator<'a>) -> Cow<'a, str> {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &right { if let Expr::Tuple(tuple) = &right {
if elts.len() == 1 { if tuple.len() == 1 {
if !locator.contains_line_break(right.range()) { if !locator.contains_line_break(right.range()) {
let mut contents = locator.slice(right).to_string(); let mut contents = locator.slice(right).to_string();
for (i, character) in contents.chars().rev().enumerate() { 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") { if semantic.has_builtin_binding("TimeoutError") {
// Filter out any `TimeoutErrors` aliases. // Filter out any `TimeoutErrors` aliases.
let mut remaining: Vec<Expr> = tuple let mut remaining: Vec<Expr> = tuple
.elts
.iter() .iter()
.filter_map(|elt| { .filter_map(|element| {
if aliases.contains(&elt) { if aliases.contains(&element) {
None None
} else { } else {
Some(elt.clone()) Some(element.clone())
} }
}) })
.collect(); .collect();
// If `TimeoutError` itself isn't already in the tuple, add it. // If `TimeoutError` itself isn't already in the tuple, add it.
if tuple if tuple
.elts
.iter() .iter()
.all(|elt| !semantic.match_builtin_expr(elt, "TimeoutError")) .all(|element| !semantic.match_builtin_expr(element, "TimeoutError"))
{ {
let node = ast::ExprName { let node = ast::ExprName {
id: Name::new_static("TimeoutError"), id: Name::new_static("TimeoutError"),
@ -171,9 +169,9 @@ pub(crate) fn timeout_error_alias_handlers(checker: &mut Checker, handlers: &[Ex
Expr::Tuple(tuple) => { Expr::Tuple(tuple) => {
// List of aliases to replace with `TimeoutError`. // List of aliases to replace with `TimeoutError`.
let mut aliases: Vec<&Expr> = vec![]; let mut aliases: Vec<&Expr> = vec![];
for elt in &tuple.elts { for element in tuple {
if is_alias(elt, checker.semantic(), checker.settings.target_version) { if is_alias(element, checker.semantic(), checker.settings.target_version) {
aliases.push(elt); aliases.push(element);
} }
} }
if !aliases.is_empty() { if !aliases.is_empty() {

View file

@ -189,7 +189,7 @@ fn is_allowed_value(expr: &Expr) -> bool {
| Expr::Subscript(_) | Expr::Subscript(_)
| Expr::Name(_) | Expr::Name(_)
| Expr::List(_) => true, | 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. // Maybe require parentheses.
Expr::Named(_) => false, Expr::Named(_) => false,
// Invalid in binary expressions. // Invalid in binary expressions.

View file

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

View file

@ -135,11 +135,10 @@ fn is_same_expr(left: &Expr, right: &Expr) -> bool {
match (&left, &right) { match (&left, &right) {
(Expr::Name(left), Expr::Name(right)) => left.id == right.id, (Expr::Name(left), Expr::Name(right)) => left.id == right.id,
(Expr::Tuple(left), Expr::Tuple(right)) => { (Expr::Tuple(left), Expr::Tuple(right)) => {
left.elts.len() == right.elts.len() left.len() == right.len()
&& left && left
.elts
.iter() .iter()
.zip(right.elts.iter()) .zip(right)
.all(|(left, right)| is_same_expr(left, right)) .all(|(left, right)| is_same_expr(left, right))
} }
_ => false, _ => 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_name_expr().into_iter().chain(
expr.as_tuple_expr() expr.as_tuple_expr()
.into_iter() .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),)` // 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)` // Ex) `type(None) | type(None)`
Expr::BinOp(ast::ExprBinOp { Expr::BinOp(ast::ExprBinOp {

View file

@ -254,13 +254,12 @@ fn itemgetter_op_tuple(
let [arg] = params.args.as_slice() else { let [arg] = params.args.as_slice() else {
return None; return None;
}; };
if expr.elts.is_empty() || expr.elts.len() == 1 { if expr.len() <= 1 {
return None; return None;
} }
Some(Operator { Some(Operator {
name: "itemgetter", name: "itemgetter",
args: expr args: expr
.elts
.iter() .iter()
.map(|expr| { .map(|expr| {
expr.as_subscript_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` // Ex) `f(x, y, z) for x, y, z in iter`
ComprehensionTarget::Tuple(tuple) => { ComprehensionTarget::Tuple(tuple) => {
if tuple.elts.len() != args.len() if tuple.len() != args.len()
|| !std::iter::zip(&tuple.elts, args) || std::iter::zip(tuple, args)
.all(|(x, y)| ComparableExpr::from(x) == ComparableExpr::from(y)) .any(|(x, y)| ComparableExpr::from(x) != ComparableExpr::from(y))
{ {
return; 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 of the members are used outside the function call, we can't replace it.
if any_over_expr(func, &|expr| { if any_over_expr(func, &|expr| {
tuple tuple
.elts
.iter() .iter()
.any(|elt| ComparableExpr::from(expr) == ComparableExpr::from(elt)) .any(|elem| ComparableExpr::from(expr) == ComparableExpr::from(elem))
}) { }) {
return; return;
} }

View file

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

View file

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

View file

@ -136,7 +136,7 @@ fn start_is_empty_list(arguments: &Arguments, semantic: &SemanticModel) -> bool
Expr::Call(ast::ExprCall { Expr::Call(ast::ExprCall {
func, arguments, .. func, arguments, ..
}) => arguments.is_empty() && semantic.match_builtin_expr(func, "list"), }) => 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, _ => 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 { let (elts, range, kind) = match node {
ast::Expr::List(ast::ExprList { elts, range, .. }) => (elts, *range, SequenceKind::List), 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, elts,
*range, *range,
SequenceKind::Tuple { SequenceKind::Tuple {
parenthesized: tuple_node.parenthesized, parenthesized: *parenthesized,
}, },
), ),
_ => return, _ => return,

View file

@ -168,9 +168,14 @@ impl<'a> StringLiteralDisplay<'a> {
kind, kind,
} }
} }
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => { ast::Expr::Tuple(ast::ExprTuple {
elts,
range,
parenthesized,
..
}) => {
let kind = DisplayKind::Sequence(SequenceKind::Tuple { let kind = DisplayKind::Sequence(SequenceKind::Tuple {
parenthesized: tuple_node.parenthesized, parenthesized: *parenthesized,
}); });
Self { Self {
elts: Cow::Borrowed(elts), elts: Cow::Borrowed(elts),
@ -186,8 +191,8 @@ impl<'a> StringLiteralDisplay<'a> {
kind, kind,
} }
} }
ast::Expr::Dict(dict @ ast::ExprDict { items, range }) => { ast::Expr::Dict(dict) => {
let mut narrowed_keys = Vec::with_capacity(items.len()); let mut narrowed_keys = Vec::with_capacity(dict.len());
for key in dict.iter_keys() { for key in dict.iter_keys() {
if let Some(key) = key { if let Some(key) = key {
// This is somewhat unfortunate, // This is somewhat unfortunate,
@ -201,11 +206,11 @@ impl<'a> StringLiteralDisplay<'a> {
// `__slots__ = {"foo": "bar", **other_dict}` // `__slots__ = {"foo": "bar", **other_dict}`
// If `None` wasn't present in the keys, // If `None` wasn't present in the keys,
// the length of the keys should always equal the length of the values // 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 { Self {
elts: Cow::Owned(narrowed_keys), elts: Cow::Owned(narrowed_keys),
range: *range, range: dict.range(),
kind: DisplayKind::Dict { items }, kind: DisplayKind::Dict { items: &dict.items },
} }
} }
_ => return None, _ => 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. /// tuple, the iterator will only yield the slice.
fn resolve_slice_value(slice: &Expr) -> impl Iterator<Item = &Expr> { fn resolve_slice_value(slice: &Expr) -> impl Iterator<Item = &Expr> {
match slice { match slice {
Expr::Tuple(ast::ExprTuple { elts: elements, .. }) => Left(elements.iter()), Expr::Tuple(tuple) => Left(tuple.iter()),
_ => Right(std::iter::once(slice)), _ => 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. /// Return `true` if the [`Expr`] is a literal or tuple of literals.
pub fn is_constant(expr: &Expr) -> bool { pub fn is_constant(expr: &Expr) -> bool {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = expr { if let Expr::Tuple(tuple) = expr {
elts.iter().all(is_constant) tuple.iter().all(is_constant)
} else { } else {
expr.is_literal_expr() expr.is_literal_expr()
} }
@ -630,8 +630,8 @@ pub fn extract_handled_exceptions(handlers: &[ExceptHandler]) -> Vec<&Expr> {
match handler { match handler {
ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, .. }) => { ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, .. }) => {
if let Some(type_) = type_ { if let Some(type_) = type_ {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_.as_ref() { if let Expr::Tuple(tuple) = &**type_ {
for type_ in elts { for type_ in tuple {
handled_exceptions.push(type_); handled_exceptions.push(type_);
} }
} else { } else {
@ -1185,8 +1185,8 @@ impl Truthiness {
Self::Truthy Self::Truthy
} }
} }
Expr::Dict(ast::ExprDict { items, .. }) => { Expr::Dict(dict) => {
if items.is_empty() { if dict.is_empty() {
Self::Falsey Self::Falsey
} else { } else {
Self::Truthy Self::Truthy

View file

@ -856,6 +856,27 @@ impl ExprDict {
pub fn value(&self, n: usize) -> &Expr { pub fn value(&self, n: usize) -> &Expr {
self.items[n].value() 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 { impl From<ExprDict> for Expr {
@ -955,6 +976,29 @@ pub struct ExprSet {
pub elts: Vec<Expr>, 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 { impl From<ExprSet> for Expr {
fn from(payload: ExprSet) -> Self { fn from(payload: ExprSet) -> Self {
Expr::Set(payload) Expr::Set(payload)
@ -2759,6 +2803,29 @@ pub struct ExprList {
pub ctx: ExprContext, 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 { impl From<ExprList> for Expr {
fn from(payload: ExprList) -> Self { fn from(payload: ExprList) -> Self {
Expr::List(payload) Expr::List(payload)
@ -2776,6 +2843,29 @@ pub struct ExprTuple {
pub parenthesized: bool, 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 { impl From<ExprTuple> for Expr {
fn from(payload: ExprTuple) -> Self { fn from(payload: ExprTuple) -> Self {
Expr::Tuple(payload) Expr::Tuple(payload)

View file

@ -921,10 +921,10 @@ impl<'a> Generator<'a> {
self.unparse_expr(orelse, precedence::IF_EXP); self.unparse_expr(orelse, precedence::IF_EXP);
}); });
} }
Expr::Dict(ast::ExprDict { items, range: _ }) => { Expr::Dict(dict) => {
self.p("{"); self.p("{");
let mut first = true; let mut first = true;
for ast::DictItem { key, value } in items { for ast::DictItem { key, value } in dict {
self.p_delim(&mut first, ", "); self.p_delim(&mut first, ", ");
if let Some(key) = key { if let Some(key) = key {
self.unparse_expr(key, precedence::COMMA); self.unparse_expr(key, precedence::COMMA);
@ -937,15 +937,15 @@ impl<'a> Generator<'a> {
} }
self.p("}"); self.p("}");
} }
Expr::Set(ast::ExprSet { elts, range: _ }) => { Expr::Set(set) => {
if elts.is_empty() { if set.is_empty() {
self.p("set()"); self.p("set()");
} else { } else {
self.p("{"); self.p("{");
let mut first = true; let mut first = true;
for v in elts { for item in set {
self.p_delim(&mut first, ", "); self.p_delim(&mut first, ", ");
self.unparse_expr(v, precedence::COMMA); self.unparse_expr(item, precedence::COMMA);
} }
self.p("}"); self.p("}");
} }
@ -1164,26 +1164,26 @@ impl<'a> Generator<'a> {
self.unparse_expr(value, precedence::MAX); self.unparse_expr(value, precedence::MAX);
} }
Expr::Name(ast::ExprName { id, .. }) => self.p(id.as_str()), Expr::Name(ast::ExprName { id, .. }) => self.p(id.as_str()),
Expr::List(ast::ExprList { elts, .. }) => { Expr::List(list) => {
self.p("["); self.p("[");
let mut first = true; let mut first = true;
for elt in elts { for item in list {
self.p_delim(&mut first, ", "); self.p_delim(&mut first, ", ");
self.unparse_expr(elt, precedence::COMMA); self.unparse_expr(item, precedence::COMMA);
} }
self.p("]"); self.p("]");
} }
Expr::Tuple(ast::ExprTuple { elts, .. }) => { Expr::Tuple(tuple) => {
if elts.is_empty() { if tuple.is_empty() {
self.p("()"); self.p("()");
} else { } else {
group_if!(precedence::TUPLE, { group_if!(precedence::TUPLE, {
let mut first = true; let mut first = true;
for elt in elts { for item in tuple {
self.p_delim(&mut first, ", "); 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 => { TupleParentheses::NeverPreserve => {
optional_parentheses(&ExprSequence::new(item)).fmt(f) 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) optional_parentheses(&ExprSequence::new(item)).fmt(f)
} }
TupleParentheses::Default | TupleParentheses::OptionalParentheses => { 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) Some(OwnParentheses::NonEmpty)
} else { } else {
Some(OwnParentheses::Empty) Some(OwnParentheses::Empty)
@ -1216,10 +1216,10 @@ pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) -
| Expr::YieldFrom(_) => true, | Expr::YieldFrom(_) => true,
// Sequence types can split if they contain at least one element. // Sequence types can split if they contain at least one element.
Expr::Tuple(tuple) => !tuple.elts.is_empty(), Expr::Tuple(tuple) => !tuple.is_empty(),
Expr::Dict(dict) => !dict.items.is_empty(), Expr::Dict(dict) => !dict.is_empty(),
Expr::Set(set) => !set.elts.is_empty(), Expr::Set(set) => !set.is_empty(),
Expr::List(list) => !list.elts.is_empty(), Expr::List(list) => !list.is_empty(),
Expr::UnaryOp(unary) => is_splittable_expression(unary.operand.as_ref(), context), Expr::UnaryOp(unary) => is_splittable_expression(unary.operand.as_ref(), context),
Expr::Yield(ast::ExprYield { value, .. }) => value.is_some(), 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 { fn quoted_annotation(slice: &Expr) -> bool {
match slice { match slice {
Expr::StringLiteral(_) => true, Expr::StringLiteral(_) => true,
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().any(quoted_annotation), Expr::Tuple(tuple) => tuple.iter().any(quoted_annotation),
_ => false, _ => false,
} }
} }
@ -160,7 +160,7 @@ pub fn to_pep604_operator(
fn starred_annotation(slice: &Expr) -> bool { fn starred_annotation(slice: &Expr) -> bool {
match slice { match slice {
Expr::Starred(_) => true, Expr::Starred(_) => true,
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().any(starred_annotation), Expr::Tuple(tuple) => tuple.iter().any(starred_annotation),
_ => false, _ => false,
} }
} }
@ -237,9 +237,9 @@ pub fn is_immutable_annotation(
if is_immutable_generic_type(qualified_name.segments()) { if is_immutable_generic_type(qualified_name.segments()) {
true true
} else if matches!(qualified_name.segments(), ["typing", "Union"]) { } else if matches!(qualified_name.segments(), ["typing", "Union"]) {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() { if let Expr::Tuple(tuple) = &**slice {
elts.iter().all(|elt| { tuple.iter().all(|element| {
is_immutable_annotation(elt, semantic, extend_immutable_calls) is_immutable_annotation(element, semantic, extend_immutable_calls)
}) })
} else { } else {
false false
@ -399,11 +399,12 @@ where
// Ex) Union[x, y] // Ex) Union[x, y]
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if semantic.match_typing_expr(value, "Union") { 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 // Traverse each element of the tuple within the union recursively to handle cases
// such as `Union[..., Union[...]] // such as `Union[..., Union[...]]
elts.iter() tuple
.for_each(|elt| inner(func, semantic, elt, Some(expr))); .iter()
.for_each(|elem| inner(func, semantic, elem, Some(expr)));
return; return;
} }
} }
@ -438,11 +439,11 @@ where
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if semantic.match_typing_expr(value, "Literal") { if semantic.match_typing_expr(value, "Literal") {
match &**slice { match &**slice {
Expr::Tuple(ast::ExprTuple { elts, .. }) => { Expr::Tuple(tuple) => {
// Traverse each element of the tuple within the literal recursively to handle cases // Traverse each element of the tuple within the literal recursively to handle cases
// such as `Literal[..., Literal[...]] // such as `Literal[..., Literal[...]]
for elt in elts { for element in tuple {
inner(func, semantic, elt, Some(expr)); inner(func, semantic, element, Some(expr));
} }
} }
other => { other => {

View file

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