rustc_trait_selection/error_reporting/traits/
on_unimplemented_format.rs

1use std::fmt;
2use std::ops::Range;
3
4use errors::*;
5use rustc_middle::ty::print::TraitRefPrintSugared;
6use rustc_middle::ty::{GenericParamDefKind, TyCtxt};
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/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces",
16/// either as string pieces or dynamic arguments.
17#[derive(Debug)]
18pub struct FormatString {
19    #[allow(dead_code, reason = "Debug impl")]
20    input: Symbol,
21    span: Span,
22    pieces: Vec<Piece>,
23    /// The formatting string was parsed successfully but with warnings
24    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    // A generic parameter, like `{T}` if we're on the `From<T>` trait.
36    GenericParam {
37        generic_param: Symbol,
38    },
39    // `{Self}`
40    SelfUpper,
41    /// `{This}` or `{TraitName}`
42    This,
43    /// The sugared form of the trait
44    Trait,
45    /// what we're in, like a function, method, closure etc.
46    ItemContext,
47    /// What the user typed, if it doesn't match anything we can use.
48    AsIs(String),
49}
50
51pub enum Ctx<'tcx> {
52    // `#[rustc_on_unimplemented]`
53    RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
54    // `#[diagnostic::...]`
55    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                // We've never deprecated anything in diagnostic namespace format strings
105                // but if we do we will emit a warning here
106
107                // FIXME(mejrs) in a couple releases, start emitting warnings for
108                // #[rustc_on_unimplemented] deprecated args
109            }
110        }
111    }
112}
113
114/// Arguments to fill a [FormatString] with.
115///
116/// For example, given a
117/// ```rust,ignore (just an example)
118///
119/// #[rustc_on_unimplemented(
120///     on(all(from_desugaring = "QuestionMark"),
121///         message = "the `?` operator can only be used in {ItemContext} \
122///                     that returns `Result` or `Option` \
123///                     (or another type that implements `{FromResidual}`)",
124///         label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`",
125///         parent_label = "this function should return `Result` or `Option` to accept `?`"
126///     ),
127/// )]
128/// pub trait FromResidual<R = <Self as Try>::Residual> {
129///    ...
130/// }
131///
132/// async fn an_async_function() -> u32 {
133///     let x: Option<u32> = None;
134///     x?; //~ ERROR the `?` operator
135///     22
136/// }
137///  ```
138/// it will look like this:
139///
140/// ```rust,ignore (just an example)
141/// FormatArgs {
142///     this: "FromResidual",
143///     trait_sugared: "FromResidual<Option<Infallible>>",
144///     item_context: "an async function",
145///     generic_args: [("Self", "u32"), ("R", "Option<Infallible>")],
146/// }
147/// ```
148#[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                // `A` if we have `trait Trait<A> {}` and `note = "i'm the actual type of {A}"`
198                Piece::Arg(FormatArg::GenericParam { generic_param }) => {
199                    // Should always be some but we can't raise errors here
200                    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                // `{Self}`
207                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                // It's only `rustc_onunimplemented` from here
216                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
236    let span = slice_span(input_span, arg.position_span.clone());
237
238    match arg.position {
239        // Something like "hello {name}"
240        Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
241            // Only `#[rustc_on_unimplemented]` can use these
242            (Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
243            (Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
244            (Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
245            // Any attribute can use these
246            (
247                Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
248                kw::SelfUpper,
249            ) => FormatArg::SelfUpper,
250            (
251                Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
252                generic_param,
253            ) if tcx.generics_of(trait_def_id).own_params.iter().any(|param| {
254                !matches!(param.kind, GenericParamDefKind::Lifetime) && param.name == generic_param
255            }) =>
256            {
257                FormatArg::GenericParam { generic_param }
258            }
259
260            (_, argument_name) => {
261                warnings.push(FormatWarning::UnknownParam { argument_name, span });
262                FormatArg::AsIs(format!("{{{}}}", argument_name.as_str()))
263            }
264        },
265
266        // `{:1}` and `{}` are ignored
267        Position::ArgumentIs(idx) => {
268            warnings.push(FormatWarning::PositionalArgument {
269                span,
270                help: format!("use `{{{idx}}}` to print a number in braces"),
271            });
272            FormatArg::AsIs(format!("{{{idx}}}"))
273        }
274        Position::ArgumentImplicitlyIs(_) => {
275            warnings.push(FormatWarning::PositionalArgument {
276                span,
277                help: String::from("use `{{}}` to print empty braces"),
278            });
279            FormatArg::AsIs(String::from("{}"))
280        }
281    }
282}
283
284/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything
285/// with specifiers, so emit a warning if they are used.
286fn warn_on_format_spec(spec: FormatSpec<'_>, warnings: &mut Vec<FormatWarning>, input_span: Span) {
287    if !matches!(
288        spec,
289        FormatSpec {
290            fill: None,
291            fill_span: None,
292            align: Alignment::AlignUnknown,
293            sign: None,
294            alternate: false,
295            zero_pad: false,
296            debug_hex: None,
297            precision: Count::CountImplied,
298            precision_span: None,
299            width: Count::CountImplied,
300            width_span: None,
301            ty: _,
302            ty_span: _,
303        },
304    ) {
305        let span = spec.ty_span.map(|inner| slice_span(input_span, inner)).unwrap_or(input_span);
306        warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
307    }
308}
309
310fn slice_span(input: Span, range: Range<usize>) -> Span {
311    let span = input.data();
312
313    Span::new(
314        span.lo + BytePos::from_usize(range.start),
315        span.lo + BytePos::from_usize(range.end),
316        span.ctxt,
317        span.parent,
318    )
319}
320
321pub mod errors {
322    use rustc_macros::LintDiagnostic;
323    use rustc_span::Ident;
324
325    use super::*;
326
327    #[derive(LintDiagnostic)]
328    #[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
329    #[help]
330    pub struct UnknownFormatParameterForOnUnimplementedAttr {
331        pub argument_name: Symbol,
332        pub trait_name: Ident,
333    }
334
335    #[derive(LintDiagnostic)]
336    #[diag(trait_selection_disallowed_positional_argument)]
337    #[help]
338    pub struct DisallowedPositionalArgument;
339
340    #[derive(LintDiagnostic)]
341    #[diag(trait_selection_invalid_format_specifier)]
342    #[help]
343    pub struct InvalidFormatSpecifier;
344
345    #[derive(LintDiagnostic)]
346    #[diag(trait_selection_missing_options_for_on_unimplemented_attr)]
347    #[help]
348    pub struct MissingOptionsForOnUnimplementedAttr;
349}