1use std::cmp::Ordering;
3
4use itertools::{EitherOrBoth, Itertools};
5use parser::T;
6use syntax::{
7 Direction, SyntaxElement, algo,
8 ast::{
9 self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind, edit_in_place::Removable,
10 make,
11 },
12 ted::{self, Position},
13};
14
15use crate::syntax_helpers::node_ext::vis_eq;
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19pub enum MergeBehavior {
20 Crate,
22 Module,
24 One,
27}
28
29impl MergeBehavior {
30 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
31 match self {
32 MergeBehavior::Crate | MergeBehavior::One => true,
33 MergeBehavior::Module => {
35 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
36 }
37 }
38 }
39}
40
41pub fn try_merge_imports(
44 lhs: &ast::Use,
45 rhs: &ast::Use,
46 merge_behavior: MergeBehavior,
47) -> Option<ast::Use> {
48 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
50 return None;
51 }
52 if !eq_attrs(lhs.attrs(), rhs.attrs()) {
53 return None;
54 }
55
56 let lhs = lhs.clone_subtree().clone_for_update();
57 let rhs = rhs.clone_subtree().clone_for_update();
58 let lhs_tree = lhs.use_tree()?;
59 let rhs_tree = rhs.use_tree()?;
60 try_merge_trees_mut(&lhs_tree, &rhs_tree, merge_behavior)?;
61
62 try_normalize_use_tree_mut(&lhs_tree, merge_behavior.into());
64
65 Some(lhs)
66}
67
68pub fn try_merge_trees(
71 lhs: &ast::UseTree,
72 rhs: &ast::UseTree,
73 merge: MergeBehavior,
74) -> Option<ast::UseTree> {
75 let lhs = lhs.clone_subtree().clone_for_update();
76 let rhs = rhs.clone_subtree().clone_for_update();
77 try_merge_trees_mut(&lhs, &rhs, merge)?;
78
79 try_normalize_use_tree_mut(&lhs, merge.into());
81
82 Some(lhs)
83}
84
85fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
86 if merge == MergeBehavior::One {
87 lhs.wrap_in_tree_list();
88 rhs.wrap_in_tree_list();
89 } else {
90 let lhs_path = lhs.path()?;
91 let rhs_path = rhs.path()?;
92
93 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
94 if lhs.is_simple_path()
95 && rhs.is_simple_path()
96 && lhs_path == lhs_prefix
97 && rhs_path == rhs_prefix
98 {
99 let lhs_name = lhs.rename().and_then(|lhs_name| lhs_name.name());
102 let rhs_name = rhs.rename().and_then(|rhs_name| rhs_name.name());
103 if lhs_name != rhs_name {
104 return None;
105 }
106
107 ted::replace(lhs.syntax(), rhs.syntax());
108 return Some(());
110 } else {
111 lhs.split_prefix(&lhs_prefix);
112 rhs.split_prefix(&rhs_prefix);
113 }
114 }
115 recursive_merge(lhs, rhs, merge)
116}
117
118#[must_use]
120fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
121 let mut use_trees: Vec<ast::UseTree> = lhs
122 .use_tree_list()
123 .into_iter()
124 .flat_map(|list| list.use_trees())
125 .map(|tree| merge.is_tree_allowed(&tree).then_some(tree))
128 .collect::<Option<_>>()?;
129 use_trees.sort_unstable_by(use_tree_cmp);
132 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
133 if !merge.is_tree_allowed(&rhs_t) {
134 return None;
135 }
136
137 match use_trees.binary_search_by(|lhs_t| use_tree_cmp_bin_search(lhs_t, &rhs_t)) {
138 Ok(idx) => {
139 let lhs_t = &mut use_trees[idx];
140 let lhs_path = lhs_t.path()?;
141 let rhs_path = rhs_t.path()?;
142 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
143 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
144 let tree_is_self = |tree: &ast::UseTree| {
145 tree.path().as_ref().map(path_is_self).unwrap_or(false)
146 };
147 let tree_contains_self = |tree: &ast::UseTree| {
152 tree.use_tree_list()
153 .map(|tree_list| tree_list.use_trees().any(|it| tree_is_self(&it)))
154 .or_else(|| tree.star_token().map(|_| false))
157 };
158
159 if lhs_t.rename().and_then(|x| x.underscore_token()).is_some() {
160 ted::replace(lhs_t.syntax(), rhs_t.syntax());
161 *lhs_t = rhs_t;
162 continue;
163 }
164
165 match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
166 (Some(true), None) => {
167 remove_subtree_if_only_self(lhs_t);
168 continue;
169 }
170 (None, Some(true)) => {
171 ted::replace(lhs_t.syntax(), rhs_t.syntax());
172 *lhs_t = rhs_t;
173 remove_subtree_if_only_self(lhs_t);
174 continue;
175 }
176 _ => (),
177 }
178
179 if lhs_t.is_simple_path() && rhs_t.is_simple_path() {
180 continue;
181 }
182 }
183 lhs_t.split_prefix(&lhs_prefix);
184 rhs_t.split_prefix(&rhs_prefix);
185 recursive_merge(lhs_t, &rhs_t, merge)?;
186 }
187 Err(_)
188 if merge == MergeBehavior::Module
189 && !use_trees.is_empty()
190 && rhs_t.use_tree_list().is_some() =>
191 {
192 return None;
193 }
194 Err(insert_idx) => {
195 use_trees.insert(insert_idx, rhs_t.clone());
196 lhs.get_or_create_use_tree_list().add_use_tree(rhs_t);
200 }
201 }
202 }
203 Some(())
204}
205
206#[derive(Copy, Clone, Debug, PartialEq, Eq)]
208pub enum NormalizationStyle {
209 Default,
216 One,
223}
224
225impl From<MergeBehavior> for NormalizationStyle {
226 fn from(mb: MergeBehavior) -> Self {
227 match mb {
228 MergeBehavior::One => NormalizationStyle::One,
229 _ => NormalizationStyle::Default,
230 }
231 }
232}
233
234pub fn try_normalize_import(use_item: &ast::Use, style: NormalizationStyle) -> Option<ast::Use> {
254 let use_item = use_item.clone_subtree().clone_for_update();
255 try_normalize_use_tree_mut(&use_item.use_tree()?, style)?;
256 Some(use_item)
257}
258
259pub fn try_normalize_use_tree(
261 use_tree: &ast::UseTree,
262 style: NormalizationStyle,
263) -> Option<ast::UseTree> {
264 let use_tree = use_tree.clone_subtree().clone_for_update();
265 try_normalize_use_tree_mut(&use_tree, style)?;
266 Some(use_tree)
267}
268
269pub fn try_normalize_use_tree_mut(
270 use_tree: &ast::UseTree,
271 style: NormalizationStyle,
272) -> Option<()> {
273 if style == NormalizationStyle::One {
274 let mut modified = false;
275 modified |= use_tree.wrap_in_tree_list().is_some();
276 modified |= recursive_normalize(use_tree, style).is_some();
277 if !modified {
278 return None;
280 }
281 } else {
282 recursive_normalize(use_tree, NormalizationStyle::Default)?;
283 }
284 Some(())
285}
286
287fn recursive_normalize(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
289 let use_tree_list = use_tree.use_tree_list()?;
290 let merge_subtree_into_parent_tree = |single_subtree: &ast::UseTree| {
291 let subtree_is_only_self = single_subtree.path().as_ref().is_some_and(path_is_self);
292
293 let merged_path = match (use_tree.path(), single_subtree.path()) {
294 _ if subtree_is_only_self => None,
298
299 (None, None) => None,
300 (Some(outer), None) => Some(outer),
301 (None, Some(inner)) => Some(inner),
302 (Some(outer), Some(inner)) => Some(make::path_concat(outer, inner).clone_for_update()),
303 };
304
305 if merged_path.is_some()
306 || single_subtree.use_tree_list().is_some()
307 || single_subtree.star_token().is_some()
308 {
309 ted::remove_all_iter(use_tree.syntax().children_with_tokens());
310 if let Some(path) = merged_path {
311 ted::insert_raw(Position::first_child_of(use_tree.syntax()), path.syntax());
312 if single_subtree.use_tree_list().is_some() || single_subtree.star_token().is_some()
313 {
314 ted::insert_raw(
315 Position::last_child_of(use_tree.syntax()),
316 make::token(T![::]),
317 );
318 }
319 }
320 if let Some(inner_use_tree_list) = single_subtree.use_tree_list() {
321 ted::insert_raw(
322 Position::last_child_of(use_tree.syntax()),
323 inner_use_tree_list.syntax(),
324 );
325 } else if single_subtree.star_token().is_some() {
326 ted::insert_raw(Position::last_child_of(use_tree.syntax()), make::token(T![*]));
327 } else if let Some(rename) = single_subtree.rename() {
328 ted::insert_raw(
329 Position::last_child_of(use_tree.syntax()),
330 make::tokens::single_space(),
331 );
332 ted::insert_raw(Position::last_child_of(use_tree.syntax()), rename.syntax());
333 }
334 Some(())
335 } else {
336 None
338 }
339 };
340 let one_style_tree_list = |subtree: &ast::UseTree| match (
341 subtree.path().is_none() && subtree.star_token().is_none() && subtree.rename().is_none(),
342 subtree.use_tree_list(),
343 ) {
344 (true, tree_list) => tree_list,
345 _ => None,
346 };
347 let add_element_to_list = |elem: SyntaxElement, elements: &mut Vec<SyntaxElement>| {
348 if !elements.is_empty() {
349 elements.push(make::token(T![,]).into());
350 elements.push(make::tokens::single_space().into());
351 }
352 elements.push(elem);
353 };
354 if let Some((single_subtree,)) = use_tree_list.use_trees().collect_tuple() {
355 if style == NormalizationStyle::One {
356 recursive_normalize(&single_subtree, NormalizationStyle::Default)?;
358 } else {
359 merge_subtree_into_parent_tree(&single_subtree)?;
362 recursive_normalize(use_tree, style);
363 }
364 } else {
365 let mut modified = false;
367
368 for subtree in use_tree_list.use_trees() {
370 if let Some(one_tree_list) = one_style_tree_list(&subtree) {
371 let mut elements = Vec::new();
372 let mut one_tree_list_iter = one_tree_list.use_trees();
373 let mut prev_skipped = Vec::new();
374 loop {
375 let mut prev_skipped_iter = prev_skipped.into_iter();
376 let mut curr_skipped = Vec::new();
377
378 while let Some(sub_sub_tree) =
379 one_tree_list_iter.next().or(prev_skipped_iter.next())
380 {
381 if let Some(sub_one_tree_list) = one_style_tree_list(&sub_sub_tree) {
382 curr_skipped.extend(sub_one_tree_list.use_trees());
383 } else {
384 modified |=
385 recursive_normalize(&sub_sub_tree, NormalizationStyle::Default)
386 .is_some();
387 add_element_to_list(
388 sub_sub_tree.syntax().clone().into(),
389 &mut elements,
390 );
391 }
392 }
393
394 if curr_skipped.is_empty() {
395 break;
397 }
398 prev_skipped = curr_skipped;
399 }
400
401 if elements.is_empty() {
404 subtree.remove();
405 } else {
406 ted::replace_with_many(subtree.syntax(), elements);
407 }
408 let _ = modified;
410 modified = true;
411 } else {
412 modified |= recursive_normalize(&subtree, NormalizationStyle::Default).is_some();
413 }
414 }
415
416 let mut tree_list_iter = use_tree_list.use_trees();
418 let mut anchor = tree_list_iter.next()?;
419 let mut prev_skipped = Vec::new();
420 loop {
421 let mut has_merged = false;
422 let mut prev_skipped_iter = prev_skipped.into_iter();
423 let mut next_anchor = None;
424 let mut curr_skipped = Vec::new();
425
426 while let Some(candidate) = tree_list_iter.next().or(prev_skipped_iter.next()) {
427 let result = try_merge_trees_mut(&anchor, &candidate, MergeBehavior::Crate);
428 if result.is_some() {
429 candidate.remove();
431 has_merged = true;
432 } else if next_anchor.is_none() {
433 next_anchor = Some(candidate);
434 } else {
435 curr_skipped.push(candidate);
436 }
437 }
438
439 if has_merged {
440 recursive_normalize(&anchor, NormalizationStyle::Default);
442 modified = true;
443 }
444
445 let (Some(next_anchor), true) = (next_anchor, !curr_skipped.is_empty()) else {
446 break;
448 };
449
450 anchor = next_anchor;
452 prev_skipped = curr_skipped;
453 }
454
455 let mut subtrees: Vec<_> = use_tree_list.use_trees().collect();
456 if subtrees.len() == 1 && style != NormalizationStyle::One {
459 modified |= merge_subtree_into_parent_tree(&subtrees[0]).is_some();
460 }
461 if subtrees.len() > 1 {
463 let mut did_sort = false;
464 subtrees.sort_unstable_by(|a, b| {
465 let order = use_tree_cmp_bin_search(a, b);
466 if !did_sort && order == Ordering::Less {
467 did_sort = true;
468 }
469 order
470 });
471 if did_sort {
472 let start = use_tree_list
473 .l_curly_token()
474 .and_then(|l_curly| algo::non_trivia_sibling(l_curly.into(), Direction::Next))
475 .filter(|it| it.kind() != T!['}']);
476 let end = use_tree_list
477 .r_curly_token()
478 .and_then(|r_curly| algo::non_trivia_sibling(r_curly.into(), Direction::Prev))
479 .filter(|it| it.kind() != T!['{']);
480 if let Some((start, end)) = start.zip(end) {
481 let mut elements = Vec::new();
483 for subtree in subtrees {
484 add_element_to_list(subtree.syntax().clone().into(), &mut elements);
485 }
486 ted::replace_all(start..=end, elements);
487 } else {
488 let new_use_tree_list = make::use_tree_list(subtrees).clone_for_update();
489 ted::replace(use_tree_list.syntax(), new_use_tree_list.syntax());
490 }
491 modified = true;
492 }
493 }
494
495 if !modified {
496 return None;
498 }
499 }
500 Some(())
501}
502
503pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
505 let mut res = None;
506 let mut lhs_curr = lhs.first_qualifier_or_self();
507 let mut rhs_curr = rhs.first_qualifier_or_self();
508 loop {
509 match (lhs_curr.segment(), rhs_curr.segment()) {
510 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
511 _ => break res,
512 }
513 res = Some((lhs_curr.clone(), rhs_curr.clone()));
514
515 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
516 Some((lhs, rhs)) => {
517 lhs_curr = lhs;
518 rhs_curr = rhs;
519 }
520 _ => break res,
521 }
522 }
523}
524
525fn use_tree_cmp_bin_search(lhs: &ast::UseTree, rhs: &ast::UseTree) -> Ordering {
527 let lhs_is_simple_path = lhs.is_simple_path() && lhs.rename().is_none();
528 let rhs_is_simple_path = rhs.is_simple_path() && rhs.rename().is_none();
529 match (
530 lhs.path().as_ref().and_then(ast::Path::first_segment),
531 rhs.path().as_ref().and_then(ast::Path::first_segment),
532 ) {
533 (None, None) => match (lhs_is_simple_path, rhs_is_simple_path) {
534 (true, true) => Ordering::Equal,
535 (true, false) => Ordering::Less,
536 (false, true) => Ordering::Greater,
537 (false, false) => use_tree_cmp_by_tree_list_glob_or_alias(lhs, rhs, false),
538 },
539 (Some(_), None) if !rhs_is_simple_path => Ordering::Less,
540 (Some(_), None) => Ordering::Greater,
541 (None, Some(_)) if !lhs_is_simple_path => Ordering::Greater,
542 (None, Some(_)) => Ordering::Less,
543 (Some(a), Some(b)) => path_segment_cmp(&a, &b),
544 }
545}
546
547pub(super) fn use_tree_cmp(a: &ast::UseTree, b: &ast::UseTree) -> Ordering {
555 let a_is_simple_path = a.is_simple_path() && a.rename().is_none();
556 let b_is_simple_path = b.is_simple_path() && b.rename().is_none();
557 match (a.path(), b.path()) {
558 (None, None) => match (a_is_simple_path, b_is_simple_path) {
559 (true, true) => Ordering::Equal,
560 (true, false) => Ordering::Less,
561 (false, true) => Ordering::Greater,
562 (false, false) => use_tree_cmp_by_tree_list_glob_or_alias(a, b, true),
563 },
564 (Some(_), None) if !b_is_simple_path => Ordering::Less,
565 (Some(_), None) => Ordering::Greater,
566 (None, Some(_)) if !a_is_simple_path => Ordering::Greater,
567 (None, Some(_)) => Ordering::Less,
568 (Some(a_path), Some(b_path)) => {
569 a_path
572 .segments()
573 .zip_longest(b_path.segments())
574 .find_map(|zipped| match zipped {
575 EitherOrBoth::Both(a_segment, b_segment) => {
576 match path_segment_cmp(&a_segment, &b_segment) {
577 Ordering::Equal => None,
578 ord => Some(ord),
579 }
580 }
581 EitherOrBoth::Left(_) if b_is_simple_path => Some(Ordering::Greater),
582 EitherOrBoth::Left(_) => Some(Ordering::Less),
583 EitherOrBoth::Right(_) if a_is_simple_path => Some(Ordering::Less),
584 EitherOrBoth::Right(_) => Some(Ordering::Greater),
585 })
586 .unwrap_or_else(|| use_tree_cmp_by_tree_list_glob_or_alias(a, b, true))
587 }
588 }
589}
590
591fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
592 match (a.kind(), b.kind()) {
593 (None, None) => Ordering::Equal,
594 (Some(_), None) => Ordering::Greater,
595 (None, Some(_)) => Ordering::Less,
596 (Some(PathSegmentKind::SelfKw), Some(PathSegmentKind::SelfKw)) => Ordering::Equal,
598 (Some(PathSegmentKind::SelfKw), _) => Ordering::Less,
599 (_, Some(PathSegmentKind::SelfKw)) => Ordering::Greater,
600 (Some(PathSegmentKind::SuperKw), Some(PathSegmentKind::SuperKw)) => Ordering::Equal,
602 (Some(PathSegmentKind::SuperKw), _) => Ordering::Less,
603 (_, Some(PathSegmentKind::SuperKw)) => Ordering::Greater,
604 (Some(PathSegmentKind::CrateKw), Some(PathSegmentKind::CrateKw)) => Ordering::Equal,
606 (Some(PathSegmentKind::CrateKw), _) => Ordering::Less,
607 (_, Some(PathSegmentKind::CrateKw)) => Ordering::Greater,
608 _ => {
610 match (
611 a.name_ref().as_ref().map(ast::NameRef::text),
612 b.name_ref().as_ref().map(ast::NameRef::text),
613 ) {
614 (None, None) => Ordering::Equal,
615 (Some(_), None) => Ordering::Greater,
616 (None, Some(_)) => Ordering::Less,
617 (Some(a_name), Some(b_name)) => {
618 let a_text = a_name.as_str().trim_start_matches("r#");
619 let b_text = b_name.as_str().trim_start_matches("r#");
620 version_sort::version_sort(a_text, b_text)
621 }
622 }
623 }
624 }
625}
626
627fn use_tree_cmp_by_tree_list_glob_or_alias(
635 a: &ast::UseTree,
636 b: &ast::UseTree,
637 strict: bool,
638) -> Ordering {
639 let cmp_by_glob_or_alias = || match (a.star_token().is_some(), b.star_token().is_some()) {
640 (true, false) => Ordering::Greater,
641 (false, true) => Ordering::Less,
642 _ => match (a.rename(), b.rename()) {
643 (None, None) => Ordering::Equal,
644 (Some(_), None) => Ordering::Greater,
645 (None, Some(_)) => Ordering::Less,
646 (Some(a_rename), Some(b_rename)) => a_rename
647 .name()
648 .as_ref()
649 .map(ast::Name::text)
650 .as_ref()
651 .map_or("_", |a_name| a_name.as_str().trim_start_matches("r#"))
652 .cmp(
653 b_rename
654 .name()
655 .as_ref()
656 .map(ast::Name::text)
657 .as_ref()
658 .map_or("_", |b_name| b_name.as_str().trim_start_matches("r#")),
659 ),
660 },
661 };
662
663 match (a.use_tree_list(), b.use_tree_list()) {
664 (Some(_), None) => Ordering::Greater,
665 (None, Some(_)) => Ordering::Less,
666 (Some(a_list), Some(b_list)) if strict => a_list
667 .use_trees()
668 .zip_longest(b_list.use_trees())
669 .find_map(|zipped| match zipped {
670 EitherOrBoth::Both(a_tree, b_tree) => match use_tree_cmp(&a_tree, &b_tree) {
671 Ordering::Equal => None,
672 ord => Some(ord),
673 },
674 EitherOrBoth::Left(_) => Some(Ordering::Greater),
675 EitherOrBoth::Right(_) => Some(Ordering::Less),
676 })
677 .unwrap_or_else(cmp_by_glob_or_alias),
678 _ => cmp_by_glob_or_alias(),
679 }
680}
681
682pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
683 match (vis0, vis1) {
684 (None, None) => true,
685 (Some(vis0), Some(vis1)) => vis_eq(&vis0, &vis1),
686 _ => false,
687 }
688}
689
690pub fn eq_attrs(
691 attrs0: impl Iterator<Item = ast::Attr>,
692 attrs1: impl Iterator<Item = ast::Attr>,
693) -> bool {
694 let attrs0 = attrs0
696 .flat_map(|attr| attr.syntax().descendants_with_tokens())
697 .flat_map(|it| it.into_token());
698 let attrs1 = attrs1
699 .flat_map(|attr| attr.syntax().descendants_with_tokens())
700 .flat_map(|it| it.into_token());
701 stdx::iter_eq_by(attrs0, attrs1, |tok, tok2| tok.text() == tok2.text())
702}
703
704fn path_is_self(path: &ast::Path) -> bool {
705 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
706}
707
708fn path_len(path: ast::Path) -> usize {
709 path.segments().count()
710}
711
712fn get_single_subtree(use_tree: &ast::UseTree) -> Option<ast::UseTree> {
713 use_tree
714 .use_tree_list()
715 .and_then(|tree_list| tree_list.use_trees().collect_tuple())
716 .map(|(single_subtree,)| single_subtree)
717}
718
719fn remove_subtree_if_only_self(use_tree: &ast::UseTree) {
720 let Some(single_subtree) = get_single_subtree(use_tree) else { return };
721 match (use_tree.path(), single_subtree.path()) {
722 (Some(_), Some(inner)) if path_is_self(&inner) => {
723 ted::remove_all_iter(single_subtree.syntax().children_with_tokens());
724 }
725 _ => (),
726 }
727}
728
729mod version_sort {
732 #![allow(clippy::all)]
735
736 use std::cmp::Ordering;
737
738 use itertools::{EitherOrBoth, Itertools};
739
740 struct VersionChunkIter<'a> {
741 ident: &'a str,
742 start: usize,
743 }
744
745 impl<'a> VersionChunkIter<'a> {
746 pub(crate) fn new(ident: &'a str) -> Self {
747 Self { ident, start: 0 }
748 }
749
750 fn parse_numeric_chunk(
751 &mut self,
752 mut chars: std::str::CharIndices<'a>,
753 ) -> Option<VersionChunk<'a>> {
754 let mut end = self.start;
755 let mut is_end_of_chunk = false;
756
757 while let Some((idx, c)) = chars.next() {
758 end = self.start + idx;
759
760 if c.is_ascii_digit() {
761 continue;
762 }
763
764 is_end_of_chunk = true;
765 break;
766 }
767
768 let source = if is_end_of_chunk {
769 let value = &self.ident[self.start..end];
770 self.start = end;
771 value
772 } else {
773 let value = &self.ident[self.start..];
774 self.start = self.ident.len();
775 value
776 };
777
778 let zeros = source.chars().take_while(|c| *c == '0').count();
779 let value = source.parse::<usize>().ok()?;
780
781 Some(VersionChunk::Number { value, zeros, source })
782 }
783
784 fn parse_str_chunk(
785 &mut self,
786 mut chars: std::str::CharIndices<'a>,
787 ) -> Option<VersionChunk<'a>> {
788 let mut end = self.start;
789 let mut is_end_of_chunk = false;
790
791 while let Some((idx, c)) = chars.next() {
792 end = self.start + idx;
793
794 if c == '_' {
795 is_end_of_chunk = true;
796 break;
797 }
798
799 if !c.is_ascii_digit() {
800 continue;
801 }
802
803 is_end_of_chunk = true;
804 break;
805 }
806
807 let source = if is_end_of_chunk {
808 let value = &self.ident[self.start..end];
809 self.start = end;
810 value
811 } else {
812 let value = &self.ident[self.start..];
813 self.start = self.ident.len();
814 value
815 };
816
817 Some(VersionChunk::Str(source))
818 }
819 }
820
821 impl<'a> Iterator for VersionChunkIter<'a> {
822 type Item = VersionChunk<'a>;
823
824 fn next(&mut self) -> Option<Self::Item> {
825 let mut chars = self.ident[self.start..].char_indices();
826 let (_, next) = chars.next()?;
827
828 if next == '_' {
829 self.start = self.start + next.len_utf8();
830 return Some(VersionChunk::Underscore);
831 }
832
833 if next.is_ascii_digit() {
834 return self.parse_numeric_chunk(chars);
835 }
836
837 self.parse_str_chunk(chars)
838 }
839 }
840
841 #[derive(Debug, PartialEq, Eq)]
843 enum VersionChunk<'a> {
844 Underscore,
846 Str(&'a str),
848 Number { value: usize, zeros: usize, source: &'a str },
850 }
851
852 #[derive(Debug, PartialEq, Eq)]
854 enum MoreLeadingZeros {
855 Left,
856 Right,
857 Equal,
858 }
859
860 pub(super) fn version_sort(a: &str, b: &str) -> Ordering {
861 let iter_a = VersionChunkIter::new(a);
862 let iter_b = VersionChunkIter::new(b);
863 let mut more_leading_zeros = MoreLeadingZeros::Equal;
864
865 for either_or_both in iter_a.zip_longest(iter_b) {
866 match either_or_both {
867 EitherOrBoth::Left(_) => return std::cmp::Ordering::Greater,
868 EitherOrBoth::Right(_) => return std::cmp::Ordering::Less,
869 EitherOrBoth::Both(a, b) => match (a, b) {
870 (VersionChunk::Underscore, VersionChunk::Underscore) => {
871 continue;
872 }
873 (VersionChunk::Underscore, _) => return std::cmp::Ordering::Less,
874 (_, VersionChunk::Underscore) => return std::cmp::Ordering::Greater,
875 (VersionChunk::Str(ca), VersionChunk::Str(cb))
876 | (VersionChunk::Str(ca), VersionChunk::Number { source: cb, .. })
877 | (VersionChunk::Number { source: ca, .. }, VersionChunk::Str(cb)) => {
878 match ca.cmp(&cb) {
879 std::cmp::Ordering::Equal => {
880 continue;
881 }
882 order @ _ => return order,
883 }
884 }
885 (
886 VersionChunk::Number { value: va, zeros: lza, .. },
887 VersionChunk::Number { value: vb, zeros: lzb, .. },
888 ) => match va.cmp(&vb) {
889 std::cmp::Ordering::Equal => {
890 if lza == lzb {
891 continue;
892 }
893
894 if more_leading_zeros == MoreLeadingZeros::Equal && lza > lzb {
895 more_leading_zeros = MoreLeadingZeros::Left;
896 } else if more_leading_zeros == MoreLeadingZeros::Equal && lza < lzb {
897 more_leading_zeros = MoreLeadingZeros::Right;
898 }
899 continue;
900 }
901 order @ _ => return order,
902 },
903 },
904 }
905 }
906
907 match more_leading_zeros {
908 MoreLeadingZeros::Equal => std::cmp::Ordering::Equal,
909 MoreLeadingZeros::Left => std::cmp::Ordering::Less,
910 MoreLeadingZeros::Right => std::cmp::Ordering::Greater,
911 }
912 }
913}