]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_trait_selection/src/traits/on_unimplemented.rs
Rollup merge of #93325 - tmiasko:lev, r=davidtwco
[rust.git] / compiler / rustc_trait_selection / src / traits / on_unimplemented.rs
1 use rustc_ast::{MetaItem, NestedMetaItem};
2 use rustc_attr as attr;
3 use rustc_data_structures::fx::FxHashMap;
4 use rustc_errors::{struct_span_err, ErrorReported};
5 use rustc_hir::def_id::DefId;
6 use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt};
7 use rustc_parse_format::{ParseMode, Parser, Piece, Position};
8 use rustc_span::symbol::{kw, sym, Symbol};
9 use rustc_span::Span;
10
11 #[derive(Clone, Debug)]
12 pub struct OnUnimplementedFormatString(Symbol);
13
14 #[derive(Debug)]
15 pub struct OnUnimplementedDirective {
16     pub condition: Option<MetaItem>,
17     pub subcommands: Vec<OnUnimplementedDirective>,
18     pub message: Option<OnUnimplementedFormatString>,
19     pub label: Option<OnUnimplementedFormatString>,
20     pub note: Option<OnUnimplementedFormatString>,
21     pub enclosing_scope: Option<OnUnimplementedFormatString>,
22     pub append_const_msg: Option<Option<Symbol>>,
23 }
24
25 #[derive(Default)]
26 pub struct OnUnimplementedNote {
27     pub message: Option<String>,
28     pub label: Option<String>,
29     pub note: Option<String>,
30     pub enclosing_scope: Option<String>,
31     /// Append a message for `~const Trait` errors. `None` means not requested and
32     /// should fallback to a generic message, `Some(None)` suggests using the default
33     /// appended message, `Some(Some(s))` suggests use the `s` message instead of the
34     /// default one..
35     pub append_const_msg: Option<Option<Symbol>>,
36 }
37
38 fn parse_error(
39     tcx: TyCtxt<'_>,
40     span: Span,
41     message: &str,
42     label: &str,
43     note: Option<&str>,
44 ) -> ErrorReported {
45     let mut diag = struct_span_err!(tcx.sess, span, E0232, "{}", message);
46     diag.span_label(span, label);
47     if let Some(note) = note {
48         diag.note(note);
49     }
50     diag.emit();
51     ErrorReported
52 }
53
54 impl<'tcx> OnUnimplementedDirective {
55     fn parse(
56         tcx: TyCtxt<'tcx>,
57         trait_def_id: DefId,
58         items: &[NestedMetaItem],
59         span: Span,
60         is_root: bool,
61     ) -> Result<Self, ErrorReported> {
62         let mut errored = false;
63         let mut item_iter = items.iter();
64
65         let condition = if is_root {
66             None
67         } else {
68             let cond = item_iter
69                 .next()
70                 .ok_or_else(|| {
71                     parse_error(
72                         tcx,
73                         span,
74                         "empty `on`-clause in `#[rustc_on_unimplemented]`",
75                         "empty on-clause here",
76                         None,
77                     )
78                 })?
79                 .meta_item()
80                 .ok_or_else(|| {
81                     parse_error(
82                         tcx,
83                         span,
84                         "invalid `on`-clause in `#[rustc_on_unimplemented]`",
85                         "invalid on-clause here",
86                         None,
87                     )
88                 })?;
89             attr::eval_condition(cond, &tcx.sess.parse_sess, Some(tcx.features()), &mut |_| true);
90             Some(cond.clone())
91         };
92
93         let mut message = None;
94         let mut label = None;
95         let mut note = None;
96         let mut enclosing_scope = None;
97         let mut subcommands = vec![];
98         let mut append_const_msg = None;
99
100         let parse_value = |value_str| {
101             OnUnimplementedFormatString::try_parse(tcx, trait_def_id, value_str, span).map(Some)
102         };
103
104         for item in item_iter {
105             if item.has_name(sym::message) && message.is_none() {
106                 if let Some(message_) = item.value_str() {
107                     message = parse_value(message_)?;
108                     continue;
109                 }
110             } else if item.has_name(sym::label) && label.is_none() {
111                 if let Some(label_) = item.value_str() {
112                     label = parse_value(label_)?;
113                     continue;
114                 }
115             } else if item.has_name(sym::note) && note.is_none() {
116                 if let Some(note_) = item.value_str() {
117                     note = parse_value(note_)?;
118                     continue;
119                 }
120             } else if item.has_name(sym::enclosing_scope) && enclosing_scope.is_none() {
121                 if let Some(enclosing_scope_) = item.value_str() {
122                     enclosing_scope = parse_value(enclosing_scope_)?;
123                     continue;
124                 }
125             } else if item.has_name(sym::on)
126                 && is_root
127                 && message.is_none()
128                 && label.is_none()
129                 && note.is_none()
130             {
131                 if let Some(items) = item.meta_item_list() {
132                     if let Ok(subcommand) =
133                         Self::parse(tcx, trait_def_id, &items, item.span(), false)
134                     {
135                         subcommands.push(subcommand);
136                     } else {
137                         errored = true;
138                     }
139                     continue;
140                 }
141             } else if item.has_name(sym::append_const_msg) && append_const_msg.is_none() {
142                 if let Some(msg) = item.value_str() {
143                     append_const_msg = Some(Some(msg));
144                     continue;
145                 } else if item.is_word() {
146                     append_const_msg = Some(None);
147                     continue;
148                 }
149             }
150
151             // nothing found
152             parse_error(
153                 tcx,
154                 item.span(),
155                 "this attribute must have a valid value",
156                 "expected value here",
157                 Some(r#"eg `#[rustc_on_unimplemented(message="foo")]`"#),
158             );
159         }
160
161         if errored {
162             Err(ErrorReported)
163         } else {
164             Ok(OnUnimplementedDirective {
165                 condition,
166                 subcommands,
167                 message,
168                 label,
169                 note,
170                 enclosing_scope,
171                 append_const_msg,
172             })
173         }
174     }
175
176     pub fn of_item(
177         tcx: TyCtxt<'tcx>,
178         trait_def_id: DefId,
179         impl_def_id: DefId,
180     ) -> Result<Option<Self>, ErrorReported> {
181         let attrs = tcx.get_attrs(impl_def_id);
182
183         let Some(attr) = tcx.sess.find_by_name(&attrs, sym::rustc_on_unimplemented) else {
184             return Ok(None);
185         };
186
187         let result = if let Some(items) = attr.meta_item_list() {
188             Self::parse(tcx, trait_def_id, &items, attr.span, true).map(Some)
189         } else if let Some(value) = attr.value_str() {
190             Ok(Some(OnUnimplementedDirective {
191                 condition: None,
192                 message: None,
193                 subcommands: vec![],
194                 label: Some(OnUnimplementedFormatString::try_parse(
195                     tcx,
196                     trait_def_id,
197                     value,
198                     attr.span,
199                 )?),
200                 note: None,
201                 enclosing_scope: None,
202                 append_const_msg: None,
203             }))
204         } else {
205             return Err(ErrorReported);
206         };
207         debug!("of_item({:?}/{:?}) = {:?}", trait_def_id, impl_def_id, result);
208         result
209     }
210
211     pub fn evaluate(
212         &self,
213         tcx: TyCtxt<'tcx>,
214         trait_ref: ty::TraitRef<'tcx>,
215         options: &[(Symbol, Option<String>)],
216     ) -> OnUnimplementedNote {
217         let mut message = None;
218         let mut label = None;
219         let mut note = None;
220         let mut enclosing_scope = None;
221         let mut append_const_msg = None;
222         info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options);
223
224         for command in self.subcommands.iter().chain(Some(self)).rev() {
225             if let Some(ref condition) = command.condition {
226                 if !attr::eval_condition(
227                     condition,
228                     &tcx.sess.parse_sess,
229                     Some(tcx.features()),
230                     &mut |c| {
231                         c.ident().map_or(false, |ident| {
232                             options.contains(&(ident.name, c.value_str().map(|s| s.to_string())))
233                         })
234                     },
235                 ) {
236                     debug!("evaluate: skipping {:?} due to condition", command);
237                     continue;
238                 }
239             }
240             debug!("evaluate: {:?} succeeded", command);
241             if let Some(ref message_) = command.message {
242                 message = Some(message_.clone());
243             }
244
245             if let Some(ref label_) = command.label {
246                 label = Some(label_.clone());
247             }
248
249             if let Some(ref note_) = command.note {
250                 note = Some(note_.clone());
251             }
252
253             if let Some(ref enclosing_scope_) = command.enclosing_scope {
254                 enclosing_scope = Some(enclosing_scope_.clone());
255             }
256
257             append_const_msg = command.append_const_msg.clone();
258         }
259
260         let options: FxHashMap<Symbol, String> =
261             options.iter().filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.to_owned()))).collect();
262         OnUnimplementedNote {
263             label: label.map(|l| l.format(tcx, trait_ref, &options)),
264             message: message.map(|m| m.format(tcx, trait_ref, &options)),
265             note: note.map(|n| n.format(tcx, trait_ref, &options)),
266             enclosing_scope: enclosing_scope.map(|e_s| e_s.format(tcx, trait_ref, &options)),
267             append_const_msg,
268         }
269     }
270 }
271
272 impl<'tcx> OnUnimplementedFormatString {
273     fn try_parse(
274         tcx: TyCtxt<'tcx>,
275         trait_def_id: DefId,
276         from: Symbol,
277         err_sp: Span,
278     ) -> Result<Self, ErrorReported> {
279         let result = OnUnimplementedFormatString(from);
280         result.verify(tcx, trait_def_id, err_sp)?;
281         Ok(result)
282     }
283
284     fn verify(
285         &self,
286         tcx: TyCtxt<'tcx>,
287         trait_def_id: DefId,
288         span: Span,
289     ) -> Result<(), ErrorReported> {
290         let name = tcx.item_name(trait_def_id);
291         let generics = tcx.generics_of(trait_def_id);
292         let s = self.0.as_str();
293         let parser = Parser::new(s, None, None, false, ParseMode::Format);
294         let mut result = Ok(());
295         for token in parser {
296             match token {
297                 Piece::String(_) => (), // Normal string, no need to check it
298                 Piece::NextArgument(a) => match a.position {
299                     // `{Self}` is allowed
300                     Position::ArgumentNamed(s) if s == kw::SelfUpper => (),
301                     // `{ThisTraitsName}` is allowed
302                     Position::ArgumentNamed(s) if s == name => (),
303                     // `{from_method}` is allowed
304                     Position::ArgumentNamed(s) if s == sym::from_method => (),
305                     // `{from_desugaring}` is allowed
306                     Position::ArgumentNamed(s) if s == sym::from_desugaring => (),
307                     // `{ItemContext}` is allowed
308                     Position::ArgumentNamed(s) if s == sym::ItemContext => (),
309                     // So is `{A}` if A is a type parameter
310                     Position::ArgumentNamed(s) => {
311                         match generics.params.iter().find(|param| param.name == s) {
312                             Some(_) => (),
313                             None => {
314                                 struct_span_err!(
315                                     tcx.sess,
316                                     span,
317                                     E0230,
318                                     "there is no parameter `{}` on trait `{}`",
319                                     s,
320                                     name
321                                 )
322                                 .emit();
323                                 result = Err(ErrorReported);
324                             }
325                         }
326                     }
327                     // `{:1}` and `{}` are not to be used
328                     Position::ArgumentIs(_) | Position::ArgumentImplicitlyIs(_) => {
329                         struct_span_err!(
330                             tcx.sess,
331                             span,
332                             E0231,
333                             "only named substitution parameters are allowed"
334                         )
335                         .emit();
336                         result = Err(ErrorReported);
337                     }
338                 },
339             }
340         }
341
342         result
343     }
344
345     pub fn format(
346         &self,
347         tcx: TyCtxt<'tcx>,
348         trait_ref: ty::TraitRef<'tcx>,
349         options: &FxHashMap<Symbol, String>,
350     ) -> String {
351         let name = tcx.item_name(trait_ref.def_id);
352         let trait_str = tcx.def_path_str(trait_ref.def_id);
353         let generics = tcx.generics_of(trait_ref.def_id);
354         let generic_map = generics
355             .params
356             .iter()
357             .filter_map(|param| {
358                 let value = match param.kind {
359                     GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
360                         trait_ref.substs[param.index as usize].to_string()
361                     }
362                     GenericParamDefKind::Lifetime => return None,
363                 };
364                 let name = param.name;
365                 Some((name, value))
366             })
367             .collect::<FxHashMap<Symbol, String>>();
368         let empty_string = String::new();
369
370         let s = self.0.as_str();
371         let parser = Parser::new(s, None, None, false, ParseMode::Format);
372         let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string);
373         parser
374             .map(|p| match p {
375                 Piece::String(s) => s,
376                 Piece::NextArgument(a) => match a.position {
377                     Position::ArgumentNamed(s) => match generic_map.get(&s) {
378                         Some(val) => val,
379                         None if s == name => &trait_str,
380                         None => {
381                             if let Some(val) = options.get(&s) {
382                                 val
383                             } else if s == sym::from_desugaring || s == sym::from_method {
384                                 // don't break messages using these two arguments incorrectly
385                                 &empty_string
386                             } else if s == sym::ItemContext {
387                                 &item_context
388                             } else {
389                                 bug!(
390                                     "broken on_unimplemented {:?} for {:?}: \
391                                       no argument matching {:?}",
392                                     self.0,
393                                     trait_ref,
394                                     s
395                                 )
396                             }
397                         }
398                     },
399                     _ => bug!("broken on_unimplemented {:?} - bad format arg", self.0),
400                 },
401             })
402             .collect()
403     }
404 }