rustc_trait_selection/error_reporting/traits/
on_unimplemented_format.rs1use 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#[derive(Debug)]
17pub struct FormatString {
18 #[allow(dead_code, reason = "Debug impl")]
19 input: Symbol,
20 span: Span,
21 pieces: Vec<Piece>,
22 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 GenericParam {
36 generic_param: Symbol,
37 },
38 SelfUpper,
40 This,
42 Trait,
44 ItemContext,
46 AsIs(String),
48}
49
50pub enum Ctx<'tcx> {
51 RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId },
53 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 }
109 }
110 }
111}
112
113#[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 Piece::Arg(FormatArg::GenericParam { generic_param }) => {
198 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 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 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 Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
241 (Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
243 (Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
244 (Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
245 (
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 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
284fn 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}