1use either::Either;
4use hir_expand::name::Name;
5use intern::Symbol;
6use rustc_parse_format as parse;
7use span::SyntaxContext;
8use stdx::TupleExt;
9use syntax::{
10 TextRange,
11 ast::{self, IsString},
12};
13
14use crate::hir::ExprId;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct FormatArgs {
18 pub template: Box<[FormatArgsPiece]>,
19 pub arguments: FormatArguments,
20 pub orphans: Vec<ExprId>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct FormatArguments {
25 pub arguments: Box<[FormatArgument]>,
26 pub num_unnamed_args: usize,
27 pub num_explicit_args: usize,
28 pub names: Box<[(Name, usize)]>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum FormatArgsPiece {
33 Literal(Symbol),
34 Placeholder(FormatPlaceholder),
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct FormatPlaceholder {
39 pub argument: FormatArgPosition,
41 pub span: Option<TextRange>,
43 pub format_trait: FormatTrait,
45 pub format_options: FormatOptions,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct FormatArgPosition {
51 pub index: Result<usize, Either<usize, Name>>,
54 pub kind: FormatArgPositionKind,
56 pub span: Option<TextRange>,
58}
59
60#[derive(Copy, Debug, Clone, PartialEq, Eq)]
61pub enum FormatArgPositionKind {
62 Implicit,
64 Number,
66 Named,
68}
69
70#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
71pub enum FormatTrait {
72 Display,
74 Debug,
76 LowerExp,
78 UpperExp,
80 Octal,
82 Pointer,
84 Binary,
86 LowerHex,
88 UpperHex,
90}
91
92#[derive(Clone, Default, Debug, PartialEq, Eq)]
93pub struct FormatOptions {
94 pub width: Option<FormatCount>,
96 pub precision: Option<FormatCount>,
98 pub alignment: Option<FormatAlignment>,
100 pub fill: Option<char>,
102 pub sign: Option<FormatSign>,
104 pub alternate: bool,
106 pub zero_pad: bool,
108 pub debug_hex: Option<FormatDebugHex>,
110}
111#[derive(Copy, Clone, Debug, PartialEq, Eq)]
112pub enum FormatSign {
113 Plus,
115 Minus,
117}
118
119#[derive(Copy, Clone, Debug, PartialEq, Eq)]
120pub enum FormatDebugHex {
121 Lower,
123 Upper,
125}
126
127#[derive(Copy, Clone, Debug, PartialEq, Eq)]
128pub enum FormatAlignment {
129 Left,
131 Right,
133 Center,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq)]
138pub enum FormatCount {
139 Literal(u16),
141 Argument(FormatArgPosition),
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct FormatArgument {
147 pub kind: FormatArgumentKind,
148 pub expr: ExprId,
149}
150
151#[derive(Clone, PartialEq, Eq, Debug)]
152pub enum FormatArgumentKind {
153 Normal,
155 Named(Name),
157 Captured(Name),
159}
160
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164enum PositionUsedAs {
165 Placeholder(Option<TextRange>),
166 Precision,
167 Width,
168}
169use PositionUsedAs::*;
170
171#[allow(clippy::unnecessary_lazy_evaluations)]
172pub(crate) fn parse(
173 s: &ast::String,
174 fmt_snippet: Option<String>,
175 mut args: FormatArgumentsCollector,
176 is_direct_literal: bool,
177 mut synth: impl FnMut(Name, Option<TextRange>) -> ExprId,
178 mut record_usage: impl FnMut(Name, Option<TextRange>),
179 call_ctx: SyntaxContext,
180) -> FormatArgs {
181 let Ok(text) = s.value() else {
182 return FormatArgs {
183 template: Default::default(),
184 arguments: args.finish(),
185 orphans: vec![],
186 };
187 };
188 let str_style = match s.quote_offsets() {
189 Some(offsets) => {
190 let raw = usize::from(offsets.quotes.0.len()) - 1;
191 (raw != 0).then(|| raw - 1)
193 }
194 None => None,
195 };
196 let mut parser =
197 parse::Parser::new(&text, str_style, fmt_snippet, false, parse::ParseMode::Format);
198
199 let mut pieces = Vec::new();
200 while let Some(piece) = parser.next() {
201 if !parser.errors.is_empty() {
202 break;
203 } else {
204 pieces.push(piece);
205 }
206 }
207 let is_source_literal = parser.is_source_literal;
208 if !parser.errors.is_empty() {
209 return FormatArgs {
211 template: Default::default(),
212 arguments: args.finish(),
213 orphans: vec![],
214 };
215 }
216
217 let to_span = |inner_span: std::ops::Range<usize>| {
218 is_source_literal.then(|| {
219 TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
220 })
221 };
222
223 let mut used = vec![false; args.explicit_args().len()];
224 let mut invalid_refs = Vec::new();
225 let mut numeric_references_to_named_arg = Vec::new();
226
227 enum ArgRef<'a> {
228 Index(usize),
229 Name(&'a str, Option<TextRange>),
230 }
231 let mut lookup_arg = |arg: ArgRef<'_>,
232 span: Option<TextRange>,
233 used_as: PositionUsedAs,
234 kind: FormatArgPositionKind|
235 -> FormatArgPosition {
236 let index = match arg {
237 ArgRef::Index(index) => {
238 if let Some(arg) = args.by_index(index) {
239 used[index] = true;
240 if arg.kind.ident().is_some() {
241 numeric_references_to_named_arg.push((index, span, used_as));
243 }
244 Ok(index)
245 } else {
246 invalid_refs.push((Either::Left(index), span, used_as, kind));
248 Err(Either::Left(index))
249 }
250 }
251 ArgRef::Name(name, span) => {
252 let name = Name::new(name, call_ctx);
253 if let Some((index, _)) = args.by_name(&name) {
254 record_usage(name, span);
255 if index < args.explicit_args().len() {
257 used[index] = true;
259 }
260 Ok(index)
261 } else {
262 if !is_direct_literal {
264 invalid_refs.push((Either::Right(name.clone()), span, used_as, kind));
268 Err(Either::Right(name))
269 } else {
270 record_usage(name.clone(), span);
271 Ok(args.add(FormatArgument {
272 kind: FormatArgumentKind::Captured(name.clone()),
273 expr: synth(name, span),
276 }))
277 }
278 }
279 }
280 };
281 FormatArgPosition { index, kind, span }
282 };
283
284 let mut template = Vec::new();
285 let mut unfinished_literal = String::new();
286 let mut placeholder_index = 0;
287
288 for piece in pieces {
289 match piece {
290 parse::Piece::Lit(s) => {
291 unfinished_literal.push_str(s);
292 }
293 parse::Piece::NextArgument(arg) => {
294 let parse::Argument { position, position_span, format } = *arg;
295 if !unfinished_literal.is_empty() {
296 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
297 unfinished_literal.clear();
298 }
299
300 let span =
301 parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone()));
302 placeholder_index += 1;
303
304 let position_span = to_span(position_span);
305 let argument = match position {
306 parse::ArgumentImplicitlyIs(i) => lookup_arg(
307 ArgRef::Index(i),
308 position_span,
309 Placeholder(span),
310 FormatArgPositionKind::Implicit,
311 ),
312 parse::ArgumentIs(i) => lookup_arg(
313 ArgRef::Index(i),
314 position_span,
315 Placeholder(span),
316 FormatArgPositionKind::Number,
317 ),
318 parse::ArgumentNamed(name) => lookup_arg(
319 ArgRef::Name(name, position_span),
320 position_span,
321 Placeholder(span),
322 FormatArgPositionKind::Named,
323 ),
324 };
325
326 let alignment = match format.align {
327 parse::AlignUnknown => None,
328 parse::AlignLeft => Some(FormatAlignment::Left),
329 parse::AlignRight => Some(FormatAlignment::Right),
330 parse::AlignCenter => Some(FormatAlignment::Center),
331 };
332
333 let format_trait = match format.ty {
334 "" => FormatTrait::Display,
335 "?" => FormatTrait::Debug,
336 "e" => FormatTrait::LowerExp,
337 "E" => FormatTrait::UpperExp,
338 "o" => FormatTrait::Octal,
339 "p" => FormatTrait::Pointer,
340 "b" => FormatTrait::Binary,
341 "x" => FormatTrait::LowerHex,
342 "X" => FormatTrait::UpperHex,
343 _ => {
344 FormatTrait::Display
346 }
347 };
348
349 let precision_span = format.precision_span.and_then(to_span);
350 let precision = match format.precision {
351 parse::CountIs(n) => Some(FormatCount::Literal(n)),
352 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
353 ArgRef::Name(name, to_span(name_span)),
354 precision_span,
355 Precision,
356 FormatArgPositionKind::Named,
357 ))),
358 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
359 ArgRef::Index(i),
360 precision_span,
361 Precision,
362 FormatArgPositionKind::Number,
363 ))),
364 parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
365 ArgRef::Index(i),
366 precision_span,
367 Precision,
368 FormatArgPositionKind::Implicit,
369 ))),
370 parse::CountImplied => None,
371 };
372
373 let width_span = format.width_span.and_then(to_span);
374 let width = match format.width {
375 parse::CountIs(n) => Some(FormatCount::Literal(n)),
376 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
377 ArgRef::Name(name, to_span(name_span)),
378 width_span,
379 Width,
380 FormatArgPositionKind::Named,
381 ))),
382 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
383 ArgRef::Index(i),
384 width_span,
385 Width,
386 FormatArgPositionKind::Number,
387 ))),
388 parse::CountIsStar(_) => unreachable!(),
389 parse::CountImplied => None,
390 };
391
392 template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
393 argument,
394 span,
395 format_trait,
396 format_options: FormatOptions {
397 fill: format.fill,
398 alignment,
399 sign: format.sign.map(|s| match s {
400 parse::Sign::Plus => FormatSign::Plus,
401 parse::Sign::Minus => FormatSign::Minus,
402 }),
403 alternate: format.alternate,
404 zero_pad: format.zero_pad,
405 debug_hex: format.debug_hex.map(|s| match s {
406 parse::DebugHex::Lower => FormatDebugHex::Lower,
407 parse::DebugHex::Upper => FormatDebugHex::Upper,
408 }),
409 precision,
410 width,
411 },
412 }));
413 }
414 }
415 }
416
417 if !unfinished_literal.is_empty() {
418 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
419 }
420
421 if !invalid_refs.is_empty() {
422 }
424
425 let unused = used
426 .iter()
427 .enumerate()
428 .filter(|&(_, used)| !used)
429 .map(|(i, _)| {
430 let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
431 (args.explicit_args()[i].expr, named)
432 })
433 .collect::<Vec<_>>();
434
435 if !unused.is_empty() {
436 }
438
439 FormatArgs {
440 template: template.into_boxed_slice(),
441 arguments: args.finish(),
442 orphans: unused.into_iter().map(TupleExt::head).collect(),
443 }
444}
445
446#[derive(Clone, Debug, Default, Eq, PartialEq)]
447pub struct FormatArgumentsCollector {
448 arguments: Vec<FormatArgument>,
449 num_unnamed_args: usize,
450 num_explicit_args: usize,
451 names: Vec<(Name, usize)>,
452}
453
454impl FormatArgumentsCollector {
455 pub(crate) fn finish(self) -> FormatArguments {
456 FormatArguments {
457 arguments: self.arguments.into_boxed_slice(),
458 num_unnamed_args: self.num_unnamed_args,
459 num_explicit_args: self.num_explicit_args,
460 names: self.names.into_boxed_slice(),
461 }
462 }
463
464 pub fn add(&mut self, arg: FormatArgument) -> usize {
465 let index = self.arguments.len();
466 if let Some(name) = arg.kind.ident() {
467 self.names.push((name.clone(), index));
468 } else if self.names.is_empty() {
469 self.num_unnamed_args += 1;
472 }
473 if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
474 assert_eq!(
477 self.num_explicit_args,
478 self.arguments.len(),
479 "captured arguments must be added last"
480 );
481 self.num_explicit_args += 1;
482 }
483 self.arguments.push(arg);
484 index
485 }
486
487 pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> {
488 let &(_, i) = self.names.iter().find(|(n, _)| n == name)?;
489 Some((i, &self.arguments[i]))
490 }
491
492 pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
493 (i < self.num_explicit_args).then(|| &self.arguments[i])
494 }
495
496 pub fn unnamed_args(&self) -> &[FormatArgument] {
497 &self.arguments[..self.num_unnamed_args]
498 }
499
500 pub fn named_args(&self) -> &[FormatArgument] {
501 &self.arguments[self.num_unnamed_args..self.num_explicit_args]
502 }
503
504 pub fn explicit_args(&self) -> &[FormatArgument] {
505 &self.arguments[..self.num_explicit_args]
506 }
507
508 pub fn all_args(&self) -> &[FormatArgument] {
509 &self.arguments[..]
510 }
511
512 pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> {
513 &mut self.arguments
514 }
515}
516
517impl FormatArgumentKind {
518 pub fn ident(&self) -> Option<&Name> {
519 match self {
520 Self::Normal => None,
521 Self::Named(id) => Some(id),
522 Self::Captured(id) => Some(id),
523 }
524 }
525}