rustc_trait_selection/error_reporting/traits/
on_unimplemented_format.rs1use std::fmt;
2use std::ops::Range;
3
4use errors::*;
5use rustc_middle::ty::TyCtxt;
6use rustc_middle::ty::print::TraitRefPrintSugared;
7use rustc_parse_format::{
8 Alignment, Argument, Count, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece,
9 Position,
10};
11use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
12use rustc_span::def_id::DefId;
13use rustc_span::{BytePos, Pos, Span, Symbol, kw, sym};
14
15#[derive(Debug)]
18pub struct FormatString {
19 #[allow(dead_code, reason = "Debug impl")]
20 input: Symbol,
21 span: Span,
22 pieces: Vec<Piece>,
23 pub warnings: Vec<FormatWarning>,
25}
26
27#[derive(Debug)]
28enum Piece {
29 Lit(String),
30 Arg(FormatArg),
31}
32
33#[derive(Debug)]
34enum FormatArg {
35 GenericParam {
37 generic_param: Symbol,
38 },
39 SelfUpper,
41 This,
43 Trait,
45 ItemContext,
47 AsIs(String),
49}
50
51pub enum Ctx<'tcx> {
52 RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
54 DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
56}
57
58#[derive(Debug)]
59pub enum FormatWarning {
60 UnknownParam { argument_name: Symbol, span: Span },
61 PositionalArgument { span: Span, help: String },
62 InvalidSpecifier { name: String, span: Span },
63 FutureIncompat { span: Span, help: String },
64}
65
66impl FormatWarning {
67 pub fn emit_warning<'tcx>(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) {
68 match *self {
69 FormatWarning::UnknownParam { argument_name, span } => {
70 let this = tcx.item_ident(item_def_id);
71 if let Some(item_def_id) = item_def_id.as_local() {
72 tcx.emit_node_span_lint(
73 UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
74 tcx.local_def_id_to_hir_id(item_def_id),
75 span,
76 UnknownFormatParameterForOnUnimplementedAttr {
77 argument_name,
78 trait_name: this,
79 },
80 );
81 }
82 }
83 FormatWarning::PositionalArgument { span, .. } => {
84 if let Some(item_def_id) = item_def_id.as_local() {
85 tcx.emit_node_span_lint(
86 UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
87 tcx.local_def_id_to_hir_id(item_def_id),
88 span,
89 DisallowedPositionalArgument,
90 );
91 }
92 }
93 FormatWarning::InvalidSpecifier { span, .. } => {
94 if let Some(item_def_id) = item_def_id.as_local() {
95 tcx.emit_node_span_lint(
96 UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
97 tcx.local_def_id_to_hir_id(item_def_id),
98 span,
99 InvalidFormatSpecifier,
100 );
101 }
102 }
103 FormatWarning::FutureIncompat { .. } => {
104 }
110 }
111 }
112}
113
114#[derive(Debug)]
149pub struct FormatArgs<'tcx> {
150 pub this: String,
151 pub trait_sugared: TraitRefPrintSugared<'tcx>,
152 pub item_context: &'static str,
153 pub generic_args: Vec<(Symbol, String)>,
154}
155
156impl FormatString {
157 pub fn span(&self) -> Span {
158 self.span
159 }
160
161 pub fn parse<'tcx>(
162 input: Symbol,
163 span: Span,
164 ctx: &Ctx<'tcx>,
165 ) -> Result<Self, Vec<ParseError>> {
166 let s = input.as_str();
167 let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
168 let mut pieces = Vec::new();
169 let mut warnings = Vec::new();
170
171 for piece in &mut parser {
172 match piece {
173 RpfPiece::Lit(lit) => {
174 pieces.push(Piece::Lit(lit.into()));
175 }
176 RpfPiece::NextArgument(arg) => {
177 warn_on_format_spec(arg.format.clone(), &mut warnings, span);
178 let arg = parse_arg(&arg, ctx, &mut warnings, span);
179 pieces.push(Piece::Arg(arg));
180 }
181 }
182 }
183
184 if parser.errors.is_empty() {
185 Ok(FormatString { input, pieces, span, warnings })
186 } else {
187 Err(parser.errors)
188 }
189 }
190
191 pub fn format(&self, args: &FormatArgs<'_>) -> String {
192 let mut ret = String::new();
193 for piece in &self.pieces {
194 match piece {
195 Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s),
196
197 Piece::Arg(FormatArg::GenericParam { generic_param }) => {
199 let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) {
201 Some((_, val)) => val.to_string(),
202 None => generic_param.to_string(),
203 };
204 ret.push_str(&value);
205 }
206 Piece::Arg(FormatArg::SelfUpper) => {
208 let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) {
209 Some((_, val)) => val.to_string(),
210 None => "Self".to_string(),
211 };
212 ret.push_str(&slf);
213 }
214
215 Piece::Arg(FormatArg::This) => ret.push_str(&args.this),
217 Piece::Arg(FormatArg::Trait) => {
218 let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared));
219 }
220 Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context),
221 }
222 }
223 ret
224 }
225}
226
227fn parse_arg<'tcx>(
228 arg: &Argument<'_>,
229 ctx: &Ctx<'tcx>,
230 warnings: &mut Vec<FormatWarning>,
231 input_span: Span,
232) -> FormatArg {
233 let (Ctx::RustcOnUnimplemented { tcx, trait_def_id }
234 | Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }) = ctx;
235 let trait_name = tcx.item_ident(*trait_def_id);
236 let generics = tcx.generics_of(trait_def_id);
237 let span = slice_span(input_span, arg.position_span.clone());
238
239 match arg.position {
240 Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
242 (Ctx::RustcOnUnimplemented { .. }, sym::_Self) => {
244 warnings
245 .push(FormatWarning::FutureIncompat { span, help: String::from("use {Self}") });
246 FormatArg::SelfUpper
247 }
248 (
249 Ctx::RustcOnUnimplemented { .. },
250 sym::from_desugaring
251 | sym::crate_local
252 | sym::direct
253 | sym::cause
254 | sym::float
255 | sym::integer_
256 | sym::integral,
257 ) => {
258 warnings.push(FormatWarning::FutureIncompat {
259 span,
260 help: String::from("don't use this in a format string"),
261 });
262 FormatArg::AsIs(String::new())
263 }
264
265 (Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
267 (Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
268 (Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
269 (Ctx::RustcOnUnimplemented { .. }, name) if name == trait_name.name => {
272 warnings
273 .push(FormatWarning::FutureIncompat { span, help: String::from("use {This}") });
274 FormatArg::This
275 }
276
277 (
279 Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
280 kw::SelfUpper,
281 ) => FormatArg::SelfUpper,
282 (
283 Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
284 generic_param,
285 ) if generics.own_params.iter().any(|param| param.name == generic_param) => {
286 FormatArg::GenericParam { generic_param }
287 }
288
289 (_, argument_name) => {
290 warnings.push(FormatWarning::UnknownParam { argument_name, span });
291 FormatArg::AsIs(format!("{{{}}}", argument_name.as_str()))
292 }
293 },
294
295 Position::ArgumentIs(idx) => {
297 warnings.push(FormatWarning::PositionalArgument {
298 span,
299 help: format!("use `{{{idx}}}` to print a number in braces"),
300 });
301 FormatArg::AsIs(format!("{{{idx}}}"))
302 }
303 Position::ArgumentImplicitlyIs(_) => {
304 warnings.push(FormatWarning::PositionalArgument {
305 span,
306 help: String::from("use `{{}}` to print empty braces"),
307 });
308 FormatArg::AsIs(String::from("{}"))
309 }
310 }
311}
312
313fn warn_on_format_spec(spec: FormatSpec<'_>, warnings: &mut Vec<FormatWarning>, input_span: Span) {
316 if !matches!(
317 spec,
318 FormatSpec {
319 fill: None,
320 fill_span: None,
321 align: Alignment::AlignUnknown,
322 sign: None,
323 alternate: false,
324 zero_pad: false,
325 debug_hex: None,
326 precision: Count::CountImplied,
327 precision_span: None,
328 width: Count::CountImplied,
329 width_span: None,
330 ty: _,
331 ty_span: _,
332 },
333 ) {
334 let span = spec.ty_span.map(|inner| slice_span(input_span, inner)).unwrap_or(input_span);
335 warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
336 }
337}
338
339fn slice_span(input: Span, range: Range<usize>) -> Span {
340 let span = input.data();
341
342 Span::new(
343 span.lo + BytePos::from_usize(range.start),
344 span.lo + BytePos::from_usize(range.end),
345 span.ctxt,
346 span.parent,
347 )
348}
349
350pub mod errors {
351 use rustc_macros::LintDiagnostic;
352 use rustc_span::Ident;
353
354 use super::*;
355
356 #[derive(LintDiagnostic)]
357 #[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
358 #[help]
359 pub struct UnknownFormatParameterForOnUnimplementedAttr {
360 pub argument_name: Symbol,
361 pub trait_name: Ident,
362 }
363
364 #[derive(LintDiagnostic)]
365 #[diag(trait_selection_disallowed_positional_argument)]
366 #[help]
367 pub struct DisallowedPositionalArgument;
368
369 #[derive(LintDiagnostic)]
370 #[diag(trait_selection_invalid_format_specifier)]
371 #[help]
372 pub struct InvalidFormatSpecifier;
373
374 #[derive(LintDiagnostic)]
375 #[diag(trait_selection_missing_options_for_on_unimplemented_attr)]
376 #[help]
377 pub struct MissingOptionsForOnUnimplementedAttr;
378
379 #[derive(LintDiagnostic)]
380 #[diag(trait_selection_ignored_diagnostic_option)]
381 pub struct IgnoredDiagnosticOption {
382 pub option_name: &'static str,
383 #[label]
384 pub span: Span,
385 #[label(trait_selection_other_label)]
386 pub prev_span: Span,
387 }
388
389 impl IgnoredDiagnosticOption {
390 pub fn maybe_emit_warning<'tcx>(
391 tcx: TyCtxt<'tcx>,
392 item_def_id: DefId,
393 new: Option<Span>,
394 old: Option<Span>,
395 option_name: &'static str,
396 ) {
397 if let (Some(new_item), Some(old_item)) = (new, old) {
398 if let Some(item_def_id) = item_def_id.as_local() {
399 tcx.emit_node_span_lint(
400 UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
401 tcx.local_def_id_to_hir_id(item_def_id),
402 new_item,
403 IgnoredDiagnosticOption {
404 span: new_item,
405 prev_span: old_item,
406 option_name,
407 },
408 );
409 }
410 }
411 }
412 }
413}