Rollup merge of #145783 - Erk-:et-cetera-span, r=compiler-errors

add span to struct pattern rest (..)

Struct pattern rest (`..`) did not retain span information compared to normal fields. This patch adds span information for it.

The motivation of this patch comes from when I implemented this PR for Clippy: https://github.com/rust-lang/rust-clippy/pull/15000#discussion_r2134145163

It is possible to get the span of the Et cetera in a bit roundabout way, but I thought this would be nicer.
This commit is contained in:
Guillaume Gomez
2025-09-02 17:08:52 +02:00
committed by GitHub
18 changed files with 50 additions and 40 deletions

View File

@@ -937,7 +937,7 @@ pub enum PatKind {
#[derive(Clone, Copy, Encodable, Decodable, Debug, PartialEq, Walkable)] #[derive(Clone, Copy, Encodable, Decodable, Debug, PartialEq, Walkable)]
pub enum PatFieldsRest { pub enum PatFieldsRest {
/// `module::StructName { field, ..}` /// `module::StructName { field, ..}`
Rest, Rest(Span),
/// `module::StructName { field, syntax error }` /// `module::StructName { field, syntax error }`
Recovered(ErrorGuaranteed), Recovered(ErrorGuaranteed),
/// `module::StructName { field }` /// `module::StructName { field }`
@@ -4051,8 +4051,8 @@ mod size_asserts {
static_assert_size!(Local, 96); static_assert_size!(Local, 96);
static_assert_size!(MetaItemLit, 40); static_assert_size!(MetaItemLit, 40);
static_assert_size!(Param, 40); static_assert_size!(Param, 40);
static_assert_size!(Pat, 72); static_assert_size!(Pat, 80);
static_assert_size!(PatKind, 48); static_assert_size!(PatKind, 56);
static_assert_size!(Path, 24); static_assert_size!(Path, 24);
static_assert_size!(PathSegment, 24); static_assert_size!(PathSegment, 24);
static_assert_size!(Stmt, 32); static_assert_size!(Stmt, 32);

View File

@@ -1434,10 +1434,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
self.dcx().emit_err(FunctionalRecordUpdateDestructuringAssignment { self.dcx().emit_err(FunctionalRecordUpdateDestructuringAssignment {
span: e.span, span: e.span,
}); });
true Some(self.lower_span(e.span))
} }
StructRest::Rest(_) => true, StructRest::Rest(span) => Some(self.lower_span(*span)),
StructRest::None => false, StructRest::None => None,
}; };
let struct_pat = hir::PatKind::Struct(qpath, field_pats, fields_omitted); let struct_pat = hir::PatKind::Struct(qpath, field_pats, fields_omitted);
return self.pat_without_dbm(lhs.span, struct_pat); return self.pat_without_dbm(lhs.span, struct_pat);

View File

