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 AstPtr, 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 pub syntax: Option<AstPtr<ast::Expr>>,
150}
151
152#[derive(Clone, PartialEq, Eq, Debug)]
153pub enum FormatArgumentKind {
154 Normal,
156 Named(Name),
158 Captured(Name),
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165enum PositionUsedAs {
166 Placeholder(Option<TextRange>),
167 Precision,
168 Width,
169}
170use PositionUsedAs::*;
171
172pub(crate) fn parse(
173 s: &ast::String,
174 string_ptr: AstPtr<ast::Expr>,
175 fmt_snippet: Option<String>,
176 mut args: FormatArgumentsCollector,
177 is_direct_literal: bool,
178 mut synth: impl FnMut(Name, Option<TextRange>) -> ExprId,
179 mut record_usage: impl FnMut(Name, Option<TextRange>),
180 call_ctx: SyntaxContext,
181) -> FormatArgs {
182 let Ok(text) = s.value() else {
183 return FormatArgs {
184 template: Default::default(),
185 arguments: args.finish(),
186 orphans: vec![],
187 };
188 };
189 let str_style = match s.quote_offsets() {
190 Some(offsets) => {
191 let raw = usize::from(offsets.quotes.0.len()) - 1;
192 (raw != 0).then(|| raw - 1)
194 }
195 None => None,
196 };
197 let mut parser =
198 parse::Parser::new(&text, str_style, fmt_snippet, false, parse::ParseMode::Format);
199
200 let mut pieces = Vec::new();
201 while let Some(piece) = parser.next() {
202 if !parser.errors.is_empty() {
203 break;
204 } else {
205 pieces.push(piece);
206 }
207 }
208 let is_source_literal = parser.is_source_literal;
209 if !parser.errors.is_empty() {
210 return FormatArgs {
212 template: Default::default(),
213 arguments: args.finish(),
214 orphans: vec![],
215 };
216 }
217
218 let to_span = |inner_span: std::ops::Range<usize>| {
219 is_source_literal.then(|| {
220 TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
221 })
222 };
223
224 let mut used = vec![false; args.explicit_args().len()];
225 let mut invalid_refs = Vec::new();
226 let mut numeric_references_to_named_arg = Vec::new();
227
228 enum ArgRef<'a> {
229 Index(usize),
230 Name(&'a str, Option<TextRange>),
231 }
232 let mut lookup_arg = |arg: ArgRef<'_>,
233 span: Option<TextRange>,
234 used_as: PositionUsedAs,
235 kind: FormatArgPositionKind|
236 -> FormatArgPosition {
237 let index = match arg {
238 ArgRef::Index(index) => {
239 if let Some(arg) = args.by_index(index) {
240 used[index] = true;
241 if arg.kind.ident().is_some() {
242 numeric_references_to_named_arg.push((index, span, used_as));
244 }
245 Ok(index)
246 } else {
247 invalid_refs.push((Either::Left(index), span, used_as, kind));
249 Err(Either::Left(index))
250 }
251 }
252 ArgRef::Name(name, span) => {
253 let name = Name::new(name, call_ctx);
254 if let Some((index, _)) = args.by_name(&name) {
255 record_usage(name, span);
256 if index < args.explicit_args().len() {
258 used[index] = true;
260 }
261 Ok(index)
262 } else {
263 if !is_direct_literal {
265 invalid_refs.push((Either::Right(name.clone()), span, used_as, kind));
269 Err(Either::Right(name))
270 } else {
271 record_usage(name.clone(), span);
272 Ok(args.add(FormatArgument {
273 kind: FormatArgumentKind::Captured(name.clone()),
274 expr: synth(name, span),
277 syntax: Some(string_ptr),
282 }))
283 }
284 }
285 }
286 };
287 FormatArgPosition { index, kind, span }
288 };
289
290 let mut template = Vec::new();
291 let mut unfinished_literal = String::new();
292 let mut placeholder_index = 0;
293
294 for piece in pieces {
295 match piece {
296 parse::Piece::Lit(s) => {
297 unfinished_literal.push_str(s);
298 }
299 parse::Piece::NextArgument(arg) => {
300 let parse::Argument { position, position_span, format } = *arg;
301 if !unfinished_literal.is_empty() {
302 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
303 unfinished_literal.clear();
304 }
305
306 let span =
307 parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone()));
308 placeholder_index += 1;
309
310 let position_span = to_span(position_span);
311 let argument = match position {
312 parse::ArgumentImplicitlyIs(i) => lookup_arg(
313 ArgRef::Index(i),
314 position_span,
315 Placeholder(span),
316 FormatArgPositionKind::Implicit,
317 ),
318 parse::ArgumentIs(i) => lookup_arg(
319 ArgRef::Index(i),
320 position_span,
321 Placeholder(span),
322 FormatArgPositionKind::Number,
323 ),
324 parse::ArgumentNamed(name) => lookup_arg(
325 ArgRef::Name(name, position_span),
326 position_span,
327 Placeholder(span),
328 FormatArgPositionKind::Named,
329 ),
330 };
331
332 let alignment = match format.align {
333 parse::AlignUnknown => None,
334 parse::AlignLeft => Some(FormatAlignment::Left),
335 parse::AlignRight => Some(FormatAlignment::Right),
336 parse::AlignCenter => Some(FormatAlignment::Center),
337 };
338
339 let format_trait = match format.ty {
340 "" => FormatTrait::Display,
341 "?" => FormatTrait::Debug,
342 "e" => FormatTrait::LowerExp,
343 "E" => FormatTrait::UpperExp,
344 "o" => FormatTrait::Octal,
345 "p" => FormatTrait::Pointer,
346 "b" => FormatTrait::Binary,
347 "x" => FormatTrait::LowerHex,
348 "X" => FormatTrait::UpperHex,
349 _ => {
350 FormatTrait::Display
352 }
353 };
354
355 let precision_span = format.precision_span.and_then(to_span);
356 let precision = match format.precision {
357 parse::CountIs(n) => Some(FormatCount::Literal(n)),
358 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
359 ArgRef::Name(name, to_span(name_span)),
360 precision_span,
361 Precision,
362 FormatArgPositionKind::Named,
363 ))),
364 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
365 ArgRef::Index(i),
366 precision_span,
367 Precision,
368 FormatArgPositionKind::Number,
369 ))),
370 parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
371 ArgRef::Index(i),
372 precision_span,
373 Precision,
374 FormatArgPositionKind::Implicit,
375 ))),
376 parse::CountImplied => None,
377 };
378
379 let width_span = format.width_span.and_then(to_span);
380 let width = match format.width {
381 parse::CountIs(n) => Some(FormatCount::Literal(n)),
382 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
383 ArgRef::Name(name, to_span(name_span)),
384 width_span,
385 Width,
386 FormatArgPositionKind::Named,
387 ))),
388 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
389 ArgRef::Index(i),
390 width_span,
391 Width,
392 FormatArgPositionKind::Number,
393 ))),
394 parse::CountIsStar(_) => unreachable!(),
395 parse::CountImplied => None,
396 };
397
398 template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
399 argument,
400 span,
401 format_trait,
402 format_options: FormatOptions {
403 fill: format.fill,
404 alignment,
405 sign: format.sign.map(|s| match s {
406 parse::Sign::Plus => FormatSign::Plus,
407 parse::Sign::Minus => FormatSign::Minus,
408 }),
409 alternate: format.alternate,
410 zero_pad: format.zero_pad,
411 debug_hex: format.debug_hex.map(|s| match s {
412 parse::DebugHex::Lower => FormatDebugHex::Lower,
413 parse::DebugHex::Upper => FormatDebugHex::Upper,
414 }),
415 precision,
416 width,
417 },
418 }));
419 }
420 }
421 }
422
423 if !unfinished_literal.is_empty() {
424 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
425 }
426
427 if !invalid_refs.is_empty() {
428 }
430
431 let unused = used
432 .iter()
433 .enumerate()
434 .filter(|&(_, used)| !used)
435 .map(|(i, _)| {
436 let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
437 (args.explicit_args()[i].expr, named)
438 })
439 .collect::<Vec<_>>();
440
441 if !unused.is_empty() {
442 }
444
445 FormatArgs {
446 template: template.into_boxed_slice(),
447 arguments: args.finish(),
448 orphans: unused.into_iter().map(TupleExt::head).collect(),
449 }
450}
451
452#[derive(Clone, Debug, Default, Eq, PartialEq)]
453pub struct FormatArgumentsCollector {
454 arguments: Vec<FormatArgument>,
455 num_unnamed_args: usize,
456 num_explicit_args: usize,
457 names: Vec<(Name, usize)>,
458}
459
460impl FormatArgumentsCollector {
461 pub(crate) fn finish(self) -> FormatArguments {
462 FormatArguments {
463 arguments: self.arguments.into_boxed_slice(),
464 num_unnamed_args: self.num_unnamed_args,
465 num_explicit_args: self.num_explicit_args,
466 names: self.names.into_boxed_slice(),
467 }
468 }
469
470 pub fn add(&mut self, arg: FormatArgument) -> usize {
471 let index = self.arguments.len();
472 if let Some(name) = arg.kind.ident() {
473 self.names.push((name.clone(), index));
474 } else if self.names.is_empty() {
475 self.num_unnamed_args += 1;
478 }
479 if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
480 assert_eq!(
483 self.num_explicit_args,
484 self.arguments.len(),
485 "captured arguments must be added last"
486 );
487 self.num_explicit_args += 1;
488 }
489 self.arguments.push(arg);
490 index
491 }
492
493 pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> {
494 let &(_, i) = self.names.iter().find(|(n, _)| n == name)?;
495 Some((i, &self.arguments[i]))
496 }
497
498 pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
499 (i < self.num_explicit_args).then(|| &self.arguments[i])
500 }
501
502 pub fn unnamed_args(&self) -> &[FormatArgument] {
503 &self.arguments[..self.num_unnamed_args]
504 }
505
506 pub fn named_args(&self) -> &[FormatArgument] {
507 &self.arguments[self.num_unnamed_args..self.num_explicit_args]
508 }
509
510 pub fn explicit_args(&self) -> &[FormatArgument] {
511 &self.arguments[..self.num_explicit_args]
512 }
513
514 pub fn all_args(&self) -> &[FormatArgument] {
515 &self.arguments[..]
516 }
517
518 pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> {
519 &mut self.arguments
520 }
521}
522
523impl FormatArgumentKind {
524 pub fn ident(&self) -> Option<&Name> {
525 match self {
526 Self::Normal => None,
527 Self::Named(id) => Some(id),
528 Self::Captured(id) => Some(id),
529 }
530 }
531}