]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_trait_selection/src/traits/on_unimplemented.rs
Rollup merge of #99386 - AngelicosPhosphoros:add_retain_test_maybeuninit, r=JohnTitor
[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, ErrorGuaranteed};
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, DUMMY_SP};
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 ) -> ErrorGuaranteed {
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 }
52
53 impl<'tcx> OnUnimplementedDirective {
54     fn parse(
55         tcx: TyCtxt<'tcx>,
56         item_def_id: DefId,
57         items: &[NestedMetaItem],
58         span: Span,
59         is_root: bool,
60     ) -> Result<Self, ErrorGuaranteed> {
61         let mut errored = None;
62         let mut item_iter = items.iter();
63
64         let parse_value = |value_str| {
65             OnUnimplementedFormatString::try_parse(tcx, item_def_id, value_str, span).map(Some)
66         };
67
68         let condition = if is_root {
69             None
70         } else {
71             let cond = item_iter
72                 .next()
73                 .ok_or_else(|| {
74                     parse_error(
75                         tcx,
76                         span,
77                         "empty `on`-clause in `#[rustc_on_unimplemented]`",
78                         "empty on-clause here",
79                         None,
80                     )
81                 })?
82                 .meta_item()
83                 .ok_or_else(|| {
84                     parse_error(
85                         tcx,
86                         span,
87                         "invalid `on`-clause in `#[rustc_on_unimplemented]`",
88                         "invalid on-clause here",
89                         None,
90                     )
91                 })?;
92             attr::eval_condition(cond, &tcx.sess.parse_sess, Some(tcx.features()), &mut |cfg| {
93                 if let Some(value) = cfg.value && let Err(guar) = parse_value(value) {
94                     errored = Some(guar);
95                 }
96                 true
97             });
98             Some(cond.clone())
99         };
100
101         let mut message = None;
102         let mut label = None;
103         let mut note = None;
104         let mut enclosing_scope = None;
105         let mut subcommands = vec![];
106         let mut append_const_msg = None;
107
108         for item in item_iter {
109             if item.has_name(sym::message) && message.is_none() {
110                 if let Some(message_) = item.value_str() {
111                     message = parse_value(message_)?;
112                     continue;
113                 }
114             } else if item.has_name(sym::label) && label.is_none() {
115                 if let Some(label_) = item.value_str() {
116                     label = parse_value(label_)?;
117                     continue;
118                 }
119             } else if item.has_name(sym::note) && note.is_none() {
120                 if let Some(note_) = item.value_str() {
121                     note = parse_value(note_)?;
122                     continue;
123                 }
124             } else if item.has_name(sym::enclosing_scope) && enclosing_scope.is_none() {
125                 if let Some(enclosing_scope_) = item.value_str() {
126                     enclosing_scope = parse_value(enclosing_scope_)?;
127                     continue;
128                 }
129             } else if item.has_name(sym::on)
130                 && is_root
131                 && message.is_none()
132                 && label.is_none()
133                 && note.is_none()
134             {
135                 if let Some(items) = item.meta_item_list() {
136                     match Self::parse(tcx, item_def_id, &items, item.span(), false) {
137                         Ok(subcommand) => subcommands.push(subcommand),
138                         Err(reported) => errored = Some(reported),
139                     };
140                     continue;
141                 }
142             } else if item.has_name(sym::append_const_msg) && append_const_msg.is_none() {
143                 if let Some(msg) = item.value_str() {
144                     append_const_msg = Some(Some(msg));
145                     continue;
146                 } else if item.is_word() {
147                     append_const_msg = Some(None);
148                     continue;
149                 }
150             }
151
152             // nothing found
153             parse_error(
154                 tcx,
155                 item.span(),
156                 "this attribute must have a valid value",
157                 "expected value here",
158                 Some(r#"eg `#[rustc_on_unimplemented(message="foo")]`"#),
159             );
160         }
161
162         if let Some(reported) = errored {
163             Err(reported)
164         } else {
165             Ok(OnUnimplementedDirective {
166                 condition,
167                 subcommands,
168                 message,
169                 label,
170                 note,
171                 enclosing_scope,
172                 append_const_msg,
173             })
174         }
175     }
176
177     pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
178         let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) else {
179             return Ok(None);
180         };
181
182         let result = if let Some(items) = attr.meta_item_list() {
183             Self::parse(tcx, item_def_id, &items, attr.span, true).map(Some)
184         } else if let Some(value) = attr.value_str() {
185             Ok(Some(OnUnimplementedDirective {
186                 condition: None,
187                 message: None,
188                 subcommands: vec![],
189                 label: Some(OnUnimplementedFormatString::try_parse(
190                     tcx,
191                     item_def_id,
192                     value,
193                     attr.span,
194                 )?),
195                 note: None,
196                 enclosing_scope: None,
197                 append_const_msg: None,
198             }))
199         } else {
200             let reported =
201                 tcx.sess.delay_span_bug(DUMMY_SP, "of_item: neither meta_item_list nor value_str");
202             return Err(reported);
203         };
204         debug!("of_item({:?}) = {:?}", item_def_id, result);
205         result
206     }
207
208     pub fn evaluate(
209         &self,
210         tcx: TyCtxt<'tcx>,
211         trait_ref: ty::TraitRef<'tcx>,
212         options: &[(Symbol, Option<String>)],
213     ) -> OnUnimplementedNote {
214         let mut message = None;
215         let mut label = None;
216         let mut note = None;
217         let mut enclosing_scope = None;
218         let mut append_const_msg = None;
219         info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options);
220
221         let options_map: FxHashMap<Symbol, String> =
222             options.iter().filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.to_owned()))).collect();
223
224         for command in self.subcommands.iter().chain(Some(self)).rev() {
225             if let Some(ref condition) = command.condition && !attr::eval_condition(
226                 condition,
227                 &tcx.sess.parse_sess,
228                 Some(tcx.features()),
229                 &mut |cfg| {
230                     let value = cfg.value.map(|v| {
231                         OnUnimplementedFormatString(v).format(tcx, trait_ref, &options_map)
232                     });
233
234                     options.contains(&(cfg.name, value))
235                 },
236             ) {
237                 debug!("evaluate: skipping {:?} due to condition", command);
238                 continue;
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;
258         }
259
260         OnUnimplementedNote {
261             label: label.map(|l| l.format(tcx, trait_ref, &options_map)),
262             message: message.map(|m| m.format(tcx, trait_ref, &options_map)),
263             note: note.map(|n| n.format(tcx, trait_ref, &options_map)),
264             enclosing_scope: enclosing_scope.map(|e_s| e_s.format(tcx, trait_ref, &options_map)),
265             append_const_msg,
266         }
267     }
268 }
269
270 impl<'tcx> OnUnimplementedFormatString {
271     fn try_parse(
272         tcx: TyCtxt<'tcx>,
273         item_def_id: DefId,
274         from: Symbol,
275         err_sp: Span,
276     ) -> Result<Self, ErrorGuaranteed> {
277         let result = OnUnimplementedFormatString(from);
278         result.verify(tcx, item_def_id, err_sp)?;
279         Ok(result)
280     }
281
282     fn verify(
283         &self,
284         tcx: TyCtxt<'tcx>,
285         item_def_id: DefId,
286         span: Span,
287     ) -> Result<(), ErrorGuaranteed> {
288         let trait_def_id = if tcx.is_trait(item_def_id) {
289             item_def_id
290         } else {
291             tcx.trait_id_of_impl(item_def_id)
292                 .expect("expected `on_unimplemented` to correspond to a trait")
293         };
294         let trait_name = tcx.item_name(trait_def_id);
295         let generics = tcx.generics_of(item_def_id);
296         let s = self.0.as_str();
297         let parser = Parser::new(s, None, None, false, ParseMode::Format);
298         let mut result = Ok(());
299         for token in parser {
300             match token {
301                 Piece::String(_) => (), // Normal string, no need to check it
302                 Piece::NextArgument(a) => match a.position {
303                     Position::ArgumentNamed(s) => {
304                         match Symbol::intern(s) {
305                             // `{Self}` is allowed
306                             kw::SelfUpper => (),
307                             // `{ThisTraitsName}` is allowed
308                             s if s == trait_name => (),
309                             // `{from_method}` is allowed
310                             sym::from_method => (),
311                             // `{from_desugaring}` is allowed
312                             sym::from_desugaring => (),
313                             // `{ItemContext}` is allowed
314                             sym::ItemContext => (),
315                             // `{integral}` and `{integer}` and `{float}` are allowed
316                             sym::integral | sym::integer_ | sym::float => (),
317                             // So is `{A}` if A is a type parameter
318                             s => match generics.params.iter().find(|param| param.name == s) {
319                                 Some(_) => (),
320                                 None => {
321                                     let reported = struct_span_err!(
322                                         tcx.sess,
323                                         span,
324                                         E0230,
325                                         "there is no parameter `{}` on {}",
326                                         s,
327                                         if trait_def_id == item_def_id {
328                                             format!("trait `{}`", trait_name)
329                                         } else {
330                                             "impl".to_string()
331                                         }
332                                     )
333                                     .emit();
334                                     result = Err(reported);
335                                 }
336                             },
337                         }
338                     }
339                     // `{:1}` and `{}` are not to be used
340                     Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => {
341                         let reported = struct_span_err!(
342                             tcx.sess,
343                             span,
344                             E0231,
345                             "only named substitution parameters are allowed"
346                         )
347                         .emit();
348                         result = Err(reported);
349                     }
350                 },
351             }
352         }
353
354         result
355     }
356
357     pub fn format(
358         &self,
359         tcx: TyCtxt<'tcx>,
360         trait_ref: ty::TraitRef<'tcx>,
361         options: &FxHashMap<Symbol, String>,
362     ) -> String {
363         let name = tcx.item_name(trait_ref.def_id);
364         let trait_str = tcx.def_path_str(trait_ref.def_id);
365         let generics = tcx.generics_of(trait_ref.def_id);
366         let generic_map = generics
367             .params
368             .iter()
369             .filter_map(|param| {
370                 let value = match param.kind {
371                     GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
372                         trait_ref.substs[param.index as usize].to_string()
373                     }
374                     GenericParamDefKind::Lifetime => return None,
375                 };
376                 let name = param.name;
377                 Some((name, value))
378             })
379             .collect::<FxHashMap<Symbol, String>>();
380         let empty_string = String::new();
381
382         let s = self.0.as_str();
383         let parser = Parser::new(s, None, None, false, ParseMode::Format);
384         let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string);
385         parser
386             .map(|p| match p {
387                 Piece::String(s) => s,
388                 Piece::NextArgument(a) => match a.position {
389                     Position::ArgumentNamed(s) => {
390                         let s = Symbol::intern(s);
391                         match generic_map.get(&s) {
392                             Some(val) => val,
393                             None if s == name => &trait_str,
394                             None => {
395                                 if let Some(val) = options.get(&s) {
396                                     val
397                                 } else if s == sym::from_desugaring || s == sym::from_method {
398                                     // don't break messages using these two arguments incorrectly
399                                     &empty_string
400                                 } else if s == sym::ItemContext {
401                                     &item_context
402                                 } else if s == sym::integral {
403                                     "{integral}"
404                                 } else if s == sym::integer_ {
405                                     "{integer}"
406                                 } else if s == sym::float {
407                                     "{float}"
408                                 } else {
409                                     bug!(
410                                         "broken on_unimplemented {:?} for {:?}: \
411                                       no argument matching {:?}",
412                                         self.0,
413                                         trait_ref,
414                                         s
415                                     )
416                                 }
417                             }
418                         }
419                     }
420                     _ => bug!("broken on_unimplemented {:?} - bad format arg", self.0),
421                 },
422             })
423             .collect()
424     }
425 }