]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2012 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | use self::ArgumentType::*; | |
12 | use self::Position::*; | |
13 | ||
1a4d82fc | 14 | use fmt_macros as parse; |
9cc50fc6 SL |
15 | |
16 | use syntax::ast; | |
b7449926 | 17 | use syntax::ext::base::{self, *}; |
9cc50fc6 | 18 | use syntax::ext::build::AstBuilder; |
8faf50e0 | 19 | use syntax::feature_gate; |
476ff2be | 20 | use syntax::parse::token; |
9cc50fc6 | 21 | use syntax::ptr::P; |
ea8adc8c | 22 | use syntax::symbol::Symbol; |
3157f602 | 23 | use syntax::tokenstream; |
8faf50e0 | 24 | use syntax_pos::{MultiSpan, Span, DUMMY_SP}; |
b7449926 | 25 | use errors::Applicability; |
1a4d82fc | 26 | |
b7449926 XL |
27 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
28 | use std::borrow::Cow; | |
5bcae85e | 29 | use std::collections::hash_map::Entry; |
1a4d82fc JJ |
30 | |
31 | #[derive(PartialEq)] | |
32 | enum ArgumentType { | |
5bcae85e SL |
33 | Placeholder(String), |
34 | Count, | |
1a4d82fc JJ |
35 | } |
36 | ||
37 | enum Position { | |
85aaf69f | 38 | Exact(usize), |
1a4d82fc JJ |
39 | Named(String), |
40 | } | |
41 | ||
9e0c209e | 42 | struct Context<'a, 'b: 'a> { |
1a4d82fc | 43 | ecx: &'a mut ExtCtxt<'b>, |
9346a6ac AL |
44 | /// The macro's call site. References to unstable formatting internals must |
45 | /// use this span to pass the stability checker. | |
46 | macsp: Span, | |
47 | /// The span of the format string literal. | |
1a4d82fc JJ |
48 | fmtsp: Span, |
49 | ||
5bcae85e SL |
50 | /// List of parsed argument expressions. |
51 | /// Named expressions are resolved early, and are appended to the end of | |
52 | /// argument expressions. | |
53 | /// | |
54 | /// Example showing the various data structures in motion: | |
55 | /// | |
56 | /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"` | |
57 | /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` | |
58 | /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` | |
59 | /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]` | |
60 | /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]` | |
61 | /// * `names` (in JSON): `{"foo": 2}` | |
1a4d82fc | 62 | args: Vec<P<ast::Expr>>, |
5bcae85e SL |
63 | /// Placeholder slot numbers indexed by argument. |
64 | arg_types: Vec<Vec<usize>>, | |
65 | /// Unique format specs seen for each argument. | |
66 | arg_unique_types: Vec<Vec<ArgumentType>>, | |
67 | /// Map from named arguments to their resolved indices. | |
b7449926 | 68 | names: FxHashMap<String, usize>, |
1a4d82fc JJ |
69 | |
70 | /// The latest consecutive literal strings, or empty if there weren't any. | |
71 | literal: String, | |
72 | ||
73 | /// Collection of the compiled `rt::Argument` structures | |
74 | pieces: Vec<P<ast::Expr>>, | |
75 | /// Collection of string literals | |
76 | str_pieces: Vec<P<ast::Expr>>, | |
77 | /// Stays `true` if all formatting parameters are default (as in "{}{}"). | |
78 | all_pieces_simple: bool, | |
79 | ||
5bcae85e SL |
80 | /// Mapping between positional argument references and indices into the |
81 | /// final generated static argument array. We record the starting indices | |
82 | /// corresponding to each positional argument, and number of references | |
83 | /// consumed so far for each argument, to facilitate correct `Position` | |
94b46f34 | 84 | /// mapping in `build_piece`. In effect this can be seen as a "flattened" |
5bcae85e SL |
85 | /// version of `arg_unique_types`. |
86 | /// | |
87 | /// Again with the example described above in docstring for `args`: | |
88 | /// | |
89 | /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]` | |
90 | arg_index_map: Vec<Vec<usize>>, | |
91 | ||
92 | /// Starting offset of count argument slots. | |
93 | count_args_index_offset: usize, | |
1a4d82fc | 94 | |
5bcae85e SL |
95 | /// Count argument slots and tracking data structures. |
96 | /// Count arguments are separately tracked for de-duplication in case | |
97 | /// multiple references are made to one argument. For example, in this | |
98 | /// format string: | |
99 | /// | |
100 | /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"` | |
101 | /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"` | |
102 | /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"` | |
103 | /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}` | |
104 | /// * `count_args`: `vec![Exact(0), Exact(5), Exact(3)]` | |
105 | count_args: Vec<Position>, | |
106 | /// Relative slot numbers for count arguments. | |
b7449926 | 107 | count_positions: FxHashMap<usize, usize>, |
5bcae85e SL |
108 | /// Number of count slots assigned. |
109 | count_positions_count: usize, | |
110 | ||
111 | /// Current position of the implicit positional arg pointer, as if it | |
112 | /// still existed in this phase of processing. | |
94b46f34 | 113 | /// Used only for `all_pieces_simple` tracking in `build_piece`. |
5bcae85e | 114 | curarg: usize, |
8faf50e0 XL |
115 | /// Current piece being evaluated, used for error reporting. |
116 | curpiece: usize, | |
117 | /// Keep track of invalid references to positional arguments. | |
118 | invalid_refs: Vec<(usize, usize)>, | |
119 | /// Spans of all the formatting arguments, in order. | |
120 | arg_spans: Vec<Span>, | |
b7449926 | 121 | /// Whether this formatting string is a literal or it comes from a macro. |
8faf50e0 | 122 | is_literal: bool, |
1a4d82fc JJ |
123 | } |
124 | ||
125 | /// Parses the arguments from the given list of tokens, returning None | |
126 | /// if there's a parse error so we can continue parsing other format! | |
127 | /// expressions. | |
128 | /// | |
129 | /// If parsing succeeds, the return value is: | |
041b39d2 XL |
130 | /// |
131 | /// ```text | |
5bcae85e | 132 | /// Some((fmtstr, parsed arguments, index map for named arguments)) |
92a42be0 | 133 | /// ``` |
9e0c209e SL |
134 | fn parse_args(ecx: &mut ExtCtxt, |
135 | sp: Span, | |
136 | tts: &[tokenstream::TokenTree]) | |
b7449926 | 137 | -> Option<(P<ast::Expr>, Vec<P<ast::Expr>>, FxHashMap<String, usize>)> { |
5bcae85e | 138 | let mut args = Vec::<P<ast::Expr>>::new(); |
b7449926 | 139 | let mut names = FxHashMap::<String, usize>::default(); |
1a4d82fc JJ |
140 | |
141 | let mut p = ecx.new_parser_from_tts(tts); | |
142 | ||
143 | if p.token == token::Eof { | |
144 | ecx.span_err(sp, "requires at least a format string argument"); | |
145 | return None; | |
146 | } | |
b7449926 | 147 | |
92a42be0 | 148 | let fmtstr = panictry!(p.parse_expr()); |
1a4d82fc | 149 | let mut named = false; |
b7449926 | 150 | |
1a4d82fc | 151 | while p.token != token::Eof { |
9cc50fc6 | 152 | if !p.eat(&token::Comma) { |
b7449926 | 153 | ecx.span_err(p.span, "expected token: `,`"); |
1a4d82fc JJ |
154 | return None; |
155 | } | |
9e0c209e SL |
156 | if p.token == token::Eof { |
157 | break; | |
158 | } // accept trailing commas | |
1a4d82fc JJ |
159 | if named || (p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq)) { |
160 | named = true; | |
161 | let ident = match p.token { | |
0531ce1d | 162 | token::Ident(i, _) => { |
9cc50fc6 | 163 | p.bump(); |
1a4d82fc JJ |
164 | i |
165 | } | |
166 | _ if named => { | |
8faf50e0 XL |
167 | ecx.span_err( |
168 | p.span, | |
169 | "expected ident, positional arguments cannot follow named arguments", | |
170 | ); | |
1a4d82fc JJ |
171 | return None; |
172 | } | |
173 | _ => { | |
8faf50e0 XL |
174 | ecx.span_err( |
175 | p.span, | |
176 | &format!( | |
177 | "expected ident for named argument, found `{}`", | |
178 | p.this_token_to_string() | |
179 | ), | |
180 | ); | |
1a4d82fc JJ |
181 | return None; |
182 | } | |
183 | }; | |
94b46f34 | 184 | let name: &str = &ident.as_str(); |
85aaf69f | 185 | |
9346a6ac | 186 | panictry!(p.expect(&token::Eq)); |
92a42be0 | 187 | let e = panictry!(p.parse_expr()); |
3157f602 | 188 | if let Some(prev) = names.get(name) { |
9e0c209e | 189 | ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", name)) |
5bcae85e | 190 | .span_note(args[*prev].span, "previously here") |
3157f602 XL |
191 | .emit(); |
192 | continue; | |
1a4d82fc | 193 | } |
5bcae85e SL |
194 | |
195 | // Resolve names into slots early. | |
196 | // Since all the positional args are already seen at this point | |
197 | // if the input is valid, we can simply append to the positional | |
198 | // args. And remember the names. | |
199 | let slot = args.len(); | |
200 | names.insert(name.to_string(), slot); | |
201 | args.push(e); | |
1a4d82fc | 202 | } else { |
92a42be0 | 203 | args.push(panictry!(p.parse_expr())); |
1a4d82fc JJ |
204 | } |
205 | } | |
5bcae85e | 206 | Some((fmtstr, args, names)) |
1a4d82fc JJ |
207 | } |
208 | ||
209 | impl<'a, 'b> Context<'a, 'b> { | |
5bcae85e SL |
210 | fn resolve_name_inplace(&self, p: &mut parse::Piece) { |
211 | // NOTE: the `unwrap_or` branch is needed in case of invalid format | |
212 | // arguments, e.g. `format_args!("{foo}")`. | |
213 | let lookup = |s| *self.names.get(s).unwrap_or(&0); | |
214 | ||
215 | match *p { | |
216 | parse::String(_) => {} | |
217 | parse::NextArgument(ref mut arg) => { | |
218 | if let parse::ArgumentNamed(s) = arg.position { | |
219 | arg.position = parse::ArgumentIs(lookup(s)); | |
220 | } | |
221 | if let parse::CountIsName(s) = arg.format.width { | |
222 | arg.format.width = parse::CountIsParam(lookup(s)); | |
223 | } | |
224 | if let parse::CountIsName(s) = arg.format.precision { | |
225 | arg.format.precision = parse::CountIsParam(lookup(s)); | |
226 | } | |
227 | } | |
228 | } | |
229 | } | |
230 | ||
231 | /// Verifies one piece of a parse string, and remembers it if valid. | |
232 | /// All errors are not emitted as fatal so we can continue giving errors | |
233 | /// about this and possibly other format strings. | |
1a4d82fc JJ |
234 | fn verify_piece(&mut self, p: &parse::Piece) { |
235 | match *p { | |
236 | parse::String(..) => {} | |
237 | parse::NextArgument(ref arg) => { | |
238 | // width/precision first, if they have implicit positional | |
239 | // parameters it makes more sense to consume them first. | |
240 | self.verify_count(arg.format.width); | |
241 | self.verify_count(arg.format.precision); | |
242 | ||
243 | // argument second, if it's an implicit positional parameter | |
244 | // it's written second, so it should come after width/precision. | |
245 | let pos = match arg.position { | |
abe05a73 | 246 | parse::ArgumentIs(i) | parse::ArgumentImplicitlyIs(i) => Exact(i), |
1a4d82fc JJ |
247 | parse::ArgumentNamed(s) => Named(s.to_string()), |
248 | }; | |
249 | ||
5bcae85e | 250 | let ty = Placeholder(arg.format.ty.to_string()); |
1a4d82fc | 251 | self.verify_arg_type(pos, ty); |
8faf50e0 | 252 | self.curpiece += 1; |
1a4d82fc JJ |
253 | } |
254 | } | |
255 | } | |
256 | ||
257 | fn verify_count(&mut self, c: parse::Count) { | |
258 | match c { | |
9e0c209e SL |
259 | parse::CountImplied | |
260 | parse::CountIs(..) => {} | |
1a4d82fc | 261 | parse::CountIsParam(i) => { |
5bcae85e | 262 | self.verify_arg_type(Exact(i), Count); |
1a4d82fc JJ |
263 | } |
264 | parse::CountIsName(s) => { | |
5bcae85e | 265 | self.verify_arg_type(Named(s.to_string()), Count); |
1a4d82fc JJ |
266 | } |
267 | } | |
268 | } | |
269 | ||
b7449926 | 270 | fn describe_num_args(&self) -> Cow<str> { |
1a4d82fc | 271 | match self.args.len() { |
b7449926 XL |
272 | 0 => "no arguments were given".into(), |
273 | 1 => "there is 1 argument".into(), | |
274 | x => format!("there are {} arguments", x).into(), | |
1a4d82fc JJ |
275 | } |
276 | } | |
277 | ||
abe05a73 XL |
278 | /// Handle invalid references to positional arguments. Output different |
279 | /// errors for the case where all arguments are positional and for when | |
280 | /// there are named arguments or numbered positional arguments in the | |
281 | /// format string. | |
282 | fn report_invalid_references(&self, numbered_position_args: bool) { | |
283 | let mut e; | |
8faf50e0 XL |
284 | let sp = if self.is_literal { |
285 | MultiSpan::from_spans(self.arg_spans.clone()) | |
286 | } else { | |
287 | MultiSpan::from_span(self.fmtsp) | |
288 | }; | |
289 | let mut refs: Vec<_> = self | |
290 | .invalid_refs | |
291 | .iter() | |
292 | .map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos))) | |
293 | .collect(); | |
abe05a73 XL |
294 | |
295 | if self.names.is_empty() && !numbered_position_args { | |
8faf50e0 XL |
296 | e = self.ecx.mut_span_err( |
297 | sp, | |
298 | &format!( | |
299 | "{} positional argument{} in format string, but {}", | |
abe05a73 XL |
300 | self.pieces.len(), |
301 | if self.pieces.len() > 1 { "s" } else { "" }, | |
8faf50e0 XL |
302 | self.describe_num_args() |
303 | ), | |
304 | ); | |
abe05a73 | 305 | } else { |
8faf50e0 XL |
306 | let (arg_list, mut sp) = match refs.len() { |
307 | 1 => { | |
308 | let (reg, pos) = refs.pop().unwrap(); | |
309 | ( | |
310 | format!("argument {}", reg), | |
311 | MultiSpan::from_span(*pos.unwrap_or(&self.fmtsp)), | |
312 | ) | |
313 | } | |
314 | _ => { | |
315 | let pos = | |
316 | MultiSpan::from_spans(refs.iter().map(|(_, p)| *p.unwrap()).collect()); | |
317 | let mut refs: Vec<String> = refs.iter().map(|(s, _)| s.to_owned()).collect(); | |
318 | let reg = refs.pop().unwrap(); | |
319 | ( | |
320 | format!( | |
321 | "arguments {head} and {tail}", | |
322 | tail = reg, | |
323 | head = refs.join(", ") | |
324 | ), | |
325 | pos, | |
326 | ) | |
327 | } | |
abe05a73 | 328 | }; |
8faf50e0 XL |
329 | if !self.is_literal { |
330 | sp = MultiSpan::from_span(self.fmtsp); | |
331 | } | |
abe05a73 | 332 | |
8faf50e0 | 333 | e = self.ecx.mut_span_err(sp, |
abe05a73 | 334 | &format!("invalid reference to positional {} ({})", |
8faf50e0 XL |
335 | arg_list, |
336 | self.describe_num_args())); | |
abe05a73 XL |
337 | e.note("positional arguments are zero-based"); |
338 | }; | |
339 | ||
340 | e.emit(); | |
341 | } | |
342 | ||
5bcae85e SL |
343 | /// Actually verifies and tracks a given format placeholder |
344 | /// (a.k.a. argument). | |
1a4d82fc JJ |
345 | fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) { |
346 | match arg { | |
347 | Exact(arg) => { | |
348 | if self.args.len() <= arg { | |
8faf50e0 | 349 | self.invalid_refs.push((arg, self.curpiece)); |
1a4d82fc JJ |
350 | return; |
351 | } | |
5bcae85e SL |
352 | match ty { |
353 | Placeholder(_) => { | |
354 | // record every (position, type) combination only once | |
355 | let ref mut seen_ty = self.arg_unique_types[arg]; | |
356 | let i = match seen_ty.iter().position(|x| *x == ty) { | |
357 | Some(i) => i, | |
358 | None => { | |
359 | let i = seen_ty.len(); | |
360 | seen_ty.push(ty); | |
361 | i | |
362 | } | |
363 | }; | |
364 | self.arg_types[arg].push(i); | |
365 | } | |
366 | Count => { | |
367 | match self.count_positions.entry(arg) { | |
368 | Entry::Vacant(e) => { | |
369 | let i = self.count_positions_count; | |
370 | e.insert(i); | |
371 | self.count_args.push(Exact(arg)); | |
372 | self.count_positions_count += 1; | |
373 | } | |
374 | Entry::Occupied(_) => {} | |
375 | } | |
376 | } | |
1a4d82fc JJ |
377 | } |
378 | } | |
379 | ||
380 | Named(name) => { | |
5bcae85e SL |
381 | let idx = match self.names.get(&name) { |
382 | Some(e) => *e, | |
1a4d82fc JJ |
383 | None => { |
384 | let msg = format!("there is no argument named `{}`", name); | |
8faf50e0 XL |
385 | let sp = if self.is_literal { |
386 | *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp) | |
387 | } else { | |
388 | self.fmtsp | |
389 | }; | |
390 | let mut err = self.ecx.struct_span_err(sp, &msg[..]); | |
391 | err.emit(); | |
1a4d82fc JJ |
392 | return; |
393 | } | |
394 | }; | |
5bcae85e SL |
395 | // Treat as positional arg. |
396 | self.verify_arg_type(Exact(idx), ty) | |
1a4d82fc JJ |
397 | } |
398 | } | |
399 | } | |
400 | ||
5bcae85e SL |
401 | /// Builds the mapping between format placeholders and argument objects. |
402 | fn build_index_map(&mut self) { | |
403 | // NOTE: Keep the ordering the same as `into_expr`'s expansion would do! | |
404 | let args_len = self.args.len(); | |
405 | self.arg_index_map.reserve(args_len); | |
406 | ||
407 | let mut sofar = 0usize; | |
408 | ||
409 | // Map the arguments | |
410 | for i in 0..args_len { | |
411 | let ref arg_types = self.arg_types[i]; | |
8faf50e0 | 412 | let arg_offsets = arg_types.iter().map(|offset| sofar + *offset).collect::<Vec<_>>(); |
5bcae85e SL |
413 | self.arg_index_map.push(arg_offsets); |
414 | sofar += self.arg_unique_types[i].len(); | |
1a4d82fc | 415 | } |
5bcae85e SL |
416 | |
417 | // Record starting index for counts, which appear just after arguments | |
418 | self.count_args_index_offset = sofar; | |
1a4d82fc JJ |
419 | } |
420 | ||
1a4d82fc | 421 | fn rtpath(ecx: &ExtCtxt, s: &str) -> Vec<ast::Ident> { |
e9174d1e | 422 | ecx.std_path(&["fmt", "rt", "v1", s]) |
1a4d82fc JJ |
423 | } |
424 | ||
94b46f34 | 425 | fn build_count(&self, c: parse::Count) -> P<ast::Expr> { |
9346a6ac | 426 | let sp = self.macsp; |
85aaf69f SL |
427 | let count = |c, arg| { |
428 | let mut path = Context::rtpath(self.ecx, "Count"); | |
429 | path.push(self.ecx.ident_of(c)); | |
430 | match arg { | |
431 | Some(arg) => self.ecx.expr_call_global(sp, path, vec![arg]), | |
432 | None => self.ecx.expr_path(self.ecx.path_global(sp, path)), | |
1a4d82fc | 433 | } |
85aaf69f SL |
434 | }; |
435 | match c { | |
436 | parse::CountIs(i) => count("Is", Some(self.ecx.expr_usize(sp, i))), | |
1a4d82fc | 437 | parse::CountIsParam(i) => { |
5bcae85e SL |
438 | // This needs mapping too, as `i` is referring to a macro |
439 | // argument. | |
440 | let i = match self.count_positions.get(&i) { | |
1a4d82fc JJ |
441 | Some(&i) => i, |
442 | None => 0, // error already emitted elsewhere | |
443 | }; | |
5bcae85e | 444 | let i = i + self.count_args_index_offset; |
85aaf69f | 445 | count("Param", Some(self.ecx.expr_usize(sp, i))) |
1a4d82fc | 446 | } |
5bcae85e SL |
447 | parse::CountImplied => count("Implied", None), |
448 | // should never be the case, names are already resolved | |
449 | parse::CountIsName(_) => panic!("should never happen"), | |
1a4d82fc JJ |
450 | } |
451 | } | |
452 | ||
94b46f34 XL |
453 | /// Build a literal expression from the accumulated string literals |
454 | fn build_literal_string(&mut self) -> P<ast::Expr> { | |
1a4d82fc | 455 | let sp = self.fmtsp; |
476ff2be | 456 | let s = Symbol::intern(&self.literal); |
1a4d82fc JJ |
457 | self.literal.clear(); |
458 | self.ecx.expr_str(sp, s) | |
459 | } | |
460 | ||
94b46f34 | 461 | /// Build a static `rt::Argument` from a `parse::Piece` or append |
1a4d82fc | 462 | /// to the `literal` string. |
94b46f34 | 463 | fn build_piece(&mut self, |
5bcae85e SL |
464 | piece: &parse::Piece, |
465 | arg_index_consumed: &mut Vec<usize>) | |
466 | -> Option<P<ast::Expr>> { | |
9346a6ac | 467 | let sp = self.macsp; |
1a4d82fc JJ |
468 | match *piece { |
469 | parse::String(s) => { | |
470 | self.literal.push_str(s); | |
471 | None | |
472 | } | |
473 | parse::NextArgument(ref arg) => { | |
94b46f34 | 474 | // Build the position |
85aaf69f SL |
475 | let pos = { |
476 | let pos = |c, arg| { | |
477 | let mut path = Context::rtpath(self.ecx, "Position"); | |
478 | path.push(self.ecx.ident_of(c)); | |
479 | match arg { | |
480 | Some(i) => { | |
481 | let arg = self.ecx.expr_usize(sp, i); | |
482 | self.ecx.expr_call_global(sp, path, vec![arg]) | |
483 | } | |
9e0c209e | 484 | None => self.ecx.expr_path(self.ecx.path_global(sp, path)), |
85aaf69f SL |
485 | } |
486 | }; | |
487 | match arg.position { | |
abe05a73 XL |
488 | parse::ArgumentIs(i) |
489 | | parse::ArgumentImplicitlyIs(i) => { | |
5bcae85e SL |
490 | // Map to index in final generated argument array |
491 | // in case of multiple types specified | |
492 | let arg_idx = match arg_index_consumed.get_mut(i) { | |
85aaf69f | 493 | None => 0, // error already emitted elsewhere |
5bcae85e SL |
494 | Some(offset) => { |
495 | let ref idx_map = self.arg_index_map[i]; | |
496 | // unwrap_or branch: error already emitted elsewhere | |
497 | let arg_idx = *idx_map.get(*offset).unwrap_or(&0); | |
498 | *offset += 1; | |
499 | arg_idx | |
500 | } | |
85aaf69f | 501 | }; |
5bcae85e | 502 | pos("At", Some(arg_idx)) |
85aaf69f | 503 | } |
5bcae85e SL |
504 | |
505 | // should never be the case, because names are already | |
506 | // resolved. | |
507 | parse::ArgumentNamed(_) => panic!("should never happen"), | |
1a4d82fc JJ |
508 | } |
509 | }; | |
510 | ||
511 | let simple_arg = parse::Argument { | |
5bcae85e SL |
512 | position: { |
513 | // We don't have ArgumentNext any more, so we have to | |
514 | // track the current argument ourselves. | |
515 | let i = self.curarg; | |
516 | self.curarg += 1; | |
517 | parse::ArgumentIs(i) | |
518 | }, | |
1a4d82fc JJ |
519 | format: parse::FormatSpec { |
520 | fill: arg.format.fill, | |
521 | align: parse::AlignUnknown, | |
522 | flags: 0, | |
523 | precision: parse::CountImplied, | |
524 | width: parse::CountImplied, | |
9e0c209e SL |
525 | ty: arg.format.ty, |
526 | }, | |
1a4d82fc JJ |
527 | }; |
528 | ||
9e0c209e SL |
529 | let fill = match arg.format.fill { |
530 | Some(c) => c, | |
531 | None => ' ', | |
532 | }; | |
1a4d82fc JJ |
533 | |
534 | if *arg != simple_arg || fill != ' ' { | |
535 | self.all_pieces_simple = false; | |
536 | } | |
537 | ||
94b46f34 | 538 | // Build the format |
7453a54e | 539 | let fill = self.ecx.expr_lit(sp, ast::LitKind::Char(fill)); |
85aaf69f SL |
540 | let align = |name| { |
541 | let mut p = Context::rtpath(self.ecx, "Alignment"); | |
542 | p.push(self.ecx.ident_of(name)); | |
543 | self.ecx.path_global(sp, p) | |
544 | }; | |
1a4d82fc | 545 | let align = match arg.format.align { |
85aaf69f SL |
546 | parse::AlignLeft => align("Left"), |
547 | parse::AlignRight => align("Right"), | |
548 | parse::AlignCenter => align("Center"), | |
549 | parse::AlignUnknown => align("Unknown"), | |
1a4d82fc JJ |
550 | }; |
551 | let align = self.ecx.expr_path(align); | |
c34b1796 | 552 | let flags = self.ecx.expr_u32(sp, arg.format.flags); |
94b46f34 XL |
553 | let prec = self.build_count(arg.format.precision); |
554 | let width = self.build_count(arg.format.width); | |
1a4d82fc | 555 | let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "FormatSpec")); |
8faf50e0 XL |
556 | let fmt = self.ecx.expr_struct( |
557 | sp, | |
9e0c209e | 558 | path, |
8faf50e0 XL |
559 | vec![ |
560 | self.ecx.field_imm(sp, self.ecx.ident_of("fill"), fill), | |
561 | self.ecx.field_imm(sp, self.ecx.ident_of("align"), align), | |
562 | self.ecx.field_imm(sp, self.ecx.ident_of("flags"), flags), | |
563 | self.ecx.field_imm(sp, self.ecx.ident_of("precision"), prec), | |
564 | self.ecx.field_imm(sp, self.ecx.ident_of("width"), width), | |
565 | ], | |
566 | ); | |
1a4d82fc JJ |
567 | |
568 | let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "Argument")); | |
8faf50e0 XL |
569 | Some(self.ecx.expr_struct( |
570 | sp, | |
9e0c209e | 571 | path, |
8faf50e0 XL |
572 | vec![ |
573 | self.ecx.field_imm(sp, self.ecx.ident_of("position"), pos), | |
574 | self.ecx.field_imm(sp, self.ecx.ident_of("format"), fmt), | |
575 | ], | |
576 | )) | |
1a4d82fc JJ |
577 | } |
578 | } | |
579 | } | |
580 | ||
5bcae85e SL |
581 | /// Actually builds the expression which the format_args! block will be |
582 | /// expanded to | |
3b2f2976 | 583 | fn into_expr(self) -> P<ast::Expr> { |
8faf50e0 XL |
584 | let mut locals = Vec::with_capacity( |
585 | (0..self.args.len()).map(|i| self.arg_unique_types[i].len()).sum() | |
586 | ); | |
587 | let mut counts = Vec::with_capacity(self.count_args.len()); | |
588 | let mut pats = Vec::with_capacity(self.args.len()); | |
589 | let mut heads = Vec::with_capacity(self.args.len()); | |
1a4d82fc | 590 | |
8faf50e0 XL |
591 | let names_pos: Vec<_> = (0..self.args.len()) |
592 | .map(|i| self.ecx.ident_of(&format!("arg{}", i)).gensym()) | |
593 | .collect(); | |
83c7162d | 594 | |
1a4d82fc JJ |
595 | // First, build up the static array which will become our precompiled |
596 | // format "string" | |
ea8adc8c | 597 | let pieces = self.ecx.expr_vec_slice(self.fmtsp, self.str_pieces); |
1a4d82fc | 598 | |
5bcae85e SL |
599 | // Before consuming the expressions, we have to remember spans for |
600 | // count arguments as they are now generated separate from other | |
601 | // arguments, hence have no access to the `P<ast::Expr>`'s. | |
602 | let spans_pos: Vec<_> = self.args.iter().map(|e| e.span.clone()).collect(); | |
1a4d82fc JJ |
603 | |
604 | // Right now there is a bug such that for the expression: | |
605 | // foo(bar(&1)) | |
606 | // the lifetime of `1` doesn't outlast the call to `bar`, so it's not | |
607 | // valid for the call to `foo`. To work around this all arguments to the | |
608 | // format! string are shoved into locals. Furthermore, we shove the address | |
609 | // of each variable because we don't want to move out of the arguments | |
610 | // passed to this function. | |
611 | for (i, e) in self.args.into_iter().enumerate() { | |
83c7162d | 612 | let name = names_pos[i]; |
ea8adc8c XL |
613 | let span = |
614 | DUMMY_SP.with_ctxt(e.span.ctxt().apply_mark(self.ecx.current_expansion.mark)); | |
041b39d2 | 615 | pats.push(self.ecx.pat_ident(span, name)); |
5bcae85e | 616 | for ref arg_ty in self.arg_unique_types[i].iter() { |
cc61c64b | 617 | locals.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty, name)); |
5bcae85e | 618 | } |
1a4d82fc JJ |
619 | heads.push(self.ecx.expr_addr_of(e.span, e)); |
620 | } | |
5bcae85e | 621 | for pos in self.count_args { |
83c7162d XL |
622 | let index = match pos { |
623 | Exact(i) => i, | |
5bcae85e | 624 | _ => panic!("should never happen"), |
1a4d82fc | 625 | }; |
83c7162d XL |
626 | let name = names_pos[index]; |
627 | let span = spans_pos[index]; | |
cc61c64b | 628 | counts.push(Context::format_arg(self.ecx, self.macsp, span, &Count, name)); |
1a4d82fc JJ |
629 | } |
630 | ||
631 | // Now create a vector containing all the arguments | |
5bcae85e | 632 | let args = locals.into_iter().chain(counts.into_iter()); |
1a4d82fc JJ |
633 | |
634 | let args_array = self.ecx.expr_vec(self.fmtsp, args.collect()); | |
635 | ||
636 | // Constructs an AST equivalent to: | |
637 | // | |
638 | // match (&arg0, &arg1) { | |
639 | // (tmp0, tmp1) => args_array | |
640 | // } | |
641 | // | |
642 | // It was: | |
643 | // | |
644 | // let tmp0 = &arg0; | |
645 | // let tmp1 = &arg1; | |
646 | // args_array | |
647 | // | |
648 | // Because of #11585 the new temporary lifetime rule, the enclosing | |
649 | // statements for these temporaries become the let's themselves. | |
650 | // If one or more of them are RefCell's, RefCell borrow() will also | |
651 | // end there; they don't last long enough for args_array to use them. | |
652 | // The match expression solves the scope problem. | |
653 | // | |
654 | // Note, it may also very well be transformed to: | |
655 | // | |
656 | // match arg0 { | |
657 | // ref tmp0 => { | |
658 | // match arg1 => { | |
659 | // ref tmp1 => args_array } } } | |
660 | // | |
661 | // But the nested match expression is proved to perform not as well | |
662 | // as series of let's; the first approach does. | |
663 | let pat = self.ecx.pat_tuple(self.fmtsp, pats); | |
9e0c209e | 664 | let arm = self.ecx.arm(self.fmtsp, vec![pat], args_array); |
7453a54e | 665 | let head = self.ecx.expr(self.fmtsp, ast::ExprKind::Tup(heads)); |
9e0c209e | 666 | let result = self.ecx.expr_match(self.fmtsp, head, vec![arm]); |
1a4d82fc JJ |
667 | |
668 | let args_slice = self.ecx.expr_addr_of(self.fmtsp, result); | |
669 | ||
670 | // Now create the fmt::Arguments struct with all our locals we created. | |
671 | let (fn_name, fn_args) = if self.all_pieces_simple { | |
85aaf69f | 672 | ("new_v1", vec![pieces, args_slice]) |
1a4d82fc JJ |
673 | } else { |
674 | // Build up the static array which will store our precompiled | |
675 | // nonstandard placeholders, if there are any. | |
ea8adc8c | 676 | let fmt = self.ecx.expr_vec_slice(self.macsp, self.pieces); |
1a4d82fc | 677 | |
85aaf69f | 678 | ("new_v1_formatted", vec![pieces, args_slice, fmt]) |
1a4d82fc JJ |
679 | }; |
680 | ||
e9174d1e SL |
681 | let path = self.ecx.std_path(&["fmt", "Arguments", fn_name]); |
682 | self.ecx.expr_call_global(self.macsp, path, fn_args) | |
1a4d82fc JJ |
683 | } |
684 | ||
9e0c209e SL |
685 | fn format_arg(ecx: &ExtCtxt, |
686 | macsp: Span, | |
cc61c64b | 687 | mut sp: Span, |
9e0c209e | 688 | ty: &ArgumentType, |
cc61c64b | 689 | arg: ast::Ident) |
1a4d82fc | 690 | -> P<ast::Expr> { |
83c7162d | 691 | sp = sp.apply_mark(ecx.current_expansion.mark); |
cc61c64b | 692 | let arg = ecx.expr_ident(sp, arg); |
1a4d82fc | 693 | let trait_ = match *ty { |
5bcae85e | 694 | Placeholder(ref tyname) => { |
85aaf69f | 695 | match &tyname[..] { |
9e0c209e | 696 | "" => "Display", |
85aaf69f | 697 | "?" => "Debug", |
1a4d82fc JJ |
698 | "e" => "LowerExp", |
699 | "E" => "UpperExp", | |
700 | "o" => "Octal", | |
701 | "p" => "Pointer", | |
702 | "b" => "Binary", | |
703 | "x" => "LowerHex", | |
704 | "X" => "UpperHex", | |
705 | _ => { | |
9e0c209e | 706 | ecx.span_err(sp, &format!("unknown format trait `{}`", *tyname)); |
1a4d82fc JJ |
707 | "Dummy" |
708 | } | |
709 | } | |
710 | } | |
5bcae85e | 711 | Count => { |
e9174d1e | 712 | let path = ecx.std_path(&["fmt", "ArgumentV1", "from_usize"]); |
9e0c209e | 713 | return ecx.expr_call_global(macsp, path, vec![arg]); |
1a4d82fc JJ |
714 | } |
715 | }; | |
716 | ||
e9174d1e SL |
717 | let path = ecx.std_path(&["fmt", trait_, "fmt"]); |
718 | let format_fn = ecx.path_global(sp, path); | |
719 | let path = ecx.std_path(&["fmt", "ArgumentV1", "new"]); | |
720 | ecx.expr_call_global(macsp, path, vec![arg, ecx.expr_path(format_fn)]) | |
1a4d82fc JJ |
721 | } |
722 | } | |
723 | ||
9e0c209e | 724 | pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt, |
041b39d2 | 725 | mut sp: Span, |
3157f602 | 726 | tts: &[tokenstream::TokenTree]) |
8faf50e0 XL |
727 | -> Box<dyn base::MacResult + 'cx> { |
728 | sp = sp.apply_mark(ecx.current_expansion.mark); | |
729 | match parse_args(ecx, sp, tts) { | |
730 | Some((efmt, args, names)) => { | |
731 | MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, false)) | |
732 | } | |
733 | None => DummyResult::expr(sp), | |
734 | } | |
735 | } | |
736 | ||
737 | pub fn expand_format_args_nl<'cx>( | |
738 | ecx: &'cx mut ExtCtxt, | |
739 | mut sp: Span, | |
740 | tts: &[tokenstream::TokenTree], | |
741 | ) -> Box<dyn base::MacResult + 'cx> { | |
742 | //if !ecx.ecfg.enable_allow_internal_unstable() { | |
743 | ||
744 | // For some reason, the only one that actually works for `println` is the first check | |
745 | if !sp.allows_unstable() // the enclosing span is marked as `#[allow_insternal_unsable]` | |
746 | && !ecx.ecfg.enable_allow_internal_unstable() // NOTE: when is this enabled? | |
747 | && !ecx.ecfg.enable_format_args_nl() // enabled using `#[feature(format_args_nl]` | |
748 | { | |
749 | feature_gate::emit_feature_err(&ecx.parse_sess, | |
750 | "format_args_nl", | |
751 | sp, | |
752 | feature_gate::GateIssue::Language, | |
753 | feature_gate::EXPLAIN_FORMAT_ARGS_NL); | |
754 | return base::DummyResult::expr(sp); | |
755 | } | |
83c7162d | 756 | sp = sp.apply_mark(ecx.current_expansion.mark); |
1a4d82fc | 757 | match parse_args(ecx, sp, tts) { |
5bcae85e | 758 | Some((efmt, args, names)) => { |
8faf50e0 | 759 | MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, true)) |
1a4d82fc | 760 | } |
9e0c209e | 761 | None => DummyResult::expr(sp), |
1a4d82fc JJ |
762 | } |
763 | } | |
764 | ||
765 | /// Take the various parts of `format_args!(efmt, args..., name=names...)` | |
766 | /// and construct the appropriate formatting expression. | |
9e0c209e SL |
767 | pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, |
768 | sp: Span, | |
1a4d82fc JJ |
769 | efmt: P<ast::Expr>, |
770 | args: Vec<P<ast::Expr>>, | |
b7449926 | 771 | names: FxHashMap<String, usize>, |
8faf50e0 | 772 | append_newline: bool) |
1a4d82fc | 773 | -> P<ast::Expr> { |
5bcae85e SL |
774 | // NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because |
775 | // `ArgumentType` does not derive `Clone`. | |
776 | let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect(); | |
777 | let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect(); | |
b7449926 | 778 | |
041b39d2 | 779 | let mut macsp = ecx.call_site(); |
83c7162d | 780 | macsp = macsp.apply_mark(ecx.current_expansion.mark); |
b7449926 | 781 | |
8faf50e0 XL |
782 | let msg = "format argument must be a string literal"; |
783 | let fmt_sp = efmt.span; | |
9e0c209e | 784 | let fmt = match expr_to_spanned_string(ecx, efmt, msg) { |
8faf50e0 XL |
785 | Ok(mut fmt) if append_newline => { |
786 | fmt.node.0 = Symbol::intern(&format!("{}\n", fmt.node.0)); | |
787 | fmt | |
788 | } | |
789 | Ok(fmt) => fmt, | |
790 | Err(mut err) => { | |
791 | let sugg_fmt = match args.len() { | |
792 | 0 => "{}".to_string(), | |
793 | _ => format!("{}{{}}", "{} ".repeat(args.len())), | |
794 | }; | |
b7449926 | 795 | err.span_suggestion_with_applicability( |
8faf50e0 XL |
796 | fmt_sp.shrink_to_lo(), |
797 | "you might be missing a string literal to format with", | |
798 | format!("\"{}\", ", sugg_fmt), | |
b7449926 | 799 | Applicability::MaybeIncorrect, |
8faf50e0 XL |
800 | ); |
801 | err.emit(); | |
802 | return DummyResult::raw_expr(sp); | |
803 | } | |
804 | }; | |
b7449926 XL |
805 | |
806 | let is_literal = match ecx.source_map().span_to_snippet(fmt_sp) { | |
8faf50e0 XL |
807 | Ok(ref s) if s.starts_with("\"") || s.starts_with("r#") => true, |
808 | _ => false, | |
9e0c209e SL |
809 | }; |
810 | ||
b7449926 XL |
811 | let fmt_str = &*fmt.node.0.as_str(); |
812 | let str_style = match fmt.node.1 { | |
813 | ast::StrStyle::Cooked => None, | |
814 | ast::StrStyle::Raw(raw) => Some(raw as usize), | |
815 | }; | |
816 | ||
817 | let mut parser = parse::Parser::new(fmt_str, str_style); | |
818 | ||
819 | let mut unverified_pieces = Vec::new(); | |
820 | while let Some(piece) = parser.next() { | |
821 | if !parser.errors.is_empty() { | |
822 | break; | |
823 | } else { | |
824 | unverified_pieces.push(piece); | |
825 | } | |
826 | } | |
827 | ||
828 | if !parser.errors.is_empty() { | |
829 | let err = parser.errors.remove(0); | |
830 | let sp = fmt.span.from_inner_byte_pos(err.start, err.end); | |
831 | let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", | |
832 | err.description)); | |
833 | e.span_label(sp, err.label + " in format string"); | |
834 | if let Some(note) = err.note { | |
835 | e.note(¬e); | |
836 | } | |
837 | e.emit(); | |
838 | return DummyResult::raw_expr(sp); | |
839 | } | |
840 | ||
841 | let arg_spans = parser.arg_places.iter() | |
842 | .map(|&(start, end)| fmt.span.from_inner_byte_pos(start, end)) | |
843 | .collect(); | |
844 | ||
1a4d82fc | 845 | let mut cx = Context { |
3b2f2976 XL |
846 | ecx, |
847 | args, | |
848 | arg_types, | |
849 | arg_unique_types, | |
850 | names, | |
5bcae85e | 851 | curarg: 0, |
8faf50e0 | 852 | curpiece: 0, |
5bcae85e SL |
853 | arg_index_map: Vec::new(), |
854 | count_args: Vec::new(), | |
b7449926 | 855 | count_positions: FxHashMap::default(), |
5bcae85e SL |
856 | count_positions_count: 0, |
857 | count_args_index_offset: 0, | |
1a4d82fc | 858 | literal: String::new(), |
b7449926 XL |
859 | pieces: Vec::with_capacity(unverified_pieces.len()), |
860 | str_pieces: Vec::with_capacity(unverified_pieces.len()), | |
1a4d82fc | 861 | all_pieces_simple: true, |
3b2f2976 | 862 | macsp, |
9e0c209e | 863 | fmtsp: fmt.span, |
abe05a73 | 864 | invalid_refs: Vec::new(), |
b7449926 | 865 | arg_spans, |
8faf50e0 | 866 | is_literal, |
1a4d82fc JJ |
867 | }; |
868 | ||
8faf50e0 | 869 | // This needs to happen *after* the Parser has consumed all pieces to create all the spans |
b7449926 | 870 | let pieces = unverified_pieces.into_iter().map(|mut piece| { |
0531ce1d XL |
871 | cx.verify_piece(&piece); |
872 | cx.resolve_name_inplace(&mut piece); | |
b7449926 XL |
873 | piece |
874 | }).collect::<Vec<_>>(); | |
5bcae85e | 875 | |
abe05a73 XL |
876 | let numbered_position_args = pieces.iter().any(|arg: &parse::Piece| { |
877 | match *arg { | |
878 | parse::String(_) => false, | |
879 | parse::NextArgument(arg) => { | |
880 | match arg.position { | |
881 | parse::Position::ArgumentIs(_) => true, | |
882 | _ => false, | |
883 | } | |
884 | } | |
885 | } | |
886 | }); | |
887 | ||
5bcae85e SL |
888 | cx.build_index_map(); |
889 | ||
890 | let mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()]; | |
b7449926 | 891 | |
5bcae85e | 892 | for piece in pieces { |
94b46f34 XL |
893 | if let Some(piece) = cx.build_piece(&piece, &mut arg_index_consumed) { |
894 | let s = cx.build_literal_string(); | |
5bcae85e SL |
895 | cx.str_pieces.push(s); |
896 | cx.pieces.push(piece); | |
897 | } | |
898 | } | |
899 | ||
1a4d82fc | 900 | if !cx.literal.is_empty() { |
94b46f34 | 901 | let s = cx.build_literal_string(); |
1a4d82fc JJ |
902 | cx.str_pieces.push(s); |
903 | } | |
904 | ||
abe05a73 XL |
905 | if cx.invalid_refs.len() >= 1 { |
906 | cx.report_invalid_references(numbered_position_args); | |
907 | } | |
908 | ||
1a4d82fc | 909 | // Make sure that all arguments were used and all arguments have types. |
5bcae85e | 910 | let num_pos_args = cx.args.len() - cx.names.len(); |
b7449926 XL |
911 | |
912 | let errs = cx.arg_types | |
913 | .iter() | |
914 | .enumerate() | |
915 | .filter(|(i, ty)| ty.is_empty() && !cx.count_positions.contains_key(&i)) | |
916 | .map(|(i, _)| { | |
917 | let msg = if i >= num_pos_args { | |
918 | // named argument | |
919 | "named argument never used" | |
920 | } else { | |
921 | // positional argument | |
922 | "argument never used" | |
923 | }; | |
924 | (cx.args[i].span, msg) | |
925 | }) | |
926 | .collect::<Vec<_>>(); | |
927 | ||
8faf50e0 | 928 | let errs_len = errs.len(); |
b7449926 | 929 | if !errs.is_empty() { |
8faf50e0 XL |
930 | let args_used = cx.arg_types.len() - errs_len; |
931 | let args_unused = errs_len; | |
476ff2be SL |
932 | |
933 | let mut diag = { | |
8faf50e0 | 934 | if errs_len == 1 { |
476ff2be SL |
935 | let (sp, msg) = errs.into_iter().next().unwrap(); |
936 | cx.ecx.struct_span_err(sp, msg) | |
937 | } else { | |
2c00a5a8 XL |
938 | let mut diag = cx.ecx.struct_span_err( |
939 | errs.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(), | |
8faf50e0 | 940 | "multiple unused formatting arguments", |
2c00a5a8 | 941 | ); |
8faf50e0 | 942 | diag.span_label(cx.fmtsp, "multiple missing formatting specifiers"); |
476ff2be SL |
943 | diag |
944 | } | |
945 | }; | |
946 | ||
8faf50e0 XL |
947 | // Used to ensure we only report translations for *one* kind of foreign format. |
948 | let mut found_foreign = false; | |
476ff2be SL |
949 | // Decide if we want to look for foreign formatting directives. |
950 | if args_used < args_unused { | |
951 | use super::format_foreign as foreign; | |
952 | ||
953 | // The set of foreign substitutions we've explained. This prevents spamming the user | |
954 | // with `%d should be written as {}` over and over again. | |
b7449926 | 955 | let mut explained = FxHashSet::default(); |
476ff2be | 956 | |
476ff2be SL |
957 | macro_rules! check_foreign { |
958 | ($kind:ident) => {{ | |
959 | let mut show_doc_note = false; | |
960 | ||
8faf50e0 | 961 | let mut suggestions = vec![]; |
476ff2be SL |
962 | for sub in foreign::$kind::iter_subs(fmt_str) { |
963 | let trn = match sub.translate() { | |
964 | Some(trn) => trn, | |
965 | ||
966 | // If it has no translation, don't call it out specifically. | |
967 | None => continue, | |
968 | }; | |
969 | ||
8faf50e0 | 970 | let pos = sub.position(); |
476ff2be SL |
971 | let sub = String::from(sub.as_str()); |
972 | if explained.contains(&sub) { | |
973 | continue; | |
974 | } | |
975 | explained.insert(sub.clone()); | |
976 | ||
977 | if !found_foreign { | |
978 | found_foreign = true; | |
979 | show_doc_note = true; | |
980 | } | |
981 | ||
8faf50e0 XL |
982 | if let Some((start, end)) = pos { |
983 | // account for `"` and account for raw strings `r#` | |
984 | let padding = str_style.map(|i| i + 2).unwrap_or(1); | |
985 | let sp = fmt_sp.from_inner_byte_pos(start + padding, end + padding); | |
986 | suggestions.push((sp, trn)); | |
987 | } else { | |
988 | diag.help(&format!("`{}` should be written as `{}`", sub, trn)); | |
989 | } | |
476ff2be SL |
990 | } |
991 | ||
992 | if show_doc_note { | |
8faf50e0 XL |
993 | diag.note(concat!( |
994 | stringify!($kind), | |
995 | " formatting not supported; see the documentation for `std::fmt`", | |
996 | )); | |
997 | } | |
998 | if suggestions.len() > 0 { | |
0bf4aa26 | 999 | diag.multipart_suggestion_with_applicability( |
8faf50e0 XL |
1000 | "format specifiers use curly braces", |
1001 | suggestions, | |
0bf4aa26 | 1002 | Applicability::MachineApplicable, |
8faf50e0 | 1003 | ); |
476ff2be SL |
1004 | } |
1005 | }}; | |
1006 | } | |
1007 | ||
1008 | check_foreign!(printf); | |
1009 | if !found_foreign { | |
1010 | check_foreign!(shell); | |
1011 | } | |
1a4d82fc | 1012 | } |
8faf50e0 XL |
1013 | if !found_foreign && errs_len == 1 { |
1014 | diag.span_label(cx.fmtsp, "formatting specifier missing"); | |
1015 | } | |
476ff2be SL |
1016 | |
1017 | diag.emit(); | |
1a4d82fc JJ |
1018 | } |
1019 | ||
1020 | cx.into_expr() | |
1021 | } |