@@ -2508,7 +2508,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
fields: &'hir [hir::PatField<'hir>], fields: &'hir [hir::PatField<'hir>],
) -> &'hir hir::Pat<'hir> { ) -> &'hir hir::Pat<'hir> {
let qpath = hir::QPath::LangItem(lang_item, self.lower_span(span)); let qpath = hir::QPath::LangItem(lang_item, self.lower_span(span));
self.pat(span, hir::PatKind::Struct(qpath, fields, false)) self.pat(span, hir::PatKind::Struct(qpath, fields, None))
} }
fn pat_ident(&mut self, span: Span, ident: Ident) -> (&'hir hir::Pat<'hir>, HirId) { fn pat_ident(&mut self, span: Span, ident: Ident) -> (&'hir hir::Pat<'hir>, HirId) {

View File

@@ -106,10 +106,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
break hir::PatKind::Struct( break hir::PatKind::Struct(
qpath, qpath,
fs, fs,
matches!( match etc {
etc, ast::PatFieldsRest::Rest(sp) => Some(self.lower_span(*sp)),
ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) ast::PatFieldsRest::Recovered(_) => Some(Span::default()),
), _ => None,
},
); );
} }
PatKind::Tuple(pats) => { PatKind::Tuple(pats) => {

View File

@@ -1769,7 +1769,7 @@ impl<'a> State<'a> {
}, },
|f| f.pat.span, |f| f.pat.span,
); );
if let ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) = etc { if let ast::PatFieldsRest::Rest(_) | ast::PatFieldsRest::Recovered(_) = etc {
if !fields.is_empty() { if !fields.is_empty() {
self.word_space(","); self.word_space(",");
} }

View File

@@ -1884,8 +1884,8 @@ pub enum PatKind<'hir> {
Binding(BindingMode, HirId, Ident, Option<&'hir Pat<'hir>>), Binding(BindingMode, HirId, Ident, Option<&'hir Pat<'hir>>),
/// A struct or struct variant pattern (e.g., `Variant {x, y, ..}`). /// A struct or struct variant pattern (e.g., `Variant {x, y, ..}`).
/// The `bool` is `true` in the presence of a `..`. /// The `Option` contains the span of a possible `..`.
Struct(QPath<'hir>, &'hir [PatField<'hir>], bool), Struct(QPath<'hir>, &'hir [PatField<'hir>], Option<Span>),
/// A tuple struct/variant pattern `Variant(x, y, .., z)`. /// A tuple struct/variant pattern `Variant(x, y, .., z)`.
/// If the `..` pattern fragment is present, then `DotDotPos` denotes its position. /// If the `..` pattern fragment is present, then `DotDotPos` denotes its position.
@@ -4979,8 +4979,8 @@ mod size_asserts {
static_assert_size!(ItemKind<'_>, 64); static_assert_size!(ItemKind<'_>, 64);
static_assert_size!(LetStmt<'_>, 72); static_assert_size!(LetStmt<'_>, 72);
static_assert_size!(Param<'_>, 32); static_assert_size!(Param<'_>, 32);
static_assert_size!(Pat<'_>, 72); static_assert_size!(Pat<'_>, 80);
static_assert_size!(PatKind<'_>, 48); static_assert_size!(PatKind<'_>, 56);
static_assert_size!(Path<'_>, 40); static_assert_size!(Path<'_>, 40);
static_assert_size!(PathSegment<'_>, 48); static_assert_size!(PathSegment<'_>, 48);
static_assert_size!(QPath<'_>, 24); static_assert_size!(QPath<'_>, 24);

View File

@@ -1958,12 +1958,12 @@ impl<'a> State<'a> {
self.print_qpath(qpath, true); self.print_qpath(qpath, true);
self.nbsp(); self.nbsp();
self.word("{"); self.word("{");
let empty = fields.is_empty() && !etc; let empty = fields.is_empty() && etc.is_none();
if !empty { if !empty {
self.space(); self.space();
} }
self.commasep_cmnt(Consistent, fields, |s, f| s.print_patfield(f), |f| f.pat.span); self.commasep_cmnt(Consistent, fields, |s, f| s.print_patfield(f), |f| f.pat.span);
if etc { if etc.is_some() {
if !fields.is_empty() { if !fields.is_empty() {
self.word_space(","); self.word_space(",");
} }

View File

@@ -605,7 +605,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}, },
PatKind::Struct(_, fields, has_rest_pat) => match opt_path_res.unwrap() { PatKind::Struct(_, fields, has_rest_pat) => match opt_path_res.unwrap() {
Ok(ResolvedPat { ty, kind: ResolvedPatKind::Struct { variant } }) => self Ok(ResolvedPat { ty, kind: ResolvedPatKind::Struct { variant } }) => self
.check_pat_struct(pat, fields, has_rest_pat, ty, variant, expected, pat_info), .check_pat_struct(
pat,
fields,
has_rest_pat.is_some(),
ty,
variant,
expected,
pat_info,
),
Err(guar) => { Err(guar) => {
let ty_err = Ty::new_error(self.tcx, guar); let ty_err = Ty::new_error(self.tcx, guar);
for field in fields { for field in fields {
@@ -2428,7 +2436,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let len = unmentioned_fields.len(); let len = unmentioned_fields.len();
let (prefix, postfix, sp) = match fields { let (prefix, postfix, sp) = match fields {
[] => match &pat.kind { [] => match &pat.kind {
PatKind::Struct(path, [], false) => { PatKind::Struct(path, [], None) => {
(" { ", " }", path.span().shrink_to_hi().until(pat.span.shrink_to_hi())) (" { ", " }", path.span().shrink_to_hi().until(pat.span.shrink_to_hi()))
} }
_ => return err, _ => return err,

View File

@@ -1516,7 +1516,7 @@ impl<'a> Parser<'a> {
|| self.check_noexpect(&token::DotDotDot) || self.check_noexpect(&token::DotDotDot)
|| self.check_keyword(exp!(Underscore)) || self.check_keyword(exp!(Underscore))
{ {
etc = PatFieldsRest::Rest; etc = PatFieldsRest::Rest(self.token.span);
let mut etc_sp = self.token.span; let mut etc_sp = self.token.span;
if first_etc_and_maybe_comma_span.is_none() { if first_etc_and_maybe_comma_span.is_none() {
if let Some(comma_tok) = if let Some(comma_tok) =

View File

@@ -1583,7 +1583,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
}); });
let can_remove = match pat.kind { let can_remove = match pat.kind {
hir::PatKind::Struct(_, fields, true) => { hir::PatKind::Struct(_, fields, Some(_)) => {
// if all fields are shorthand, remove the struct field, otherwise, mark with _ as prefix // if all fields are shorthand, remove the struct field, otherwise, mark with _ as prefix
fields.iter().all(|f| f.is_shorthand) fields.iter().all(|f| f.is_shorthand)
} }

View File

@@ -3922,7 +3922,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
fn record_patterns_with_skipped_bindings(&mut self, pat: &Pat, rest: &ast::PatFieldsRest) { fn record_patterns_with_skipped_bindings(&mut self, pat: &Pat, rest: &ast::PatFieldsRest) {
match rest { match rest {
ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) => { ast::PatFieldsRest::Rest(_) | ast::PatFieldsRest::Recovered(_) => {
// Record that the pattern doesn't introduce all the bindings it could. // Record that the pattern doesn't introduce all the bindings it could.
if let Some(partial_res) = self.r.partial_res_map.get(&pat.id) if let Some(partial_res) = self.r.partial_res_map.get(&pat.id)
&& let Some(res) = partial_res.full_res() && let Some(res) = partial_res.full_res()

View File

@@ -53,7 +53,7 @@ fn unary_pattern(pat: &Pat<'_>) -> bool {
| PatKind::Never | PatKind::Never
| PatKind::Or(_) | PatKind::Or(_)
| PatKind::Err(_) => false, | PatKind::Err(_) => false,
PatKind::Struct(_, a, etc) => !etc && a.iter().all(|x| unary_pattern(x.pat)), PatKind::Struct(_, a, etc) => etc.is_none() && a.iter().all(|x| unary_pattern(x.pat)),
PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a), PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a),
PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x), PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x),
PatKind::Expr(_) => true, PatKind::Expr(_) => true,

View File

@@ -287,7 +287,7 @@ fn replace_in_pattern(
} }
return or_pat; return or_pat;
}, },
PatKind::Struct(path, fields, has_dot_dot) => { PatKind::Struct(path, fields, dot_dot) => {
let fields = fields let fields = fields
.iter() .iter()
.map(|fld| { .map(|fld| {
@@ -311,7 +311,7 @@ fn replace_in_pattern(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let fields_string = fields.join(", "); let fields_string = fields.join(", ");
let dot_dot_str = if has_dot_dot { " .." } else { "" }; let dot_dot_str = if dot_dot.is_some() { " .." } else { "" };
let (sn_pth, _) = snippet_with_context(cx, path.span(), span.ctxt(), "", app); let (sn_pth, _) = snippet_with_context(cx, path.span(), span.ctxt(), "", app);
return format!("{sn_pth} {{ {fields_string}{dot_dot_str} }}"); return format!("{sn_pth} {{ {fields_string}{dot_dot_str} }}");
}, },

View File

@@ -7,7 +7,7 @@ use super::REST_PAT_IN_FULLY_BOUND_STRUCTS;
pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) {
if !pat.span.from_expansion() if !pat.span.from_expansion()
&& let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind && let PatKind::Struct(QPath::Resolved(_, path), fields, Some(_)) = pat.kind
&& let Some(def_id) = path.res.opt_def_id() && let Some(def_id) = path.res.opt_def_id()
&& let ty = cx.tcx.type_of(def_id).instantiate_identity() && let ty = cx.tcx.type_of(def_id).instantiate_identity()
&& let ty::Adt(def, _) = ty.kind() && let ty::Adt(def, _) = ty.kind()

View File

@@ -754,7 +754,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
self.ident(name); self.ident(name);
sub.if_some(|p| self.pat(p)); sub.if_some(|p| self.pat(p));
}, },
PatKind::Struct(ref qpath, fields, ignore) => { PatKind::Struct(ref qpath, fields, etc) => {
let ignore = etc.is_some();
bind!(self, qpath, fields); bind!(self, qpath, fields);
kind!("Struct(ref {qpath}, {fields}, {ignore})"); kind!("Struct(ref {qpath}, {fields}, {ignore})");
self.qpath(qpath, pat); self.qpath(qpath, pat);

View File

@@ -2011,7 +2011,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
false false
} }
}, },
(PatKind::Struct(pat_ident, field_pats, false), ExprKind::Struct(ident, fields, hir::StructTailExpr::None)) (PatKind::Struct(pat_ident, field_pats, None), ExprKind::Struct(ident, fields, hir::StructTailExpr::None))
if field_pats.len() == fields.len() => if field_pats.len() == fields.len() =>
{ {
// check ident // check ident

View File

@@ -303,7 +303,7 @@ impl Rewrite for Pat {
qself, qself,
path, path,
fields, fields,
rest == ast::PatFieldsRest::Rest, matches!(rest, ast::PatFieldsRest::Rest(_)),
self.span, self.span,
context, context,
shape, shape,

View File

@@ -23,10 +23,10 @@ ast-stats - Path 72 (NN.N%) 1
ast-stats - Struct 72 (NN.N%) 1 ast-stats - Struct 72 (NN.N%) 1
ast-stats - Lit 144 (NN.N%) 2 ast-stats - Lit 144 (NN.N%) 2
ast-stats - Block 216 (NN.N%) 3 ast-stats - Block 216 (NN.N%) 3
ast-stats Pat 504 (NN.N%) 7 72 ast-stats Pat 560 (NN.N%) 7 80
ast-stats - Struct 72 (NN.N%) 1 ast-stats - Struct 80 (NN.N%) 1
ast-stats - Wild 72 (NN.N%) 1 ast-stats - Wild 80 (NN.N%) 1
ast-stats - Ident 360 (NN.N%) 5 ast-stats - Ident 400 (NN.N%) 5
ast-stats GenericParam 480 (NN.N%) 5 96 ast-stats GenericParam 480 (NN.N%) 5 96
ast-stats GenericBound 352 (NN.N%) 4 88 ast-stats GenericBound 352 (NN.N%) 4 88
ast-stats - Trait 352 (NN.N%) 4 ast-stats - Trait 352 (NN.N%) 4
@@ -57,7 +57,7 @@ ast-stats GenericArgs 40 (NN.N%) 1 40
ast-stats - AngleBracketed 40 (NN.N%) 1 ast-stats - AngleBracketed 40 (NN.N%) 1
ast-stats Crate 40 (NN.N%) 1 40 ast-stats Crate 40 (NN.N%) 1 40
ast-stats ---------------------------------------------------------------- ast-stats ----------------------------------------------------------------
ast-stats Total 7_472 129 ast-stats Total 7_528 129
ast-stats ================================================================ ast-stats ================================================================
hir-stats ================================================================ hir-stats ================================================================
hir-stats HIR STATS: input_stats hir-stats HIR STATS: input_stats
@@ -85,11 +85,11 @@ hir-stats - Ptr 48 (NN.N%) 1
hir-stats - Ref 48 (NN.N%) 1 hir-stats - Ref 48 (NN.N%) 1
hir-stats - Path 624 (NN.N%) 13 hir-stats - Path 624 (NN.N%) 13
hir-stats Generics 560 (NN.N%) 10 56 hir-stats Generics 560 (NN.N%) 10 56
hir-stats Pat 400 (NN.N%) 5 80
hir-stats - Struct 80 (NN.N%) 1
hir-stats - Wild 80 (NN.N%) 1
hir-stats - Binding 240 (NN.N%) 3
hir-stats GenericParam 400 (NN.N%) 5 80 hir-stats GenericParam 400 (NN.N%) 5 80
hir-stats Pat 360 (NN.N%) 5 72
hir-stats - Struct 72 (NN.N%) 1
hir-stats - Wild 72 (NN.N%) 1
hir-stats - Binding 216 (NN.N%) 3
hir-stats Block 288 (NN.N%) 6 48 hir-stats Block 288 (NN.N%) 6 48
hir-stats GenericBound 256 (NN.N%) 4 64 hir-stats GenericBound 256 (NN.N%) 4 64
hir-stats - Trait 256 (NN.N%) 4 hir-stats - Trait 256 (NN.N%) 4
@@ -119,5 +119,5 @@ hir-stats TraitItemId 8 (NN.N%) 2 4
hir-stats ImplItemId 8 (NN.N%) 2 4 hir-stats ImplItemId 8 (NN.N%) 2 4
hir-stats ForeignItemId 4 (NN.N%) 1 4 hir-stats ForeignItemId 4 (NN.N%) 1 4
hir-stats ---------------------------------------------------------------- hir-stats ----------------------------------------------------------------
hir-stats Total 8_584 173 hir-stats Total 8_624 173
hir-stats ================================================================ hir-stats ================================================================