]>
Commit | Line | Data |
---|---|---|
74b04a01 | 1 | use rustc_ast::ptr::P; |
2b03887a | 2 | use rustc_ast::token; |
74b04a01 | 3 | use rustc_ast::tokenstream::TokenStream; |
9ffffee4 FG |
4 | use rustc_ast::{ |
5 | Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs, | |
6 | FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount, | |
7 | FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, | |
8 | }; | |
2b03887a | 9 | use rustc_data_structures::fx::FxHashSet; |
04454e1e | 10 | use rustc_errors::{pluralize, Applicability, MultiSpan, PResult}; |
dfeec247 | 11 | use rustc_expand::base::{self, *}; |
f035d41b | 12 | use rustc_parse_format as parse; |
2b03887a | 13 | use rustc_span::symbol::{Ident, Symbol}; |
064997fb | 14 | use rustc_span::{BytePos, InnerSpan, Span}; |
1a4d82fc | 15 | |
064997fb FG |
16 | use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY; |
17 | use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId}; | |
064997fb | 18 | |
2b03887a FG |
19 | // The format_args!() macro is expanded in three steps: |
20 | // 1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax, | |
21 | // but doesn't parse the template (the literal) itself. | |
22 | // 2. Second, `make_format_args` will parse the template, the format options, resolve argument references, | |
9ffffee4 FG |
23 | // produce diagnostics, and turn the whole thing into a `FormatArgs` AST node. |
24 | // 3. Much later, in AST lowering (rustc_ast_lowering), that `FormatArgs` structure will be turned | |
25 | // into the expression of type `core::fmt::Arguments`. | |
5bcae85e | 26 | |
9ffffee4 | 27 | // See rustc_ast/src/format.rs for the FormatArgs structure and glossary. |
1a4d82fc | 28 | |
2b03887a FG |
29 | // Only used in parse_args and report_invalid_references, |
30 | // to indicate how a referred argument was used. | |
31 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
32 | enum PositionUsedAs { | |
33 | Placeholder(Option<Span>), | |
34 | Precision, | |
35 | Width, | |
1a4d82fc | 36 | } |
2b03887a | 37 | use PositionUsedAs::*; |
1a4d82fc | 38 | |
0731742a | 39 | /// Parses the arguments from the given list of tokens, returning the diagnostic |
1a4d82fc JJ |
40 | /// if there's a parse error so we can continue parsing other format! |
41 | /// expressions. | |
42 | /// | |
43 | /// If parsing succeeds, the return value is: | |
041b39d2 XL |
44 | /// |
45 | /// ```text | |
2b03887a | 46 | /// Ok((fmtstr, parsed arguments)) |
92a42be0 | 47 | /// ``` |
0731742a XL |
48 | fn parse_args<'a>( |
49 | ecx: &mut ExtCtxt<'a>, | |
50 | sp: Span, | |
e1599b0c | 51 | tts: TokenStream, |
2b03887a FG |
52 | ) -> PResult<'a, (P<Expr>, FormatArguments)> { |
53 | let mut args = FormatArguments::new(); | |
1a4d82fc JJ |
54 | |
55 | let mut p = ecx.new_parser_from_tts(tts); | |
56 | ||
57 | if p.token == token::Eof { | |
0731742a | 58 | return Err(ecx.struct_span_err(sp, "requires at least a format string argument")); |
1a4d82fc | 59 | } |
b7449926 | 60 | |
1b1a35ee XL |
61 | let first_token = &p.token; |
62 | let fmtstr = match first_token.kind { | |
63 | token::TokenKind::Literal(token::Lit { | |
64 | kind: token::LitKind::Str | token::LitKind::StrRaw(_), | |
65 | .. | |
66 | }) => { | |
67 | // If the first token is a string literal, then a format expression | |
68 | // is constructed from it. | |
69 | // | |
70 | // This allows us to properly handle cases when the first comma | |
71 | // after the format string is mistakenly replaced with any operator, | |
72 | // which cause the expression parser to eat too much tokens. | |
73 | p.parse_literal_maybe_minus()? | |
74 | } | |
75 | _ => { | |
76 | // Otherwise, we fall back to the expression parser. | |
77 | p.parse_expr()? | |
78 | } | |
79 | }; | |
80 | ||
e1599b0c | 81 | let mut first = true; |
b7449926 | 82 | |
1a4d82fc | 83 | while p.token != token::Eof { |
9cc50fc6 | 84 | if !p.eat(&token::Comma) { |
e1599b0c | 85 | if first { |
1b1a35ee XL |
86 | p.clear_expected_tokens(); |
87 | } | |
88 | ||
c295e0f8 XL |
89 | match p.expect(&token::Comma) { |
90 | Err(mut err) => { | |
91 | match token::TokenKind::Comma.similar_tokens() { | |
92 | Some(tks) if tks.contains(&p.token.kind) => { | |
93 | // If a similar token is found, then it may be a typo. We | |
94 | // consider it as a comma, and continue parsing. | |
95 | err.emit(); | |
96 | p.bump(); | |
97 | } | |
98 | // Otherwise stop the parsing and return the error. | |
99 | _ => return Err(err), | |
100 | } | |
101 | } | |
102 | Ok(recovered) => { | |
103 | assert!(recovered); | |
1b1a35ee | 104 | } |
e1599b0c | 105 | } |
1a4d82fc | 106 | } |
e1599b0c | 107 | first = false; |
9e0c209e SL |
108 | if p.token == token::Eof { |
109 | break; | |
110 | } // accept trailing commas | |
74b04a01 XL |
111 | match p.token.ident() { |
112 | Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { | |
0731742a | 113 | p.bump(); |
74b04a01 | 114 | p.expect(&token::Eq)?; |
2b03887a FG |
115 | let expr = p.parse_expr()?; |
116 | if let Some((_, prev)) = args.by_name(ident.name) { | |
117 | ecx.struct_span_err( | |
118 | ident.span, | |
119 | &format!("duplicate argument named `{}`", ident), | |
120 | ) | |
121 | .span_label(prev.kind.ident().unwrap().span, "previously here") | |
122 | .span_label(ident.span, "duplicate argument") | |
123 | .emit(); | |
74b04a01 XL |
124 | continue; |
125 | } | |
2b03887a | 126 | args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); |
1a4d82fc | 127 | } |
74b04a01 | 128 | _ => { |
2b03887a FG |
129 | let expr = p.parse_expr()?; |
130 | if !args.named_args().is_empty() { | |
74b04a01 | 131 | let mut err = ecx.struct_span_err( |
2b03887a | 132 | expr.span, |
74b04a01 XL |
133 | "positional arguments cannot follow named arguments", |
134 | ); | |
2b03887a FG |
135 | err.span_label( |
136 | expr.span, | |
137 | "positional arguments must be before named arguments", | |
138 | ); | |
139 | for arg in args.named_args() { | |
140 | if let Some(name) = arg.kind.ident() { | |
141 | err.span_label(name.span.to(arg.expr.span), "named argument"); | |
142 | } | |
74b04a01 XL |
143 | } |
144 | err.emit(); | |
416331ca | 145 | } |
2b03887a | 146 | args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); |
416331ca | 147 | } |
1a4d82fc JJ |
148 | } |
149 | } | |
2b03887a | 150 | Ok((fmtstr, args)) |
1a4d82fc JJ |
151 | } |
152 | ||
2b03887a | 153 | pub fn make_format_args( |
416331ca | 154 | ecx: &mut ExtCtxt<'_>, |
2b03887a FG |
155 | efmt: P<Expr>, |
156 | mut args: FormatArguments, | |
416331ca | 157 | append_newline: bool, |
2b03887a | 158 | ) -> Result<FormatArgs, ()> { |
8faf50e0 | 159 | let msg = "format argument must be a string literal"; |
2b03887a | 160 | let unexpanded_fmt_span = efmt.span; |
e1599b0c | 161 | let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) { |
8faf50e0 | 162 | Ok(mut fmt) if append_newline => { |
e1599b0c | 163 | fmt.0 = Symbol::intern(&format!("{}\n", fmt.0)); |
8faf50e0 XL |
164 | fmt |
165 | } | |
166 | Ok(fmt) => fmt, | |
0731742a | 167 | Err(err) => { |
c295e0f8 | 168 | if let Some((mut err, suggested)) = err { |
2b03887a | 169 | let sugg_fmt = match args.explicit_args().len() { |
0731742a | 170 | 0 => "{}".to_string(), |
2b03887a | 171 | _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())), |
0731742a | 172 | }; |
c295e0f8 XL |
173 | if !suggested { |
174 | err.span_suggestion( | |
2b03887a | 175 | unexpanded_fmt_span.shrink_to_lo(), |
c295e0f8 XL |
176 | "you might be missing a string literal to format with", |
177 | format!("\"{}\", ", sugg_fmt), | |
178 | Applicability::MaybeIncorrect, | |
179 | ); | |
180 | } | |
0731742a XL |
181 | err.emit(); |
182 | } | |
2b03887a | 183 | return Err(()); |
8faf50e0 XL |
184 | } |
185 | }; | |
b7449926 | 186 | |
e1599b0c | 187 | let str_style = match fmt_style { |
2b03887a FG |
188 | rustc_ast::StrStyle::Cooked => None, |
189 | rustc_ast::StrStyle::Raw(raw) => Some(raw as usize), | |
0731742a XL |
190 | }; |
191 | ||
a2a8927a | 192 | let fmt_str = fmt_str.as_str(); // for the suggestions below |
2b03887a | 193 | let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok(); |
f9f354fc XL |
194 | let mut parser = parse::Parser::new( |
195 | fmt_str, | |
196 | str_style, | |
197 | fmt_snippet, | |
198 | append_newline, | |
199 | parse::ParseMode::Format, | |
200 | ); | |
b7449926 | 201 | |
2b03887a | 202 | let mut pieces = Vec::new(); |
b7449926 XL |
203 | while let Some(piece) = parser.next() { |
204 | if !parser.errors.is_empty() { | |
205 | break; | |
206 | } else { | |
2b03887a | 207 | pieces.push(piece); |
b7449926 XL |
208 | } |
209 | } | |
210 | ||
2b03887a FG |
211 | let is_literal = parser.is_literal; |
212 | ||
b7449926 XL |
213 | if !parser.errors.is_empty() { |
214 | let err = parser.errors.remove(0); | |
2b03887a | 215 | let sp = if is_literal { |
04454e1e | 216 | fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)) |
136023e0 XL |
217 | } else { |
218 | // The format string could be another macro invocation, e.g.: | |
219 | // format!(concat!("abc", "{}"), 4); | |
220 | // However, `err.span` is an inner span relative to the *result* of | |
221 | // the macro invocation, which is why we would get a nonsensical | |
222 | // result calling `fmt_span.from_inner(err.span)` as above, and | |
223 | // might even end up inside a multibyte character (issue #86085). | |
224 | // Therefore, we conservatively report the error for the entire | |
225 | // argument span here. | |
226 | fmt_span | |
227 | }; | |
dfeec247 | 228 | let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", err.description)); |
b7449926 XL |
229 | e.span_label(sp, err.label + " in format string"); |
230 | if let Some(note) = err.note { | |
231 | e.note(¬e); | |
232 | } | |
2b03887a FG |
233 | if let Some((label, span)) = err.secondary_label && is_literal { |
234 | e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label); | |
0731742a | 235 | } |
064997fb FG |
236 | if err.should_be_replaced_with_positional_argument { |
237 | let captured_arg_span = | |
238 | fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)); | |
064997fb | 239 | if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { |
2b03887a | 240 | let span = match args.unnamed_args().last() { |
064997fb | 241 | Some(arg) => arg.expr.span, |
2b03887a | 242 | None => fmt_span, |
064997fb FG |
243 | }; |
244 | e.multipart_suggestion_verbose( | |
245 | "consider using a positional formatting argument instead", | |
246 | vec![ | |
2b03887a | 247 | (captured_arg_span, args.unnamed_args().len().to_string()), |
064997fb FG |
248 | (span.shrink_to_hi(), format!(", {}", arg)), |
249 | ], | |
250 | Applicability::MachineApplicable, | |
251 | ); | |
252 | } | |
253 | } | |
b7449926 | 254 | e.emit(); |
2b03887a | 255 | return Err(()); |
b7449926 XL |
256 | } |
257 | ||
2b03887a FG |
258 | let to_span = |inner_span: rustc_parse_format::InnerSpan| { |
259 | is_literal.then(|| { | |
260 | fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }) | |
261 | }) | |
262 | }; | |
263 | ||
264 | let mut used = vec![false; args.explicit_args().len()]; | |
265 | let mut invalid_refs = Vec::new(); | |
266 | let mut numeric_refences_to_named_arg = Vec::new(); | |
b7449926 | 267 | |
2b03887a FG |
268 | enum ArgRef<'a> { |
269 | Index(usize), | |
270 | Name(&'a str, Option<Span>), | |
271 | } | |
272 | use ArgRef::*; | |
273 | ||
274 | let mut lookup_arg = |arg: ArgRef<'_>, | |
275 | span: Option<Span>, | |
276 | used_as: PositionUsedAs, | |
277 | kind: FormatArgPositionKind| | |
278 | -> FormatArgPosition { | |
279 | let index = match arg { | |
280 | Index(index) => { | |
281 | if let Some(arg) = args.by_index(index) { | |
282 | used[index] = true; | |
283 | if arg.kind.ident().is_some() { | |
284 | // This was a named argument, but it was used as a positional argument. | |
285 | numeric_refences_to_named_arg.push((index, span, used_as)); | |
286 | } | |
287 | Ok(index) | |
288 | } else { | |
289 | // Doesn't exist as an explicit argument. | |
290 | invalid_refs.push((index, span, used_as, kind)); | |
291 | Err(index) | |
292 | } | |
293 | } | |
294 | Name(name, span) => { | |
295 | let name = Symbol::intern(name); | |
296 | if let Some((index, _)) = args.by_name(name) { | |
297 | // Name found in `args`, so we resolve it to its index. | |
298 | if index < args.explicit_args().len() { | |
299 | // Mark it as used, if it was an explicit argument. | |
300 | used[index] = true; | |
301 | } | |
302 | Ok(index) | |
303 | } else { | |
304 | // Name not found in `args`, so we add it as an implicitly captured argument. | |
305 | let span = span.unwrap_or(fmt_span); | |
306 | let ident = Ident::new(name, span); | |
307 | let expr = if is_literal { | |
308 | ecx.expr_ident(span, ident) | |
309 | } else { | |
310 | // For the moment capturing variables from format strings expanded from macros is | |
311 | // disabled (see RFC #2795) | |
312 | ecx.struct_span_err(span, &format!("there is no argument named `{name}`")) | |
313 | .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?")) | |
314 | .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro") | |
315 | .emit(); | |
316 | DummyResult::raw_expr(span, true) | |
317 | }; | |
318 | Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) | |
319 | } | |
320 | } | |
321 | }; | |
322 | FormatArgPosition { index, kind, span } | |
1a4d82fc JJ |
323 | }; |
324 | ||
2b03887a FG |
325 | let mut template = Vec::new(); |
326 | let mut unfinished_literal = String::new(); | |
327 | let mut placeholder_index = 0; | |
dfeec247 | 328 | |
2b03887a FG |
329 | for piece in pieces { |
330 | match piece { | |
331 | parse::Piece::String(s) => { | |
332 | unfinished_literal.push_str(s); | |
333 | } | |
9c376795 | 334 | parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => { |
2b03887a FG |
335 | if !unfinished_literal.is_empty() { |
336 | template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); | |
337 | unfinished_literal.clear(); | |
338 | } | |
abe05a73 | 339 | |
2b03887a FG |
340 | let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s)); |
341 | placeholder_index += 1; | |
342 | ||
343 | let position_span = to_span(position_span); | |
344 | let argument = match position { | |
345 | parse::ArgumentImplicitlyIs(i) => lookup_arg( | |
346 | Index(i), | |
347 | position_span, | |
348 | Placeholder(span), | |
349 | FormatArgPositionKind::Implicit, | |
350 | ), | |
351 | parse::ArgumentIs(i) => lookup_arg( | |
352 | Index(i), | |
353 | position_span, | |
354 | Placeholder(span), | |
355 | FormatArgPositionKind::Number, | |
356 | ), | |
357 | parse::ArgumentNamed(name) => lookup_arg( | |
358 | Name(name, position_span), | |
359 | position_span, | |
360 | Placeholder(span), | |
361 | FormatArgPositionKind::Named, | |
362 | ), | |
363 | }; | |
5bcae85e | 364 | |
2b03887a FG |
365 | let alignment = match format.align { |
366 | parse::AlignUnknown => None, | |
367 | parse::AlignLeft => Some(FormatAlignment::Left), | |
368 | parse::AlignRight => Some(FormatAlignment::Right), | |
369 | parse::AlignCenter => Some(FormatAlignment::Center), | |
370 | }; | |
b7449926 | 371 | |
2b03887a FG |
372 | let format_trait = match format.ty { |
373 | "" => FormatTrait::Display, | |
374 | "?" => FormatTrait::Debug, | |
375 | "e" => FormatTrait::LowerExp, | |
376 | "E" => FormatTrait::UpperExp, | |
377 | "o" => FormatTrait::Octal, | |
378 | "p" => FormatTrait::Pointer, | |
379 | "b" => FormatTrait::Binary, | |
380 | "x" => FormatTrait::LowerHex, | |
381 | "X" => FormatTrait::UpperHex, | |
382 | _ => { | |
383 | invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span); | |
384 | FormatTrait::Display | |
385 | } | |
386 | }; | |
387 | ||
388 | let precision_span = format.precision_span.and_then(to_span); | |
389 | let precision = match format.precision { | |
390 | parse::CountIs(n) => Some(FormatCount::Literal(n)), | |
391 | parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( | |
392 | Name(name, to_span(name_span)), | |
393 | precision_span, | |
394 | Precision, | |
395 | FormatArgPositionKind::Named, | |
396 | ))), | |
397 | parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( | |
398 | Index(i), | |
399 | precision_span, | |
400 | Precision, | |
401 | FormatArgPositionKind::Number, | |
402 | ))), | |
403 | parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg( | |
404 | Index(i), | |
405 | precision_span, | |
406 | Precision, | |
407 | FormatArgPositionKind::Implicit, | |
408 | ))), | |
409 | parse::CountImplied => None, | |
410 | }; | |
411 | ||
412 | let width_span = format.width_span.and_then(to_span); | |
413 | let width = match format.width { | |
414 | parse::CountIs(n) => Some(FormatCount::Literal(n)), | |
415 | parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( | |
416 | Name(name, to_span(name_span)), | |
417 | width_span, | |
418 | Width, | |
419 | FormatArgPositionKind::Named, | |
420 | ))), | |
421 | parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( | |
422 | Index(i), | |
423 | width_span, | |
424 | Width, | |
425 | FormatArgPositionKind::Number, | |
426 | ))), | |
427 | parse::CountIsStar(_) => unreachable!(), | |
428 | parse::CountImplied => None, | |
429 | }; | |
430 | ||
431 | template.push(FormatArgsPiece::Placeholder(FormatPlaceholder { | |
432 | argument, | |
433 | span, | |
434 | format_trait, | |
435 | format_options: FormatOptions { | |
436 | fill: format.fill, | |
437 | alignment, | |
9ffffee4 FG |
438 | sign: format.sign.map(|s| match s { |
439 | parse::Sign::Plus => FormatSign::Plus, | |
440 | parse::Sign::Minus => FormatSign::Minus, | |
441 | }), | |
442 | alternate: format.alternate, | |
443 | zero_pad: format.zero_pad, | |
444 | debug_hex: format.debug_hex.map(|s| match s { | |
445 | parse::DebugHex::Lower => FormatDebugHex::Lower, | |
446 | parse::DebugHex::Upper => FormatDebugHex::Upper, | |
447 | }), | |
2b03887a FG |
448 | precision, |
449 | width, | |
450 | }, | |
451 | })); | |
452 | } | |
5bcae85e SL |
453 | } |
454 | } | |
455 | ||
2b03887a FG |
456 | if !unfinished_literal.is_empty() { |
457 | template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); | |
1a4d82fc JJ |
458 | } |
459 | ||
2b03887a FG |
460 | if !invalid_refs.is_empty() { |
461 | report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser); | |
abe05a73 XL |
462 | } |
463 | ||
2b03887a | 464 | let unused = used |
dfeec247 XL |
465 | .iter() |
466 | .enumerate() | |
2b03887a | 467 | .filter(|&(_, used)| !used) |
dfeec247 | 468 | .map(|(i, _)| { |
2b03887a | 469 | let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind { |
dfeec247 XL |
470 | "named argument never used" |
471 | } else { | |
dfeec247 XL |
472 | "argument never used" |
473 | }; | |
2b03887a | 474 | (args.explicit_args()[i].expr.span, msg) |
dfeec247 XL |
475 | }) |
476 | .collect::<Vec<_>>(); | |
b7449926 | 477 | |
2b03887a FG |
478 | if !unused.is_empty() { |
479 | // If there's a lot of unused arguments, | |
480 | // let's check if this format arguments looks like another syntax (printf / shell). | |
481 | let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2; | |
482 | report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span); | |
483 | } | |
476ff2be | 484 | |
2b03887a FG |
485 | // Only check for unused named argument names if there are no other errors to avoid causing |
486 | // too much noise in output errors, such as when a named argument is entirely unused. | |
487 | if invalid_refs.is_empty() && ecx.sess.err_count() == 0 { | |
488 | for &(index, span, used_as) in &numeric_refences_to_named_arg { | |
489 | let (position_sp_to_replace, position_sp_for_msg) = match used_as { | |
490 | Placeholder(pspan) => (span, pspan), | |
491 | Precision => { | |
492 | // Strip the leading `.` for precision. | |
493 | let span = span.map(|span| span.with_lo(span.lo() + BytePos(1))); | |
494 | (span, span) | |
0731742a | 495 | } |
2b03887a FG |
496 | Width => (span, span), |
497 | }; | |
498 | let arg_name = args.explicit_args()[index].kind.ident().unwrap(); | |
499 | ecx.buffered_early_lint.push(BufferedEarlyLint { | |
500 | span: arg_name.span.into(), | |
501 | msg: format!("named argument `{}` is not used by name", arg_name.name).into(), | |
502 | node_id: rustc_ast::CRATE_NODE_ID, | |
503 | lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY), | |
504 | diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally { | |
505 | position_sp_to_replace, | |
506 | position_sp_for_msg, | |
507 | named_arg_sp: arg_name.span, | |
508 | named_arg_name: arg_name.name.to_string(), | |
509 | is_formatting_arg: matches!(used_as, Width | Precision), | |
510 | }, | |
511 | }); | |
512 | } | |
513 | } | |
476ff2be | 514 | |
2b03887a FG |
515 | Ok(FormatArgs { span: fmt_span, template, arguments: args }) |
516 | } | |
476ff2be | 517 | |
2b03887a FG |
518 | fn invalid_placeholder_type_error( |
519 | ecx: &ExtCtxt<'_>, | |
520 | ty: &str, | |
521 | ty_span: Option<rustc_parse_format::InnerSpan>, | |
522 | fmt_span: Span, | |
523 | ) { | |
524 | let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end))); | |
525 | let mut err = | |
526 | ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty)); | |
527 | err.note( | |
528 | "the only appropriate formatting traits are:\n\ | |
529 | - ``, which uses the `Display` trait\n\ | |
530 | - `?`, which uses the `Debug` trait\n\ | |
531 | - `e`, which uses the `LowerExp` trait\n\ | |
532 | - `E`, which uses the `UpperExp` trait\n\ | |
533 | - `o`, which uses the `Octal` trait\n\ | |
534 | - `p`, which uses the `Pointer` trait\n\ | |
535 | - `b`, which uses the `Binary` trait\n\ | |
536 | - `x`, which uses the `LowerHex` trait\n\ | |
537 | - `X`, which uses the `UpperHex` trait", | |
538 | ); | |
539 | if let Some(sp) = sp { | |
540 | for (fmt, name) in &[ | |
541 | ("", "Display"), | |
542 | ("?", "Debug"), | |
543 | ("e", "LowerExp"), | |
544 | ("E", "UpperExp"), | |
545 | ("o", "Octal"), | |
546 | ("p", "Pointer"), | |
547 | ("b", "Binary"), | |
548 | ("x", "LowerHex"), | |
549 | ("X", "UpperHex"), | |
550 | ] { | |
551 | err.tool_only_span_suggestion( | |
552 | sp, | |
553 | &format!("use the `{}` trait", name), | |
554 | *fmt, | |
555 | Applicability::MaybeIncorrect, | |
556 | ); | |
557 | } | |
558 | } | |
559 | err.emit(); | |
560 | } | |
476ff2be | 561 | |
2b03887a FG |
562 | fn report_missing_placeholders( |
563 | ecx: &mut ExtCtxt<'_>, | |
564 | unused: Vec<(Span, &str)>, | |
565 | detect_foreign_fmt: bool, | |
566 | str_style: Option<usize>, | |
567 | fmt_str: &str, | |
568 | fmt_span: Span, | |
569 | ) { | |
570 | let mut diag = if let &[(span, msg)] = &unused[..] { | |
571 | let mut diag = ecx.struct_span_err(span, msg); | |
572 | diag.span_label(span, msg); | |
573 | diag | |
574 | } else { | |
575 | let mut diag = ecx.struct_span_err( | |
576 | unused.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(), | |
577 | "multiple unused formatting arguments", | |
578 | ); | |
579 | diag.span_label(fmt_span, "multiple missing formatting specifiers"); | |
580 | for &(span, msg) in &unused { | |
581 | diag.span_label(span, msg); | |
582 | } | |
583 | diag | |
584 | }; | |
476ff2be | 585 | |
2b03887a FG |
586 | // Used to ensure we only report translations for *one* kind of foreign format. |
587 | let mut found_foreign = false; | |
588 | ||
589 | // Decide if we want to look for foreign formatting directives. | |
590 | if detect_foreign_fmt { | |
591 | use super::format_foreign as foreign; | |
592 | ||
9c376795 | 593 | // The set of foreign substitutions we've explained. This prevents spamming the user |
2b03887a FG |
594 | // with `%d should be written as {}` over and over again. |
595 | let mut explained = FxHashSet::default(); | |
596 | ||
597 | macro_rules! check_foreign { | |
598 | ($kind:ident) => {{ | |
599 | let mut show_doc_note = false; | |
600 | ||
601 | let mut suggestions = vec![]; | |
602 | // account for `"` and account for raw strings `r#` | |
603 | let padding = str_style.map(|i| i + 2).unwrap_or(1); | |
604 | for sub in foreign::$kind::iter_subs(fmt_str, padding) { | |
605 | let (trn, success) = match sub.translate() { | |
606 | Ok(trn) => (trn, true), | |
607 | Err(Some(msg)) => (msg, false), | |
608 | ||
609 | // If it has no translation, don't call it out specifically. | |
610 | _ => continue, | |
611 | }; | |
612 | ||
613 | let pos = sub.position(); | |
614 | let sub = String::from(sub.as_str()); | |
615 | if explained.contains(&sub) { | |
616 | continue; | |
617 | } | |
618 | explained.insert(sub.clone()); | |
476ff2be | 619 | |
2b03887a FG |
620 | if !found_foreign { |
621 | found_foreign = true; | |
622 | show_doc_note = true; | |
623 | } | |
476ff2be | 624 | |
2b03887a FG |
625 | if let Some(inner_sp) = pos { |
626 | let sp = fmt_span.from_inner(inner_sp); | |
476ff2be | 627 | |
2b03887a FG |
628 | if success { |
629 | suggestions.push((sp, trn)); | |
630 | } else { | |
631 | diag.span_note( | |
632 | sp, | |
633 | &format!("format specifiers use curly braces, and {}", trn), | |
634 | ); | |
476ff2be | 635 | } |
2b03887a FG |
636 | } else { |
637 | if success { | |
638 | diag.help(&format!("`{}` should be written as `{}`", sub, trn)); | |
8faf50e0 | 639 | } else { |
2b03887a | 640 | diag.note(&format!("`{}` should use curly braces, and {}", sub, trn)); |
8faf50e0 | 641 | } |
476ff2be | 642 | } |
2b03887a | 643 | } |
476ff2be | 644 | |
2b03887a FG |
645 | if show_doc_note { |
646 | diag.note(concat!( | |
647 | stringify!($kind), | |
9c376795 | 648 | " formatting is not supported; see the documentation for `std::fmt`", |
2b03887a FG |
649 | )); |
650 | } | |
651 | if suggestions.len() > 0 { | |
652 | diag.multipart_suggestion( | |
653 | "format specifiers use curly braces", | |
654 | suggestions, | |
655 | Applicability::MachineApplicable, | |
656 | ); | |
657 | } | |
658 | }}; | |
8faf50e0 | 659 | } |
476ff2be | 660 | |
2b03887a FG |
661 | check_foreign!(printf); |
662 | if !found_foreign { | |
663 | check_foreign!(shell); | |
664 | } | |
665 | } | |
666 | if !found_foreign && unused.len() == 1 { | |
667 | diag.span_label(fmt_span, "formatting specifier missing"); | |
1a4d82fc JJ |
668 | } |
669 | ||
2b03887a | 670 | diag.emit(); |
1a4d82fc | 671 | } |
5099ac24 | 672 | |
2b03887a FG |
673 | /// Handle invalid references to positional arguments. Output different |
674 | /// errors for the case where all arguments are positional and for when | |
675 | /// there are named arguments or numbered positional arguments in the | |
676 | /// format string. | |
677 | fn report_invalid_references( | |
678 | ecx: &mut ExtCtxt<'_>, | |
679 | invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)], | |
680 | template: &[FormatArgsPiece], | |
681 | fmt_span: Span, | |
682 | args: &FormatArguments, | |
683 | parser: parse::Parser<'_>, | |
684 | ) { | |
685 | let num_args_desc = match args.explicit_args().len() { | |
686 | 0 => "no arguments were given".to_string(), | |
687 | 1 => "there is 1 argument".to_string(), | |
688 | n => format!("there are {} arguments", n), | |
689 | }; | |
690 | ||
691 | let mut e; | |
5099ac24 | 692 | |
2b03887a FG |
693 | if template.iter().all(|piece| match piece { |
694 | FormatArgsPiece::Placeholder(FormatPlaceholder { | |
695 | argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. }, | |
696 | .. | |
697 | }) => false, | |
698 | FormatArgsPiece::Placeholder(FormatPlaceholder { | |
699 | format_options: | |
700 | FormatOptions { | |
701 | precision: | |
702 | Some(FormatCount::Argument(FormatArgPosition { | |
703 | kind: FormatArgPositionKind::Number, | |
704 | .. | |
705 | })), | |
706 | .. | |
707 | } | |
708 | | FormatOptions { | |
709 | width: | |
710 | Some(FormatCount::Argument(FormatArgPosition { | |
711 | kind: FormatArgPositionKind::Number, | |
712 | .. | |
713 | })), | |
714 | .. | |
715 | }, | |
716 | .. | |
717 | }) => false, | |
718 | _ => true, | |
719 | }) { | |
720 | // There are no numeric positions. | |
721 | // Collect all the implicit positions: | |
722 | let mut spans = Vec::new(); | |
723 | let mut num_placeholders = 0; | |
724 | for piece in template { | |
725 | let mut placeholder = None; | |
726 | // `{arg:.*}` | |
727 | if let FormatArgsPiece::Placeholder(FormatPlaceholder { | |
728 | format_options: | |
729 | FormatOptions { | |
730 | precision: | |
731 | Some(FormatCount::Argument(FormatArgPosition { | |
732 | span, | |
733 | kind: FormatArgPositionKind::Implicit, | |
734 | .. | |
735 | })), | |
736 | .. | |
737 | }, | |
738 | .. | |
739 | }) = piece | |
740 | { | |
741 | placeholder = *span; | |
742 | num_placeholders += 1; | |
5099ac24 | 743 | } |
2b03887a FG |
744 | // `{}` |
745 | if let FormatArgsPiece::Placeholder(FormatPlaceholder { | |
746 | argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. }, | |
747 | span, | |
748 | .. | |
749 | }) = piece | |
750 | { | |
751 | placeholder = *span; | |
752 | num_placeholders += 1; | |
753 | } | |
754 | // For `{:.*}`, we only push one span. | |
755 | spans.extend(placeholder); | |
5099ac24 | 756 | } |
2b03887a FG |
757 | let span = if spans.is_empty() { |
758 | MultiSpan::from_span(fmt_span) | |
759 | } else { | |
760 | MultiSpan::from_spans(spans) | |
761 | }; | |
762 | e = ecx.struct_span_err( | |
763 | span, | |
764 | &format!( | |
765 | "{} positional argument{} in format string, but {}", | |
766 | num_placeholders, | |
767 | pluralize!(num_placeholders), | |
768 | num_args_desc, | |
769 | ), | |
770 | ); | |
771 | for arg in args.explicit_args() { | |
772 | e.span_label(arg.expr.span, ""); | |
773 | } | |
774 | // Point out `{:.*}` placeholders: those take an extra argument. | |
775 | let mut has_precision_star = false; | |
776 | for piece in template { | |
777 | if let FormatArgsPiece::Placeholder(FormatPlaceholder { | |
778 | format_options: | |
779 | FormatOptions { | |
780 | precision: | |
781 | Some(FormatCount::Argument(FormatArgPosition { | |
782 | index, | |
783 | span: Some(span), | |
784 | kind: FormatArgPositionKind::Implicit, | |
785 | .. | |
786 | })), | |
787 | .. | |
788 | }, | |
789 | .. | |
790 | }) = piece | |
791 | { | |
792 | let (Ok(index) | Err(index)) = index; | |
793 | has_precision_star = true; | |
794 | e.span_label( | |
795 | *span, | |
796 | &format!( | |
797 | "this precision flag adds an extra required argument at position {}, which is why there {} expected", | |
798 | index, | |
799 | if num_placeholders == 1 { | |
800 | "is 1 argument".to_string() | |
801 | } else { | |
802 | format!("are {} arguments", num_placeholders) | |
803 | }, | |
804 | ), | |
805 | ); | |
806 | } | |
807 | } | |
808 | if has_precision_star { | |
809 | e.note("positional arguments are zero-based"); | |
5099ac24 | 810 | } |
2b03887a FG |
811 | } else { |
812 | let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect(); | |
813 | // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` | |
814 | // for `println!("{7:7$}", 1);` | |
815 | indexes.sort(); | |
816 | indexes.dedup(); | |
817 | let span: MultiSpan = if !parser.is_literal || parser.arg_places.is_empty() { | |
818 | MultiSpan::from_span(fmt_span) | |
819 | } else { | |
820 | MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect()) | |
821 | }; | |
822 | let arg_list = if let &[index] = &indexes[..] { | |
823 | format!("argument {index}") | |
824 | } else { | |
825 | let tail = indexes.pop().unwrap(); | |
826 | format!( | |
827 | "arguments {head} and {tail}", | |
828 | head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ") | |
829 | ) | |
830 | }; | |
831 | e = ecx.struct_span_err( | |
832 | span, | |
833 | &format!("invalid reference to positional {} ({})", arg_list, num_args_desc), | |
834 | ); | |
835 | e.note("positional arguments are zero-based"); | |
836 | } | |
5099ac24 | 837 | |
2b03887a FG |
838 | if template.iter().any(|piece| match piece { |
839 | FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => { | |
840 | *f != FormatOptions::default() | |
5099ac24 | 841 | } |
2b03887a FG |
842 | _ => false, |
843 | }) { | |
844 | e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html"); | |
845 | } | |
846 | ||
847 | e.emit(); | |
848 | } | |
5099ac24 | 849 | |
2b03887a FG |
850 | fn expand_format_args_impl<'cx>( |
851 | ecx: &'cx mut ExtCtxt<'_>, | |
852 | mut sp: Span, | |
853 | tts: TokenStream, | |
854 | nl: bool, | |
855 | ) -> Box<dyn base::MacResult + 'cx> { | |
856 | sp = ecx.with_def_site_ctxt(sp); | |
857 | match parse_args(ecx, sp, tts) { | |
858 | Ok((efmt, args)) => { | |
859 | if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) { | |
9ffffee4 | 860 | MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args)))) |
2b03887a FG |
861 | } else { |
862 | MacEager::expr(DummyResult::raw_expr(sp, true)) | |
863 | } | |
864 | } | |
865 | Err(mut err) => { | |
866 | err.emit(); | |
867 | DummyResult::any(sp) | |
5099ac24 FG |
868 | } |
869 | } | |
2b03887a FG |
870 | } |
871 | ||
872 | pub fn expand_format_args<'cx>( | |
873 | ecx: &'cx mut ExtCtxt<'_>, | |
874 | sp: Span, | |
875 | tts: TokenStream, | |
876 | ) -> Box<dyn base::MacResult + 'cx> { | |
877 | expand_format_args_impl(ecx, sp, tts, false) | |
878 | } | |
5099ac24 | 879 | |
2b03887a FG |
880 | pub fn expand_format_args_nl<'cx>( |
881 | ecx: &'cx mut ExtCtxt<'_>, | |
882 | sp: Span, | |
883 | tts: TokenStream, | |
884 | ) -> Box<dyn base::MacResult + 'cx> { | |
885 | expand_format_args_impl(ecx, sp, tts, true) | |
5099ac24 | 886 | } |