Skip to main content

rustfmt_nightly/
reorder.rs

1//! Reorder items.
2//!
3//! `mod`, `extern crate` and `use` declarations are reordered in alphabetical
4//! order. Trait items are reordered in pre-determined order (associated types
5//! and constants comes before methods).
6
7// FIXME(#2455): Reorder trait items.
8
9use std::cmp::Ordering;
10
11use rustc_ast::{ast, attr};
12use rustc_span::{Span, symbol::sym};
13
14use crate::StyleEdition;
15use crate::config::{Config, GroupImportsTactic};
16use crate::imports::{UseSegmentKind, UseTree, normalize_use_trees_with_granularity};
17use crate::items::{is_mod_decl, rewrite_extern_crate, rewrite_mod};
18use crate::lists::{ListFormatting, ListItem, itemize_list, write_list};
19use crate::rewrite::{RewriteContext, RewriteError, RewriteResult};
20use crate::shape::Shape;
21use crate::sort::version_sort;
22use crate::source_map::LineRangeUtils;
23use crate::spanned::Spanned;
24use crate::utils::{contains_skip, mk_sp};
25use crate::visitor::FmtVisitor;
26
27/// Choose the ordering between the given two items.
28fn compare_items(a: &ast::Item, b: &ast::Item, context: &RewriteContext<'_>) -> Ordering {
29    let style_edition = context.config.style_edition();
30    match (&a.kind, &b.kind) {
31        (&ast::ItemKind::Mod(_, a_ident, _), &ast::ItemKind::Mod(_, b_ident, _)) => {
32            if style_edition <= StyleEdition::Edition2024 {
33                a_ident.as_str().cmp(b_ident.as_str())
34            } else {
35                version_sort(a_ident.as_str(), b_ident.as_str())
36            }
37        }
38        (
39            &ast::ItemKind::ExternCrate(ref a_name, a_ident),
40            &ast::ItemKind::ExternCrate(ref b_name, b_ident),
41        ) => {
42            // `extern crate foo as bar;`
43            //               ^^^ Comparing this.
44            let a_orig_name = a_name.unwrap_or(a_ident.name);
45            let b_orig_name = b_name.unwrap_or(b_ident.name);
46            let result = if style_edition <= StyleEdition::Edition2024 {
47                a_orig_name.as_str().cmp(b_orig_name.as_str())
48            } else {
49                version_sort(a_orig_name.as_str(), b_orig_name.as_str())
50            };
51            if result != Ordering::Equal {
52                return result;
53            }
54
55            // `extern crate foo as bar;`
56            //                      ^^^ Comparing this.
57            match (a_name, b_name) {
58                (Some(..), None) => Ordering::Greater,
59                (None, Some(..)) => Ordering::Less,
60                (None, None) => Ordering::Equal,
61                (Some(..), Some(..)) if style_edition <= StyleEdition::Edition2024 => {
62                    a_ident.as_str().cmp(b_ident.as_str())
63                }
64                (Some(..), Some(..)) => version_sort(a_ident.as_str(), b_ident.as_str()),
65            }
66        }
67        _ => unreachable!(),
68    }
69}
70
71fn wrap_reorderable_items(
72    context: &RewriteContext<'_>,
73    list_items: &[ListItem],
74    shape: Shape,
75) -> RewriteResult {
76    let fmt = ListFormatting::new(shape, context.config)
77        .separator("")
78        .align_comments(false);
79    write_list(list_items, &fmt)
80}
81
82fn rewrite_reorderable_item(
83    context: &RewriteContext<'_>,
84    item: &ast::Item,
85    shape: Shape,
86) -> RewriteResult {
87    match item.kind {
88        ast::ItemKind::ExternCrate(..) => rewrite_extern_crate(context, item, shape),
89        ast::ItemKind::Mod(_, ident, _) => rewrite_mod(context, item, ident, shape),
90        _ => Err(RewriteError::Unknown),
91    }
92}
93
94/// Rewrite a list of items with reordering and/or regrouping. Every item
95/// in `items` must have the same `ast::ItemKind`. Whether reordering, regrouping,
96/// or both are done is determined from the `context`.
97fn rewrite_reorderable_or_regroupable_items(
98    context: &RewriteContext<'_>,
99    reorderable_items: &[&ast::Item],
100    shape: Shape,
101    span: Span,
102) -> RewriteResult {
103    match reorderable_items[0].kind {
104        // FIXME: Remove duplicated code.
105        ast::ItemKind::Use(..) => {
106            let mut normalized_items: Vec<_> = reorderable_items
107                .iter()
108                .filter_map(|item| UseTree::from_ast_with_normalization(context, item))
109                .collect();
110            let cloned = normalized_items.clone();
111            // Add comments before merging.
112            let list_items = itemize_list(
113                context.snippet_provider,
114                cloned.iter(),
115                "",
116                ";",
117                |item| item.span().lo(),
118                |item| item.span().hi(),
119                |_item| Ok("".to_owned()),
120                span.lo(),
121                span.hi(),
122                false,
123            );
124            for (item, list_item) in normalized_items.iter_mut().zip(list_items) {
125                item.list_item = Some(list_item.clone());
126            }
127            normalized_items = normalize_use_trees_with_granularity(
128                normalized_items,
129                context.config.imports_granularity(),
130            );
131
132            let mut regrouped_items = match context.config.group_imports() {
133                GroupImportsTactic::Preserve | GroupImportsTactic::One => {
134                    vec![normalized_items]
135                }
136                GroupImportsTactic::StdExternalCrate => group_imports(normalized_items),
137            };
138
139            if context.config.reorder_imports() {
140                regrouped_items.iter_mut().for_each(|items| items.sort())
141            }
142
143            // 4 = "use ", 1 = ";"
144            let nested_shape = shape.offset_left(4, span)?.sub_width(1, span)?;
145            let item_vec: Vec<_> = regrouped_items
146                .into_iter()
147                .filter(|use_group| !use_group.is_empty())
148                .map(|use_group| {
149                    let item_vec: Vec<_> = use_group
150                        .into_iter()
151                        .map(|use_tree| {
152                            let item = use_tree.rewrite_top_level(context, nested_shape);
153                            if let Some(list_item) = use_tree.list_item {
154                                ListItem {
155                                    item: item,
156                                    ..list_item
157                                }
158                            } else {
159                                ListItem::from_item(item)
160                            }
161                        })
162                        .collect();
163                    wrap_reorderable_items(context, &item_vec, nested_shape)
164                })
165                .collect::<Result<Vec<_>, RewriteError>>()?;
166
167            let join_string = format!("\n\n{}", shape.indent.to_string(context.config));
168            Ok(item_vec.join(&join_string))
169        }
170        _ => {
171            let list_items = itemize_list(
172                context.snippet_provider,
173                reorderable_items.iter(),
174                "",
175                ";",
176                |item| item.span().lo(),
177                |item| item.span().hi(),
178                |item| rewrite_reorderable_item(context, item, shape),
179                span.lo(),
180                span.hi(),
181                false,
182            );
183
184            let mut item_pair_vec: Vec<_> = list_items.zip(reorderable_items.iter()).collect();
185            item_pair_vec.sort_by(|a, b| compare_items(a.1, b.1, context));
186            let item_vec: Vec<_> = item_pair_vec.into_iter().map(|pair| pair.0).collect();
187
188            wrap_reorderable_items(context, &item_vec, shape)
189        }
190    }
191}
192
193fn contains_macro_use_attr(item: &ast::Item) -> bool {
194    attr::contains_name(&item.attrs, sym::macro_use)
195}
196
197/// Divides imports into three groups, corresponding to standard, external
198/// and local imports. Sorts each subgroup.
199fn group_imports(uts: Vec<UseTree>) -> Vec<Vec<UseTree>> {
200    let mut std_imports = Vec::new();
201    let mut external_imports = Vec::new();
202    let mut local_imports = Vec::new();
203
204    for ut in uts.into_iter() {
205        if ut.path.is_empty() {
206            external_imports.push(ut);
207            continue;
208        }
209        match &ut.path[0].kind {
210            UseSegmentKind::Ident(id, _) => match id.as_ref() {
211                "std" | "alloc" | "core" => std_imports.push(ut),
212                _ => external_imports.push(ut),
213            },
214            UseSegmentKind::Slf(_) | UseSegmentKind::Super(_) | UseSegmentKind::Crate(_) => {
215                local_imports.push(ut)
216            }
217            // These are probably illegal here
218            UseSegmentKind::Glob | UseSegmentKind::List(_) => external_imports.push(ut),
219        }
220    }
221
222    vec![std_imports, external_imports, local_imports]
223}
224
225/// A simplified version of `ast::ItemKind`.
226#[derive(Debug, PartialEq, Eq, Copy, Clone)]
227enum ReorderableItemKind {
228    ExternCrate,
229    Mod,
230    Use,
231    /// An item that cannot be reordered. Either has an unreorderable item kind
232    /// or an `macro_use` attribute.
233    Other,
234}
235
236impl ReorderableItemKind {
237    fn from(item: &ast::Item) -> Self {
238        match item.kind {
239            _ if contains_macro_use_attr(item) | contains_skip(&item.attrs) => {
240                ReorderableItemKind::Other
241            }
242            ast::ItemKind::ExternCrate(..) => ReorderableItemKind::ExternCrate,
243            ast::ItemKind::Mod(..) if is_mod_decl(item) => ReorderableItemKind::Mod,
244            ast::ItemKind::Use(..) => ReorderableItemKind::Use,
245            _ => ReorderableItemKind::Other,
246        }
247    }
248
249    fn is_same_item_kind(self, item: &ast::Item) -> bool {
250        ReorderableItemKind::from(item) == self
251    }
252
253    fn is_reorderable(self, config: &Config) -> bool {
254        match self {
255            ReorderableItemKind::ExternCrate => config.reorder_imports(),
256            ReorderableItemKind::Mod => config.reorder_modules(),
257            ReorderableItemKind::Use => config.reorder_imports(),
258            ReorderableItemKind::Other => false,
259        }
260    }
261
262    fn is_regroupable(self, config: &Config) -> bool {
263        match self {
264            ReorderableItemKind::ExternCrate
265            | ReorderableItemKind::Mod
266            | ReorderableItemKind::Other => false,
267            ReorderableItemKind::Use => config.group_imports() != GroupImportsTactic::Preserve,
268        }
269    }
270
271    fn in_group(self, config: &Config) -> bool {
272        match self {
273            ReorderableItemKind::ExternCrate | ReorderableItemKind::Mod => true,
274            ReorderableItemKind::Use => config.group_imports() == GroupImportsTactic::Preserve,
275            ReorderableItemKind::Other => false,
276        }
277    }
278}
279
280impl<'b, 'a: 'b> FmtVisitor<'a> {
281    /// Format items with the same item kind and reorder them, regroup them, or
282    /// both. If `in_group` is `true`, then the items separated by an empty line
283    /// will not be reordered together.
284    fn walk_reorderable_or_regroupable_items(
285        &mut self,
286        items: &[&ast::Item],
287        item_kind: ReorderableItemKind,
288        in_group: bool,
289    ) -> usize {
290        let mut last = self.psess.lookup_line_range(items[0].span());
291        let item_length = items
292            .iter()
293            .take_while(|ppi| {
294                item_kind.is_same_item_kind(&***ppi)
295                    && (!in_group || {
296                        let current = self.psess.lookup_line_range(ppi.span());
297                        let in_same_group = current.lo < last.hi + 2;
298                        last = current;
299                        in_same_group
300                    })
301            })
302            .count();
303        let items = &items[..item_length];
304
305        let at_least_one_in_file_lines = items
306            .iter()
307            .any(|item| !out_of_file_lines_range!(self, item.span));
308
309        if at_least_one_in_file_lines && !items.is_empty() {
310            let lo = items.first().unwrap().span().lo();
311            let hi = items.last().unwrap().span().hi();
312            let span = mk_sp(lo, hi);
313            let rw = rewrite_reorderable_or_regroupable_items(
314                &self.get_context(),
315                items,
316                self.shape(),
317                span,
318            );
319            self.push_rewrite(span, rw.ok());
320        } else {
321            for item in items {
322                self.push_rewrite(item.span, None);
323            }
324        }
325
326        item_length
327    }
328
329    /// Visits and format the given items. Items are reordered If they are
330    /// consecutive and reorderable.
331    pub(crate) fn visit_items_with_reordering(&mut self, mut items: &[&ast::Item]) {
332        while !items.is_empty() {
333            // If the next item is a `use`, `extern crate` or `mod`, then extract it and any
334            // subsequent items that have the same item kind to be reordered within
335            // `walk_reorderable_items`. Otherwise, just format the next item for output.
336            let item_kind = ReorderableItemKind::from(items[0]);
337            if item_kind.is_reorderable(self.config) || item_kind.is_regroupable(self.config) {
338                let visited_items_num = self.walk_reorderable_or_regroupable_items(
339                    items,
340                    item_kind,
341                    item_kind.in_group(self.config),
342                );
343                let (_, rest) = items.split_at(visited_items_num);
344                items = rest;
345            } else {
346                // Reaching here means items were not reordered. There must be at least
347                // one item left in `items`, so calling `unwrap()` here is safe.
348                let (item, rest) = items.split_first().unwrap();
349                self.visit_item(item);
350                items = rest;
351            }
352        }
353    }
354}