Skip to main content

rustidy_ast/item/
use_.rs

1//! Use statements
2
3// Imports
4use {
5	crate::{path::{SimplePath, SimplePathSegment}, token, util::Braced},
6	rustidy_ast_util::{
7		Identifier,
8		Punctuated,
9		PunctuatedTrailing,
10		delimited,
11		punct::{self, PunctuatedRest},
12	},
13	rustidy_format::{Format, FormatOutput, Formattable, WhitespaceConfig, WhitespaceFormat},
14	rustidy_parse::Parse,
15	rustidy_print::Print,
16	rustidy_util::Whitespace,
17	std::{borrow::Cow, cmp},
18};
19
20/// `UseDeclaration`
21#[derive(PartialEq, Eq, Clone, Debug)]
22#[derive(serde::Serialize, serde::Deserialize)]
23#[derive(Parse, Formattable, Format, Print)]
24#[parse(name = "use declaration")]
25pub struct UseDeclaration {
26	pub use_: token::Use,
27	#[parse(fatal)]
28	#[format(prefix_ws = Whitespace::SINGLE)]
29	pub tree: UseTree,
30	#[format(prefix_ws = Whitespace::REMOVE)]
31	pub semi: token::Semi,
32}
33
34impl UseDeclaration {
35	/// Merges another use declaration into this one
36	pub fn merge(&mut self, other: Self) {
37		self.tree.merge(other.tree);
38	}
39}
40
41/// `UseTree`
42#[derive(PartialEq, Eq, Clone, Debug)]
43#[derive(strum::EnumIs)]
44#[derive(serde::Serialize, serde::Deserialize)]
45#[derive(Parse, Formattable, Format, Print)]
46pub enum UseTree {
47	Glob(UseTreeGlob),
48	Group(UseTreeGroup),
49	Simple(UseTreeSimple),
50}
51
52impl UseTree {
53	/// Converts this tree into a group.
54	#[must_use]
55	pub fn into_group(self) -> UseTreeGroup {
56		match self {
57			Self::Group(tree) => tree,
58
59			_ => UseTreeGroup {
60				prefix: None,
61				tree: Braced::from_value(Some(PunctuatedTrailing::single(Box::new(self)))),
62			},
63		}
64	}
65
66	/// Merges another tree into this one
67	pub fn merge(&mut self, other: Self) {
68		match (self, other) {
69			(Self::Group(lhs), rhs) => lhs.push_back(rhs),
70			(lhs, Self::Group(mut rhs)) => replace_with::replace_with_or_abort(lhs, |lhs| {
71				rhs.push_front(lhs);
72				Self::Group(rhs)
73			}),
74
75			(lhs, rhs) => replace_with::replace_with_or_abort(lhs, |lhs| {
76				let mut values = PunctuatedTrailing::single(Box::new(lhs));
77				values.push_value(Box::new(rhs));
78				Self::Group(UseTreeGroup {
79					prefix: None,
80					tree: Braced::from_value(Some(values))
81				})
82			}),
83		}
84	}
85}
86
87#[derive(PartialEq, Eq, Clone, Debug)]
88#[derive(serde::Serialize, serde::Deserialize)]
89#[derive(Parse, Formattable, Format, Print)]
90pub struct UseTreeGlob {
91	pub prefix: Option<UseTreeGlobPrefix>,
92	#[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.prefix.is_some()))]
93	pub glob:   token::Star,
94}
95
96#[derive(PartialEq, Eq, Clone, Debug)]
97#[derive(serde::Serialize, serde::Deserialize)]
98#[derive(Parse, Formattable, Format, Print)]
99pub struct UseTreeGlobPrefix {
100	pub path: Option<SimplePath>,
101	#[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.path.is_some()))]
102	pub sep:  token::PathSep,
103}
104
105#[derive(PartialEq, Eq, Clone, Debug)]
106#[derive(serde::Serialize, serde::Deserialize)]
107#[derive(Parse, Formattable, Format, Print)]
108#[format(before_with = Self::flatten)]
109#[format(before_with = Self::sort)]
110pub struct UseTreeGroup {
111	pub prefix: Option<UseTreeGroupPrefix>,
112	#[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.prefix.is_some()))]
113	#[format(with = Self::format_tree)]
114	pub tree:   Braced<Option<PunctuatedTrailing<Box<UseTree>, token::Comma>>>,
115}
116
117impl UseTreeGroup {
118	/// Pushes a tree at the front of this group
119	pub fn push_front(&mut self, tree: UseTree) {
120		match tree {
121			UseTree::Group(rhs) if self.prefix == rhs.prefix => match &mut self.tree.value {
122				Some(lhs) => if let Some(mut rhs) = rhs.tree.value {
123					replace_with::replace_with_or_abort(lhs, |lhs| {
124						rhs.extend_from_punctuated_trailing(lhs);
125						rhs
126					});
127				},
128				None => self.tree.value = rhs.tree.value,
129			},
130
131			UseTree::Group(rhs) => replace_with::replace_with_or_abort(self, |lhs| {
132				let mut values = PunctuatedTrailing::single(Box::new(UseTree::Group(rhs)));
133				values
134					.push_value(Box::new(UseTree::Group(lhs)));
135				Self {
136					prefix: None,
137					tree: Braced::from_value(Some(values))
138				}
139			}),
140
141			_ => match &mut self.tree.value {
142				Some(lhs) => lhs.push_front_value(Box::new(tree)),
143				None => self.tree.value = Some(PunctuatedTrailing::single(Box::new(tree))),
144			},
145		}
146	}
147
148	/// Pushes a tree at the back of this group
149	pub fn push_back(&mut self, tree: UseTree) {
150		match tree {
151			UseTree::Group(rhs) if self.prefix == rhs.prefix => match &mut self.tree.value {
152				Some(lhs) => if let Some(rhs) = rhs.tree.value {
153					lhs.extend_from_punctuated_trailing(rhs);
154				},
155				None => self.tree.value = rhs.tree.value,
156			},
157
158			UseTree::Group(rhs) => replace_with::replace_with_or_abort(self, |lhs| {
159				let mut values = PunctuatedTrailing::single(Box::new(UseTree::Group(lhs)));
160				values
161					.push_value(Box::new(UseTree::Group(rhs)));
162				Self {
163					prefix: None,
164					tree: Braced::from_value(Some(values))
165				}
166			}),
167
168			_ => match &mut self.tree.value {
169				Some(lhs) => lhs.push_value(Box::new(tree)),
170				None => self.tree.value = Some(PunctuatedTrailing::single(Box::new(tree))),
171			},
172		}
173	}
174
175	/// Sorts all trees inside
176	pub fn sort(&mut self, _ctx: &mut rustidy_format::Context) {
177		let Some(trees) = &mut self.tree.value else {
178			return
179		};
180
181		// TODO: Move this wrapper elsewhere
182		struct SimplePathSortOrder<'a>(&'a SimplePath);
183
184		impl PartialEq for SimplePathSortOrder<'_> {
185			fn eq(&self, other: &Self) -> bool {
186				self.cmp(other).is_eq()
187			}
188		}
189		impl Eq for SimplePathSortOrder<'_> {}
190		impl PartialOrd for SimplePathSortOrder<'_> {
191			fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
192				Some(self.cmp(other))
193			}
194		}
195		impl Ord for SimplePathSortOrder<'_> {
196			fn cmp(&self, other: &Self) -> cmp::Ordering {
197				let prefix_cmp = self.0
198					.prefix
199					.is_some()
200					.cmp(&other.0.prefix.is_some());
201				if !prefix_cmp.is_eq() {
202					return prefix_cmp;
203				}
204
205				let mut lhs = self.0.segments.values();
206				let mut rhs = other.0.segments.values();
207				loop {
208					let (lhs, rhs) = match (lhs.next(), rhs.next()) {
209						(Some(lhs), Some(rhs)) => (lhs, rhs),
210						(Some(_), None) => return cmp::Ordering::Less,
211						(None, Some(_)) => return cmp::Ordering::Greater,
212						(None, None) => return cmp::Ordering::Equal,
213					};
214
215					#[derive(PartialEq, Eq, PartialOrd, Ord)]
216					pub enum Segment<'a> {
217						Crate,
218						Super,
219						SelfLower,
220						DollarCrate,
221						Ident(Cow<'a, str>),
222					}
223					fn segment(segment: &SimplePathSegment) -> Segment<'_> {
224						match segment {
225							SimplePathSegment::Super(_) => Segment::Super,
226							SimplePathSegment::SelfLower(_) => Segment::SelfLower,
227							SimplePathSegment::Crate(_) => Segment::Crate,
228							SimplePathSegment::DollarCrate(_) => Segment::DollarCrate,
229							SimplePathSegment::Ident(ident) => Segment::Ident(ident.as_str()),
230						}
231					}
232
233					let segment_cmp = segment(lhs).cmp(&segment(rhs));
234					if !segment_cmp.is_eq() {
235						return segment_cmp;
236					}
237				}
238			}
239		}
240
241		#[derive(PartialEq, Eq, PartialOrd, Ord)]
242		enum SortOrder<'a> {
243			GroupWithPrefixRoot,
244			Glob,
245			WithPath(SimplePathSortOrder<'a>),
246			GroupNoPrefix,
247		}
248
249		#[expect(clippy::borrowed_box, reason = "It's necessary for the closure")]
250		trees.sort_values_by_key(
251			for<'a> |tree: &'a Box<UseTree>, _: Option<&'a token::Comma>| -> SortOrder<'a> {
252				match &**tree {
253					UseTree::Glob(_) => SortOrder::Glob,
254					UseTree::Group(tree) => match &tree.prefix {
255						Some(prefix) => match &prefix.path {
256							Some(path) => SortOrder::WithPath(SimplePathSortOrder(path)),
257							None => SortOrder::GroupWithPrefixRoot,
258						},
259						None => SortOrder::GroupNoPrefix,
260					},
261					UseTree::Simple(tree) => SortOrder::WithPath(SimplePathSortOrder(&tree.path)),
262				}
263			}
264		);
265	}
266
267	/// Flattens this use group
268	pub fn flatten(&mut self, ctx: &mut rustidy_format::Context) {
269		replace_with::replace_with_or_abort(&mut self.tree.value, |trees| {
270			let mut trees = trees?;
271			let mut trees_first = Some(PunctuatedRest {
272				punct: token::Comma::new(),
273				value: trees.punctuated.first,
274			});
275			let mut sub_trees = vec![];
276			let mut new_trees: Vec<PunctuatedRest<_, token::Comma>> = vec![];
277			let mut trailing_comma = trees.trailing;
278
279			// Note: We process the trees backwards to ensure that we always have
280			//       somewhere to add the whitespace of the braces we're removing.
281			while let Some(PunctuatedRest { punct: mut comma, value: tree, }) = sub_trees
282				.pop()
283				.or_else(|| trees.punctuated.rest.pop())
284				.or_else(|| trees_first.take()) {
285				// Joins a prefix whitespace to the latest whitespace we have
286				let mut latest_ws_join_prefix = |ws: Whitespace| match new_trees.last_mut() {
287					Some(PunctuatedRest { punct: last_comma, .. }) => last_comma.ws.join_prefix(ws),
288					None => match &mut trailing_comma {
289						Some(trailing_comma) => trailing_comma.ws.join_prefix(ws),
290						None => self.tree.suffix.ws.join_prefix(ws),
291					},
292				};
293
294				match *tree {
295					UseTree::Group(group) if group.prefix.is_none() => {
296						latest_ws_join_prefix(group.tree.suffix.ws);
297
298						match group.tree.value {
299							Some(trees) => {
300								comma
301									.ws
302									.join_prefix(group.tree.prefix.ws);
303								sub_trees.push(
304									PunctuatedRest { punct: comma, value: trees.punctuated.first, }
305								);
306								for rest in trees.punctuated.rest {
307									sub_trees.push(rest);
308								}
309							},
310							None => latest_ws_join_prefix(group.tree.prefix.ws),
311						}
312					},
313					_ => new_trees
314						.push(PunctuatedRest { punct: comma, value: tree, }),
315				}
316			}
317
318			new_trees.pop().map(
319				|PunctuatedRest { punct: first_comma, value: mut first }| {
320					first
321						.prefix_ws_join_prefix(ctx, first_comma.ws)
322						.expect("Use tree should have prefix whitespace");
323
324					new_trees.reverse();
325					PunctuatedTrailing {
326						punctuated: Punctuated { first, rest: new_trees },
327						trailing: trailing_comma,
328					}
329				},
330			)
331		});
332	}
333
334	fn format_tree_compact(
335		tree: &mut Braced<Option<PunctuatedTrailing<Box<UseTree>, token::Comma>>>,
336		ctx: &mut rustidy_format::Context,
337		prefix_ws: WhitespaceConfig,
338	) -> FormatOutput {
339		if let Some(punct) = &mut tree.value {
340			punct.trailing = None;
341		}
342
343		tree.format(
344			ctx,
345			prefix_ws,
346			delimited::FmtRemoveWith(punct::FmtArgs {
347				value_prefix_ws: Whitespace::SINGLE,
348				punct_prefix_ws: Whitespace::REMOVE,
349				value_args: (),
350				punct_args: (),
351			}),
352		)
353	}
354
355	fn format_tree(
356		tree: &mut Braced<Option<PunctuatedTrailing<Box<UseTree>, token::Comma>>>,
357		ctx: &mut rustidy_format::Context,
358		prefix_ws: WhitespaceConfig,
359		_args: (),
360	) -> FormatOutput {
361		let compact_output = Self::format_tree_compact(tree, ctx, prefix_ws);
362
363		// Note: We don't use `len_non_multiline_ws` because we never want to emit
364		//       something like `{a, b::{\n...\n}, c, d}`, and if any newlines are
365		//       found we'd instead want to make it multi-line.
366		match compact_output.len_without_prefix_ws() > ctx.config().max_use_tree_len {
367			true => {
368				if let Some(punct) = &mut tree.value && punct.trailing.is_none() {
369					punct.trailing = Some(token::Comma::new());
370				}
371
372				tree.format(
373					ctx,
374					prefix_ws,
375					delimited::fmt_indent_if_non_blank_with((), punct::FmtArgs {
376						value_prefix_ws: Whitespace::INDENT,
377						punct_prefix_ws: Whitespace::REMOVE,
378						value_args: (),
379						punct_args: (),
380					}, (),),
381				)
382			},
383			false => compact_output,
384		}
385	}
386}
387
388#[derive(PartialEq, Eq, Clone, Debug)]
389#[derive(serde::Serialize, serde::Deserialize)]
390#[derive(Parse, Formattable, Format, Print)]
391pub struct UseTreeGroupPrefix {
392	pub path: Option<SimplePath>,
393	#[format(prefix_ws(expr = Whitespace::REMOVE, if_ = self.path.is_some()))]
394	pub sep:  token::PathSep,
395}
396
397#[derive(PartialEq, Eq, Clone, Debug)]
398#[derive(serde::Serialize, serde::Deserialize)]
399#[derive(Parse, Formattable, Format, Print)]
400pub struct UseTreeSimple {
401	pub path: SimplePath,
402	#[format(prefix_ws = Whitespace::SINGLE)]
403	pub as_:  Option<UseTreeSimpleAs>,
404}
405
406#[derive(PartialEq, Eq, Clone, Debug)]
407#[derive(serde::Serialize, serde::Deserialize)]
408#[derive(Parse, Formattable, Format, Print)]
409pub struct UseTreeSimpleAs {
410	pub as_:   token::As,
411	#[parse(fatal)]
412	#[format(prefix_ws = Whitespace::SINGLE)]
413	pub value: UseTreeSimpleAsValue,
414}
415
416#[derive(PartialEq, Eq, Clone, Debug)]
417#[derive(serde::Serialize, serde::Deserialize)]
418#[derive(Parse, Formattable, Format, Print)]
419pub enum UseTreeSimpleAsValue {
420	Ident(Identifier),
421	Underscore(token::Underscore),
422}