]> git.proxmox.com Git - rustc.git/blame - src/tools/rustfmt/src/patterns.rs
New upstream version 1.53.0+dfsg1
[rustc.git] / src / tools / rustfmt / src / patterns.rs
CommitLineData
cdc7bbd5 1use rustc_ast::ast::{self, BindingMode, Pat, PatField, PatKind, RangeEnd, RangeSyntax};
f20569fa
XL
2use rustc_ast::ptr;
3use rustc_span::{BytePos, Span};
4
5use crate::comment::{combine_strs_with_missing_comments, FindUncommented};
6use crate::config::lists::*;
7use crate::expr::{can_be_overflowed_expr, rewrite_unary_prefix, wrap_struct_field};
8use crate::lists::{
9 definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape,
10 struct_lit_tactic, write_list, ListFormatting, ListItem, Separator,
11};
12use crate::macros::{rewrite_macro, MacroPosition};
13use crate::overflow;
14use crate::pairs::{rewrite_pair, PairParts};
15use crate::rewrite::{Rewrite, RewriteContext};
16use crate::shape::Shape;
17use crate::source_map::SpanUtils;
18use crate::spanned::Spanned;
19use crate::types::{rewrite_path, PathContext};
cdc7bbd5 20use crate::utils::{format_mutability, mk_sp, mk_sp_lo_plus_one, rewrite_ident};
f20569fa
XL
21
22/// Returns `true` if the given pattern is "short".
23/// A short pattern is defined by the following grammar:
24///
25/// [small, ntp]:
26/// - single token
27/// - `&[single-line, ntp]`
28///
29/// [small]:
30/// - `[small, ntp]`
31/// - unary tuple constructor `([small, ntp])`
32/// - `&[small]`
33pub(crate) fn is_short_pattern(pat: &ast::Pat, pat_str: &str) -> bool {
34 // We also require that the pattern is reasonably 'small' with its literal width.
35 pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(pat)
36}
37
38fn is_short_pattern_inner(pat: &ast::Pat) -> bool {
39 match pat.kind {
40 ast::PatKind::Rest | ast::PatKind::Wild | ast::PatKind::Lit(_) => true,
41 ast::PatKind::Ident(_, _, ref pat) => pat.is_none(),
42 ast::PatKind::Struct(..)
43 | ast::PatKind::MacCall(..)
44 | ast::PatKind::Slice(..)
45 | ast::PatKind::Path(..)
46 | ast::PatKind::Range(..) => false,
47 ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1,
48 ast::PatKind::TupleStruct(ref path, ref subpats) => {
49 path.segments.len() <= 1 && subpats.len() <= 1
50 }
51 ast::PatKind::Box(ref p) | ast::PatKind::Ref(ref p, _) | ast::PatKind::Paren(ref p) => {
52 is_short_pattern_inner(&*p)
53 }
54 PatKind::Or(ref pats) => pats.iter().all(|p| is_short_pattern_inner(p)),
55 }
56}
57
58struct RangeOperand<'a>(&'a Option<ptr::P<ast::Expr>>);
59
60impl<'a> Rewrite for RangeOperand<'a> {
61 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
62 match &self.0 {
63 None => Some("".to_owned()),
64 Some(ref exp) => exp.rewrite(context, shape),
65 }
66 }
67}
68
69impl Rewrite for Pat {
70 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
71 match self.kind {
72 PatKind::Or(ref pats) => {
73 let pat_strs = pats
74 .iter()
75 .map(|p| p.rewrite(context, shape))
76 .collect::<Option<Vec<_>>>()?;
77
78 let use_mixed_layout = pats
79 .iter()
80 .zip(pat_strs.iter())
81 .all(|(pat, pat_str)| is_short_pattern(pat, pat_str));
82 let items: Vec<_> = pat_strs.into_iter().map(ListItem::from_str).collect();
83 let tactic = if use_mixed_layout {
84 DefinitiveListTactic::Mixed
85 } else {
86 definitive_tactic(
87 &items,
88 ListTactic::HorizontalVertical,
89 Separator::VerticalBar,
90 shape.width,
91 )
92 };
93 let fmt = ListFormatting::new(shape, context.config)
94 .tactic(tactic)
95 .separator(" |")
96 .separator_place(context.config.binop_separator())
97 .ends_with_newline(false);
98 write_list(&items, &fmt)
99 }
100 PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape),
101 PatKind::Ident(binding_mode, ident, ref sub_pat) => {
102 let (prefix, mutability) = match binding_mode {
103 BindingMode::ByRef(mutability) => ("ref", mutability),
104 BindingMode::ByValue(mutability) => ("", mutability),
105 };
106 let mut_infix = format_mutability(mutability).trim();
107 let id_str = rewrite_ident(context, ident);
108 let sub_pat = match *sub_pat {
109 Some(ref p) => {
110 // 2 - `@ `.
111 let width = shape
112 .width
113 .checked_sub(prefix.len() + mut_infix.len() + id_str.len() + 2)?;
114 let lo = context.snippet_provider.span_after(self.span, "@");
115 combine_strs_with_missing_comments(
116 context,
117 "@",
118 &p.rewrite(context, Shape::legacy(width, shape.indent))?,
119 mk_sp(lo, p.span.lo()),
120 shape,
121 true,
122 )?
123 }
124 None => "".to_owned(),
125 };
126
127 // combine prefix and mut
128 let (first_lo, first) = if !prefix.is_empty() && !mut_infix.is_empty() {
129 let hi = context.snippet_provider.span_before(self.span, "mut");
130 let lo = context.snippet_provider.span_after(self.span, "ref");
131 (
132 context.snippet_provider.span_after(self.span, "mut"),
133 combine_strs_with_missing_comments(
134 context,
135 prefix,
136 mut_infix,
137 mk_sp(lo, hi),
138 shape,
139 true,
140 )?,
141 )
142 } else if !prefix.is_empty() {
143 (
144 context.snippet_provider.span_after(self.span, "ref"),
145 prefix.to_owned(),
146 )
147 } else if !mut_infix.is_empty() {
148 (
149 context.snippet_provider.span_after(self.span, "mut"),
150 mut_infix.to_owned(),
151 )
152 } else {
153 (self.span.lo(), "".to_owned())
154 };
155
156 let next = if !sub_pat.is_empty() {
157 let hi = context.snippet_provider.span_before(self.span, "@");
158 combine_strs_with_missing_comments(
159 context,
160 id_str,
161 &sub_pat,
162 mk_sp(ident.span.hi(), hi),
163 shape,
164 true,
165 )?
166 } else {
167 id_str.to_owned()
168 };
169
170 combine_strs_with_missing_comments(
171 context,
172 &first,
173 &next,
174 mk_sp(first_lo, ident.span.lo()),
175 shape,
176 true,
177 )
178 }
179 PatKind::Wild => {
180 if 1 <= shape.width {
181 Some("_".to_owned())
182 } else {
183 None
184 }
185 }
186 PatKind::Rest => {
187 if 1 <= shape.width {
188 Some("..".to_owned())
189 } else {
190 None
191 }
192 }
193 PatKind::Range(ref lhs, ref rhs, ref end_kind) => {
194 let infix = match end_kind.node {
195 RangeEnd::Included(RangeSyntax::DotDotDot) => "...",
196 RangeEnd::Included(RangeSyntax::DotDotEq) => "..=",
197 RangeEnd::Excluded => "..",
198 };
199 let infix = if context.config.spaces_around_ranges() {
200 let lhs_spacing = match lhs {
201 None => "",
202 Some(_) => " ",
203 };
204 let rhs_spacing = match rhs {
205 None => "",
206 Some(_) => " ",
207 };
208 format!("{}{}{}", lhs_spacing, infix, rhs_spacing)
209 } else {
210 infix.to_owned()
211 };
212 rewrite_pair(
213 &RangeOperand(lhs),
214 &RangeOperand(rhs),
215 PairParts::infix(&infix),
216 context,
217 shape,
218 SeparatorPlace::Front,
219 )
220 }
221 PatKind::Ref(ref pat, mutability) => {
222 let prefix = format!("&{}", format_mutability(mutability));
223 rewrite_unary_prefix(context, &prefix, &**pat, shape)
224 }
225 PatKind::Tuple(ref items) => rewrite_tuple_pat(items, None, self.span, context, shape),
226 PatKind::Path(ref q_self, ref path) => {
227 rewrite_path(context, PathContext::Expr, q_self.as_ref(), path, shape)
228 }
229 PatKind::TupleStruct(ref path, ref pat_vec) => {
230 let path_str = rewrite_path(context, PathContext::Expr, None, path, shape)?;
231 rewrite_tuple_pat(pat_vec, Some(path_str), self.span, context, shape)
232 }
233 PatKind::Lit(ref expr) => expr.rewrite(context, shape),
234 PatKind::Slice(ref slice_pat) => {
235 let rw: Vec<String> = slice_pat
236 .iter()
237 .map(|p| {
238 if let Some(rw) = p.rewrite(context, shape) {
239 rw
240 } else {
241 format!("{}", context.snippet(p.span))
242 }
243 })
244 .collect();
245 Some(format!("[{}]", rw.join(", ")))
246 }
247 PatKind::Struct(ref path, ref fields, ellipsis) => {
248 rewrite_struct_pat(path, fields, ellipsis, self.span, context, shape)
249 }
250 PatKind::MacCall(ref mac) => {
251 rewrite_macro(mac, None, context, shape, MacroPosition::Pat)
252 }
253 PatKind::Paren(ref pat) => pat
254 .rewrite(context, shape.offset_left(1)?.sub_width(1)?)
255 .map(|inner_pat| format!("({})", inner_pat)),
256 }
257 }
258}
259
260fn rewrite_struct_pat(
261 path: &ast::Path,
cdc7bbd5 262 fields: &[ast::PatField],
f20569fa
XL
263 ellipsis: bool,
264 span: Span,
265 context: &RewriteContext<'_>,
266 shape: Shape,
267) -> Option<String> {
268 // 2 = ` {`
269 let path_shape = shape.sub_width(2)?;
270 let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?;
271
272 if fields.is_empty() && !ellipsis {
273 return Some(format!("{} {{}}", path_str));
274 }
275
276 let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") };
277
278 // 3 = ` { `, 2 = ` }`.
279 let (h_shape, v_shape) =
280 struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)?;
281
282 let items = itemize_list(
283 context.snippet_provider,
284 fields.iter(),
285 terminator,
286 ",",
287 |f| {
288 if f.attrs.is_empty() {
289 f.span.lo()
290 } else {
291 f.attrs.first().unwrap().span.lo()
292 }
293 },
294 |f| f.span.hi(),
295 |f| f.rewrite(context, v_shape),
296 context.snippet_provider.span_after(span, "{"),
297 span.hi(),
298 false,
299 );
300 let item_vec = items.collect::<Vec<_>>();
301
302 let tactic = struct_lit_tactic(h_shape, context, &item_vec);
303 let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
304 let fmt = struct_lit_formatting(nested_shape, tactic, context, false);
305
306 let mut fields_str = write_list(&item_vec, &fmt)?;
307 let one_line_width = h_shape.map_or(0, |shape| shape.width);
308
309 if ellipsis {
310 if fields_str.contains('\n') || fields_str.len() > one_line_width {
311 // Add a missing trailing comma.
312 if context.config.trailing_comma() == SeparatorTactic::Never {
313 fields_str.push_str(",");
314 }
315 fields_str.push_str("\n");
316 fields_str.push_str(&nested_shape.indent.to_string(context.config));
317 fields_str.push_str("..");
318 } else {
319 if !fields_str.is_empty() {
320 // there are preceding struct fields being matched on
321 if tactic == DefinitiveListTactic::Vertical {
322 // if the tactic is Vertical, write_list already added a trailing ,
323 fields_str.push_str(" ");
324 } else {
325 fields_str.push_str(", ");
326 }
327 }
328 fields_str.push_str("..");
329 }
330 }
331
332 // ast::Pat doesn't have attrs so use &[]
333 let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?;
334 Some(format!("{} {{{}}}", path_str, fields_str))
335}
336
cdc7bbd5 337impl Rewrite for PatField {
f20569fa
XL
338 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
339 let hi_pos = if let Some(last) = self.attrs.last() {
340 last.span.hi()
341 } else {
342 self.pat.span.lo()
343 };
344
345 let attrs_str = if self.attrs.is_empty() {
346 String::from("")
347 } else {
348 self.attrs.rewrite(context, shape)?
349 };
350
351 let pat_str = self.pat.rewrite(context, shape)?;
352 if self.is_shorthand {
353 combine_strs_with_missing_comments(
354 context,
355 &attrs_str,
356 &pat_str,
357 mk_sp(hi_pos, self.pat.span.lo()),
358 shape,
359 false,
360 )
361 } else {
362 let nested_shape = shape.block_indent(context.config.tab_spaces());
363 let id_str = rewrite_ident(context, self.ident);
364 let one_line_width = id_str.len() + 2 + pat_str.len();
365 let pat_and_id_str = if one_line_width <= shape.width {
366 format!("{}: {}", id_str, pat_str)
367 } else {
368 format!(
369 "{}:\n{}{}",
370 id_str,
371 nested_shape.indent.to_string(context.config),
372 self.pat.rewrite(context, nested_shape)?
373 )
374 };
375 combine_strs_with_missing_comments(
376 context,
377 &attrs_str,
378 &pat_and_id_str,
379 mk_sp(hi_pos, self.pat.span.lo()),
380 nested_shape,
381 false,
382 )
383 }
384 }
385}
386
387#[derive(Debug)]
388pub(crate) enum TuplePatField<'a> {
389 Pat(&'a ptr::P<ast::Pat>),
390 Dotdot(Span),
391}
392
393impl<'a> Rewrite for TuplePatField<'a> {
394 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
395 match *self {
396 TuplePatField::Pat(p) => p.rewrite(context, shape),
397 TuplePatField::Dotdot(_) => Some("..".to_string()),
398 }
399 }
400}
401
402impl<'a> Spanned for TuplePatField<'a> {
403 fn span(&self) -> Span {
404 match *self {
405 TuplePatField::Pat(p) => p.span(),
406 TuplePatField::Dotdot(span) => span,
407 }
408 }
409}
410
411impl<'a> TuplePatField<'a> {
412 fn is_dotdot(&self) -> bool {
413 match self {
414 TuplePatField::Pat(pat) => match pat.kind {
415 ast::PatKind::Rest => true,
416 _ => false,
417 },
418 TuplePatField::Dotdot(_) => true,
419 }
420 }
421}
422
423pub(crate) fn can_be_overflowed_pat(
424 context: &RewriteContext<'_>,
425 pat: &TuplePatField<'_>,
426 len: usize,
427) -> bool {
428 match *pat {
429 TuplePatField::Pat(pat) => match pat.kind {
430 ast::PatKind::Path(..)
431 | ast::PatKind::Tuple(..)
432 | ast::PatKind::Struct(..)
433 | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1,
434 ast::PatKind::Ref(ref p, _) | ast::PatKind::Box(ref p) => {
435 can_be_overflowed_pat(context, &TuplePatField::Pat(p), len)
436 }
437 ast::PatKind::Lit(ref expr) => can_be_overflowed_expr(context, expr, len),
438 _ => false,
439 },
440 TuplePatField::Dotdot(..) => false,
441 }
442}
443
444fn rewrite_tuple_pat(
445 pats: &[ptr::P<ast::Pat>],
446 path_str: Option<String>,
447 span: Span,
448 context: &RewriteContext<'_>,
449 shape: Shape,
450) -> Option<String> {
451 let mut pat_vec: Vec<_> = pats.iter().map(|x| TuplePatField::Pat(x)).collect();
452
453 if pat_vec.is_empty() {
454 return Some(format!("{}()", path_str.unwrap_or_default()));
455 }
456 let wildcard_suffix_len = count_wildcard_suffix_len(context, &pat_vec, span, shape);
457 let (pat_vec, span) = if context.config.condense_wildcard_suffixes() && wildcard_suffix_len >= 2
458 {
459 let new_item_count = 1 + pat_vec.len() - wildcard_suffix_len;
460 let sp = pat_vec[new_item_count - 1].span();
461 let snippet = context.snippet(sp);
462 let lo = sp.lo() + BytePos(snippet.find_uncommented("_").unwrap() as u32);
cdc7bbd5 463 pat_vec[new_item_count - 1] = TuplePatField::Dotdot(mk_sp_lo_plus_one(lo));
f20569fa
XL
464 (
465 &pat_vec[..new_item_count],
466 mk_sp(span.lo(), lo + BytePos(1)),
467 )
468 } else {
469 (&pat_vec[..], span)
470 };
471
472 let is_last_pat_dotdot = pat_vec.last().map_or(false, |p| p.is_dotdot());
473 let add_comma = path_str.is_none() && pat_vec.len() == 1 && !is_last_pat_dotdot;
474 let path_str = path_str.unwrap_or_default();
475
476 overflow::rewrite_with_parens(
477 &context,
478 &path_str,
479 pat_vec.iter(),
480 shape,
481 span,
482 context.config.max_width(),
483 if add_comma {
484 Some(SeparatorTactic::Always)
485 } else {
486 None
487 },
488 )
489}
490
491fn count_wildcard_suffix_len(
492 context: &RewriteContext<'_>,
493 patterns: &[TuplePatField<'_>],
494 span: Span,
495 shape: Shape,
496) -> usize {
497 let mut suffix_len = 0;
498
499 let items: Vec<_> = itemize_list(
500 context.snippet_provider,
501 patterns.iter(),
502 ")",
503 ",",
504 |item| item.span().lo(),
505 |item| item.span().hi(),
506 |item| item.rewrite(context, shape),
507 context.snippet_provider.span_after(span, "("),
508 span.hi() - BytePos(1),
509 false,
510 )
511 .collect();
512
513 for item in items.iter().rev().take_while(|i| match i.item {
514 Some(ref internal_string) if internal_string == "_" => true,
515 _ => false,
516 }) {
517 suffix_len += 1;
518
519 if item.has_comment() {
520 break;
521 }
522 }
523
524 suffix_len
525}