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    Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
9};
10use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
11use rustc_span::def_id::DefId;
12use rustc_span::{InnerSpan, Span, Symbol, kw, sym};
13
14/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces",
15/// either as string pieces or dynamic arguments.
16#[derive(Debug)]
17pub struct FormatString {
18    #[allow(dead_code, reason = "Debug impl")]
19    input: Symbol,
20    span: Span,
21    pieces: Vec<Piece>,
22    /// The formatting string was parsed successfully but with warnings
23    pub warnings: Vec<FormatWarning>,
24}
25
26#[derive(Debug)]
27enum Piece {
28    Lit(String),
29    Arg(FormatArg),
30}
31
32#[derive(Debug)]
33enum FormatArg {
34    // A generic parameter, like `{T}` if we're on the `From<T>` trait.
35    GenericParam {
36        generic_param: Symbol,
37    },
38    // `{Self}`
39    SelfUpper,
40    /// `{This}` or `{TraitName}`
41    This,
42    /// The sugared form of the trait
43    Trait,
44    /// what we're in, like a function, method, closure etc.
45    ItemContext,
46    /// What the user typed, if it doesn't match anything we can use.
47    AsIs(String),
48}
49
50pub enum Ctx<'tcx> {
51    // `#[rustc_on_unimplemented]`
52    RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
53    // `#[diagnostic::...]`
54    DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
55}
56
57#[derive(Debug)]
58pub enum FormatWarning {
59    UnknownParam { argument_name: Symbol, span: Span },
60    PositionalArgument { span: Span, help: String },
61    InvalidSpecifier { name: String, span: Span },
62    FutureIncompat { span: Span, help: String },
63}
64
65impl FormatWarning {
66    pub fn emit_warning<'tcx>(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) {
67        match *self {
68            FormatWarning::UnknownParam { argument_name, span } => {
69                let this = tcx.item_ident(item_def_id);
70                if let Some(item_def_id) = item_def_id.as_local() {
71                    tcx.emit_node_span_lint(
72                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
73                        tcx.local_def_id_to_hir_id(item_def_id),
74                        span,
75                        UnknownFormatParameterForOnUnimplementedAttr {
76                            argument_name,
77                            trait_name: this,
78                        },
79                    );
80                }
81            }
82            FormatWarning::PositionalArgument { span, .. } => {
83                if let Some(item_def_id) = item_def_id.as_local() {
84                    tcx.emit_node_span_lint(
85                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
86                        tcx.local_def_id_to_hir_id(item_def_id),
87                        span,
88                        DisallowedPositionalArgument,
89                    );
90                }
91            }
92            FormatWarning::InvalidSpecifier { span, .. } => {
93                if let Some(item_def_id) = item_def_id.as_local() {
94                    tcx.emit_node_span_lint(
95                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
96                        tcx.local_def_id_to_hir_id(item_def_id),
97                        span,
98                        InvalidFormatSpecifier,
99                    );
100                }
101            }
102            FormatWarning::FutureIncompat { .. } => {
103                // We've never deprecated anything in diagnostic namespace format strings
104                // but if we do we will emit a warning here
105
106                // FIXME(mejrs) in a couple releases, start emitting warnings for
107                // #[rustc_on_unimplemented] deprecated args
108            }
109        }
110    }
111}
112
113/// Arguments to fill a [FormatString] with.
114///
115/// For example, given a
116/// ```rust,ignore (just an example)
117///
118/// #[rustc_on_unimplemented(
119///     on(all(from_desugaring = "QuestionMark"),
120///         message = "the `?` operator can only be used in {ItemContext} \
121///                     that returns `Result` or `Option` \
122///                     (or another type that implements `{FromResidual}`)",
123///         label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`",
124///         parent_label = "this function should return `Result` or `Option` to accept `?`"
125///     ),
126/// )]
127/// pub trait FromResidual<R = <Self as Try>::Residual> {
128///    ...
129/// }
130///
131/// async fn an_async_function() -> u32 {
132///     let x: Option<u32> = None;
133///     x?; //~ ERROR the `?` operator
134///     22
135/// }
136///  ```
137/// it will look like this:
138///
139/// ```rust,ignore (just an example)
140/// FormatArgs {
141///     this: "FromResidual",
142///     trait_sugared: "FromResidual<Option<Infallible>>",
143///     item_context: "an async function",
144///     generic_args: [("Self", "u32"), ("R", "Option<Infallible>")],
145/// }
146/// ```
147#[derive(Debug)]
148pub struct FormatArgs<'tcx> {
149    pub this: String,
150    pub trait_sugared: TraitRefPrintSugared<'tcx>,
151    pub item_context: &'static str,
152    pub generic_args: Vec<(Symbol, String)>,
153}
154
155impl FormatString {
156    pub fn span(&self) -> Span {
157        self.span
158    }
159
160    pub fn parse<'tcx>(
161        input: Symbol,
162        snippet: Option<String>,
163        span: Span,
164        ctx: &Ctx<'tcx>,
165    ) -> Result<Self, ParseError> {
166        let s = input.as_str();
167        let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic);
168        let pieces: Vec<_> = parser.by_ref().collect();
169
170        if let Some(err) = parser.errors.into_iter().next() {
171            return Err(err);
172        }
173        let mut warnings = Vec::new();
174
175        let pieces = pieces
176            .into_iter()
177            .map(|piece| match piece {
178                RpfPiece::Lit(lit) => Piece::Lit(lit.into()),
179                RpfPiece::NextArgument(arg) => {
180                    warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal);
181                    let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal);
182                    Piece::Arg(arg)
183                }
184            })
185            .collect();
186
187        Ok(FormatString { input, pieces, span, warnings })
188    }
189
190    pub fn format(&self, args: &FormatArgs<'_>) -> String {
191        let mut ret = String::new();
192        for piece in &self.pieces {
193            match piece {
194                Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s),
195
196                // `A` if we have `trait Trait<A> {}` and `note = "i'm the actual type of {A}"`
197                Piece::Arg(FormatArg::GenericParam { generic_param }) => {
198                    // Should always be some but we can't raise errors here
199                    let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) {
200                        Some((_, val)) => val.to_string(),
201                        None => generic_param.to_string(),
202                    };
203                    ret.push_str(&value);
204                }
205                // `{Self}`
206                Piece::Arg(FormatArg::SelfUpper) => {
207                    let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) {
208                        Some((_, val)) => val.to_string(),
209                        None => "Self".to_string(),
210                    };
211                    ret.push_str(&slf);
212                }
213
214                // It's only `rustc_onunimplemented` from here
215                Piece::Arg(FormatArg::This) => ret.push_str(&args.this),
216                Piece::Arg(FormatArg::Trait) => {
217                    let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared));
218                }
219                Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context),
220            }
221        }
222        ret
223    }
224}
225
226fn parse_arg<'tcx>(
227    arg: &Argument<'_>,
228    ctx: &Ctx<'tcx>,
229    warnings: &mut Vec<FormatWarning>,
230    input_span: Span,
231    is_source_literal: bool,
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(), is_source_literal);
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(
287    spec: &FormatSpec<'_>,
288    warnings: &mut Vec<FormatWarning>,
289    input_span: Span,
290    is_source_literal: bool,
291) {
292    if spec.ty != "" {
293        let span = spec
294            .ty_span
295            .as_ref()
296            .map(|inner| slice_span(input_span, inner.clone(), is_source_literal))
297            .unwrap_or(input_span);
298        warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
299    }
300}
301
302fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal: bool) -> Span {
303    if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input }
304}
305
306pub mod errors {
307    use rustc_macros::LintDiagnostic;
308    use rustc_span::Ident;
309
310    use super::*;
311
312    #[derive(LintDiagnostic)]
313    #[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)]
314    #[help]
315    pub struct UnknownFormatParameterForOnUnimplementedAttr {
316        pub argument_name: Symbol,
317        pub trait_name: Ident,
318    }
319
320    #[derive(LintDiagnostic)]
321    #[diag(trait_selection_disallowed_positional_argument)]
322    #[help]
323    pub struct DisallowedPositionalArgument;
324
325    #[derive(LintDiagnostic)]
326    #[diag(trait_selection_invalid_format_specifier)]
327    #[help]
328    pub struct InvalidFormatSpecifier;
329
330    #[derive(LintDiagnostic)]
331    #[diag(trait_selection_missing_options_for_on_unimplemented_attr)]
332    #[help]
333    pub struct MissingOptionsForOnUnimplementedAttr;
334}