]> git.lizzy.rs Git - rust.git/blob - src/librustc/traits/on_unimplemented.rs
Improve some compiletest documentation
[rust.git] / src / librustc / traits / on_unimplemented.rs
1 use fmt_macros::{Parser, Piece, Position};
2
3 use crate::hir::def_id::DefId;
4 use crate::ty::{self, TyCtxt, GenericParamDefKind};
5 use crate::util::common::ErrorReported;
6 use crate::util::nodemap::FxHashMap;
7
8 use syntax::ast::{MetaItem, NestedMetaItem};
9 use syntax::attr;
10 use syntax_pos::Span;
11 use syntax_pos::symbol::LocalInternedString;
12
13 #[derive(Clone, Debug)]
14 pub struct OnUnimplementedFormatString(LocalInternedString);
15
16 #[derive(Debug)]
17 pub struct OnUnimplementedDirective {
18     pub condition: Option<MetaItem>,
19     pub subcommands: Vec<OnUnimplementedDirective>,
20     pub message: Option<OnUnimplementedFormatString>,
21     pub label: Option<OnUnimplementedFormatString>,
22     pub note: Option<OnUnimplementedFormatString>,
23 }
24
25 pub struct OnUnimplementedNote {
26     pub message: Option<String>,
27     pub label: Option<String>,
28     pub note: Option<String>,
29 }
30
31 impl OnUnimplementedNote {
32     pub fn empty() -> Self {
33         OnUnimplementedNote { message: None, label: None, note: None }
34     }
35 }
36
37 fn parse_error(tcx: TyCtxt<'_, '_, '_>, span: Span,
38                message: &str,
39                label: &str,
40                note: Option<&str>)
41                -> ErrorReported
42 {
43     let mut diag = struct_span_err!(
44         tcx.sess, span, E0232, "{}", message);
45     diag.span_label(span, label);
46     if let Some(note) = note {
47         diag.note(note);
48     }
49     diag.emit();
50     ErrorReported
51 }
52
53 impl<'a, 'gcx, 'tcx> OnUnimplementedDirective {
54     pub fn parse(tcx: TyCtxt<'a, 'gcx, 'tcx>,
55                  trait_def_id: DefId,
56                  items: &[NestedMetaItem],
57                  span: Span,
58                  is_root: bool)
59                  -> Result<Self, ErrorReported>
60     {
61         let mut errored = false;
62         let mut item_iter = items.iter();
63
64         let condition = if is_root {
65             None
66         } else {
67             let cond = item_iter.next().ok_or_else(||
68                 parse_error(tcx, span,
69                             "empty `on`-clause in `#[rustc_on_unimplemented]`",
70                             "empty on-clause here",
71                             None)
72             )?.meta_item().ok_or_else(||
73                 parse_error(tcx, span,
74                             "invalid `on`-clause in `#[rustc_on_unimplemented]`",
75                             "invalid on-clause here",
76                             None)
77             )?;
78             attr::eval_condition(cond, &tcx.sess.parse_sess, &mut |_| true);
79             Some(cond.clone())
80         };
81
82         let mut message = None;
83         let mut label = None;
84         let mut note = None;
85         let mut subcommands = vec![];
86         for item in item_iter {
87             if item.check_name("message") && message.is_none() {
88                 if let Some(message_) = item.value_str() {
89                     message = Some(OnUnimplementedFormatString::try_parse(
90                         tcx, trait_def_id, message_.as_str(), span)?);
91                     continue;
92                 }
93             } else if item.check_name("label") && label.is_none() {
94                 if let Some(label_) = item.value_str() {
95                     label = Some(OnUnimplementedFormatString::try_parse(
96                         tcx, trait_def_id, label_.as_str(), span)?);
97                     continue;
98                 }
99             } else if item.check_name("note") && note.is_none() {
100                 if let Some(note_) = item.value_str() {
101                     note = Some(OnUnimplementedFormatString::try_parse(
102                         tcx, trait_def_id, note_.as_str(), span)?);
103                     continue;
104                 }
105             } else if item.check_name("on") && is_root &&
106                 message.is_none() && label.is_none() && note.is_none()
107             {
108                 if let Some(items) = item.meta_item_list() {
109                     if let Ok(subcommand) =
110                         Self::parse(tcx, trait_def_id, &items, item.span(), false)
111                     {
112                         subcommands.push(subcommand);
113                     } else {
114                         errored = true;
115                     }
116                     continue
117                 }
118             }
119
120             // nothing found
121             parse_error(tcx, item.span(),
122                         "this attribute must have a valid value",
123                         "expected value here",
124                         Some(r#"eg `#[rustc_on_unimplemented(message="foo")]`"#));
125         }
126
127         if errored {
128             Err(ErrorReported)
129         } else {
130             Ok(OnUnimplementedDirective { condition, message, label, subcommands, note })
131         }
132     }
133
134
135     pub fn of_item(tcx: TyCtxt<'a, 'gcx, 'tcx>,
136                    trait_def_id: DefId,
137                    impl_def_id: DefId)
138                    -> Result<Option<Self>, ErrorReported>
139     {
140         let attrs = tcx.get_attrs(impl_def_id);
141
142         let attr = if let Some(item) = attr::find_by_name(&attrs, "rustc_on_unimplemented") {
143             item
144         } else {
145             return Ok(None);
146         };
147
148         let result = if let Some(items) = attr.meta_item_list() {
149             Self::parse(tcx, trait_def_id, &items, attr.span, true).map(Some)
150         } else if let Some(value) = attr.value_str() {
151             Ok(Some(OnUnimplementedDirective {
152                 condition: None,
153                 message: None,
154                 subcommands: vec![],
155                 label: Some(OnUnimplementedFormatString::try_parse(
156                     tcx, trait_def_id, value.as_str(), attr.span)?),
157                 note: None,
158             }))
159         } else {
160             return Err(ErrorReported);
161         };
162         debug!("of_item({:?}/{:?}) = {:?}", trait_def_id, impl_def_id, result);
163         result
164     }
165
166     pub fn evaluate(&self,
167                     tcx: TyCtxt<'a, 'gcx, 'tcx>,
168                     trait_ref: ty::TraitRef<'tcx>,
169                     options: &[(String, Option<String>)])
170                     -> OnUnimplementedNote
171     {
172         let mut message = None;
173         let mut label = None;
174         let mut note = None;
175         info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options);
176
177         for command in self.subcommands.iter().chain(Some(self)).rev() {
178             if let Some(ref condition) = command.condition {
179                 if !attr::eval_condition(condition, &tcx.sess.parse_sess, &mut |c| {
180                     c.ident_str().map_or(false, |name| {
181                         options.contains(&(
182                             name.to_string(),
183                             c.value_str().map(|s| s.as_str().to_string())
184                         ))
185                     })
186                 }) {
187                     debug!("evaluate: skipping {:?} due to condition", command);
188                     continue
189                 }
190             }
191             debug!("evaluate: {:?} succeeded", command);
192             if let Some(ref message_) = command.message {
193                 message = Some(message_.clone());
194             }
195
196             if let Some(ref label_) = command.label {
197                 label = Some(label_.clone());
198             }
199
200             if let Some(ref note_) = command.note {
201                 note = Some(note_.clone());
202             }
203         }
204
205         let options: FxHashMap<String, String> = options.into_iter()
206             .filter_map(|(k, v)| v.as_ref().map(|v| (k.to_owned(), v.to_owned())))
207             .collect();
208         OnUnimplementedNote {
209             label: label.map(|l| l.format(tcx, trait_ref, &options)),
210             message: message.map(|m| m.format(tcx, trait_ref, &options)),
211             note: note.map(|n| n.format(tcx, trait_ref, &options)),
212         }
213     }
214 }
215
216 impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString {
217     pub fn try_parse(tcx: TyCtxt<'a, 'gcx, 'tcx>,
218                      trait_def_id: DefId,
219                      from: LocalInternedString,
220                      err_sp: Span)
221                      -> Result<Self, ErrorReported>
222     {
223         let result = OnUnimplementedFormatString(from);
224         result.verify(tcx, trait_def_id, err_sp)?;
225         Ok(result)
226     }
227
228     fn verify(&self,
229               tcx: TyCtxt<'a, 'gcx, 'tcx>,
230               trait_def_id: DefId,
231               span: Span)
232               -> Result<(), ErrorReported>
233     {
234         let name = tcx.item_name(trait_def_id);
235         let generics = tcx.generics_of(trait_def_id);
236         let parser = Parser::new(&self.0, None, vec![], false);
237         let mut result = Ok(());
238         for token in parser {
239             match token {
240                 Piece::String(_) => (), // Normal string, no need to check it
241                 Piece::NextArgument(a) => match a.position {
242                     // `{Self}` is allowed
243                     Position::ArgumentNamed(s) if s == "Self" => (),
244                     // `{ThisTraitsName}` is allowed
245                     Position::ArgumentNamed(s) if s == name => (),
246                     // `{from_method}` is allowed
247                     Position::ArgumentNamed(s) if s == "from_method" => (),
248                     // `{from_desugaring}` is allowed
249                     Position::ArgumentNamed(s) if s == "from_desugaring" => (),
250                     // So is `{A}` if A is a type parameter
251                     Position::ArgumentNamed(s) => match generics.params.iter().find(|param|
252                         param.name == s
253                     ) {
254                         Some(_) => (),
255                         None => {
256                             span_err!(tcx.sess, span, E0230,
257                                       "there is no parameter `{}` on trait `{}`", s, name);
258                             result = Err(ErrorReported);
259                         }
260                     },
261                     // `{:1}` and `{}` are not to be used
262                     Position::ArgumentIs(_) | Position::ArgumentImplicitlyIs(_) => {
263                         span_err!(tcx.sess, span, E0231,
264                                   "only named substitution parameters are allowed");
265                         result = Err(ErrorReported);
266                     }
267                 }
268             }
269         }
270
271         result
272     }
273
274     pub fn format(&self,
275                   tcx: TyCtxt<'a, 'gcx, 'tcx>,
276                   trait_ref: ty::TraitRef<'tcx>,
277                   options: &FxHashMap<String, String>)
278                   -> String
279     {
280         let name = tcx.item_name(trait_ref.def_id);
281         let trait_str = tcx.def_path_str(trait_ref.def_id);
282         let generics = tcx.generics_of(trait_ref.def_id);
283         let generic_map = generics.params.iter().filter_map(|param| {
284             let value = match param.kind {
285                 GenericParamDefKind::Type { .. } |
286                 GenericParamDefKind::Const => {
287                     trait_ref.substs[param.index as usize].to_string()
288                 },
289                 GenericParamDefKind::Lifetime => return None
290             };
291             let name = param.name.to_string();
292             Some((name, value))
293         }).collect::<FxHashMap<String, String>>();
294         let empty_string = String::new();
295
296         let parser = Parser::new(&self.0, None, vec![], false);
297         parser.map(|p|
298             match p {
299                 Piece::String(s) => s,
300                 Piece::NextArgument(a) => match a.position {
301                     Position::ArgumentNamed(s) => match generic_map.get(s) {
302                         Some(val) => val,
303                         None if s == name => {
304                             &trait_str
305                         }
306                         None => {
307                             if let Some(val) = options.get(s) {
308                                 val
309                             } else if s == "from_desugaring" || s == "from_method" {
310                                 // don't break messages using these two arguments incorrectly
311                                 &empty_string
312                             } else {
313                                 bug!("broken on_unimplemented {:?} for {:?}: \
314                                       no argument matching {:?}",
315                                      self.0, trait_ref, s)
316                             }
317                         }
318                     },
319                     _ => bug!("broken on_unimplemented {:?} - bad format arg", self.0)
320                 }
321             }
322         ).collect()
323     }
324 }