]>
Commit | Line | Data |
---|---|---|
3dfed10e | 1 | use rustc_ast::{MetaItem, NestedMetaItem}; |
74b04a01 | 2 | use rustc_attr as attr; |
dfeec247 | 3 | use rustc_data_structures::fx::FxHashMap; |
5e7ed085 | 4 | use rustc_errors::{struct_span_err, ErrorGuaranteed}; |
dfeec247 | 5 | use rustc_hir::def_id::DefId; |
ba9703b0 | 6 | use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt}; |
f035d41b | 7 | use rustc_parse_format::{ParseMode, Parser, Piece, Position}; |
dfeec247 | 8 | use rustc_span::symbol::{kw, sym, Symbol}; |
5e7ed085 | 9 | use rustc_span::{Span, DUMMY_SP}; |
60c5eb7d | 10 | |
ea8adc8c | 11 | #[derive(Clone, Debug)] |
e1599b0c | 12 | pub struct OnUnimplementedFormatString(Symbol); |
ea8adc8c XL |
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>, | |
2c00a5a8 | 20 | pub note: Option<OnUnimplementedFormatString>, |
60c5eb7d | 21 | pub enclosing_scope: Option<OnUnimplementedFormatString>, |
5099ac24 | 22 | pub append_const_msg: Option<Option<Symbol>>, |
ea8adc8c XL |
23 | } |
24 | ||
60c5eb7d | 25 | #[derive(Default)] |
ea8adc8c XL |
26 | pub struct OnUnimplementedNote { |
27 | pub message: Option<String>, | |
28 | pub label: Option<String>, | |
2c00a5a8 | 29 | pub note: Option<String>, |
60c5eb7d | 30 | pub enclosing_scope: Option<String>, |
5099ac24 FG |
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>>, | |
ea8adc8c XL |
36 | } |
37 | ||
dc9dc135 XL |
38 | fn parse_error( |
39 | tcx: TyCtxt<'_>, | |
40 | span: Span, | |
41 | message: &str, | |
42 | label: &str, | |
43 | note: Option<&str>, | |
5e7ed085 | 44 | ) -> ErrorGuaranteed { |
dfeec247 | 45 | let mut diag = struct_span_err!(tcx.sess, span, E0232, "{}", message); |
ea8adc8c XL |
46 | diag.span_label(span, label); |
47 | if let Some(note) = note { | |
48 | diag.note(note); | |
49 | } | |
5e7ed085 | 50 | diag.emit() |
ea8adc8c XL |
51 | } |
52 | ||
dc9dc135 XL |
53 | impl<'tcx> OnUnimplementedDirective { |
54 | fn parse( | |
55 | tcx: TyCtxt<'tcx>, | |
5e7ed085 | 56 | item_def_id: DefId, |
dc9dc135 XL |
57 | items: &[NestedMetaItem], |
58 | span: Span, | |
59 | is_root: bool, | |
5e7ed085 FG |
60 | ) -> Result<Self, ErrorGuaranteed> { |
61 | let mut errored = None; | |
ea8adc8c XL |
62 | let mut item_iter = items.iter(); |
63 | ||
5099ac24 | 64 | let parse_value = |value_str| { |
5e7ed085 | 65 | OnUnimplementedFormatString::try_parse(tcx, item_def_id, value_str, span).map(Some) |
5099ac24 FG |
66 | }; |
67 | ||
ea8adc8c XL |
68 | let condition = if is_root { |
69 | None | |
70 | } else { | |
dfeec247 XL |
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 | })?; | |
5099ac24 | 92 | attr::eval_condition(cond, &tcx.sess.parse_sess, Some(tcx.features()), &mut |item| { |
5e7ed085 FG |
93 | if let Some(symbol) = item.value_str() && let Err(guar) = parse_value(symbol) { |
94 | errored = Some(guar); | |
5099ac24 FG |
95 | } |
96 | true | |
97 | }); | |
ea8adc8c XL |
98 | Some(cond.clone()) |
99 | }; | |
100 | ||
101 | let mut message = None; | |
102 | let mut label = None; | |
2c00a5a8 | 103 | let mut note = None; |
60c5eb7d | 104 | let mut enclosing_scope = None; |
ea8adc8c | 105 | let mut subcommands = vec![]; |
5099ac24 | 106 | let mut append_const_msg = None; |
60c5eb7d | 107 | |
ea8adc8c | 108 | for item in item_iter { |
3dfed10e | 109 | if item.has_name(sym::message) && message.is_none() { |
ea8adc8c | 110 | if let Some(message_) = item.value_str() { |
60c5eb7d | 111 | message = parse_value(message_)?; |
ea8adc8c XL |
112 | continue; |
113 | } | |
3dfed10e | 114 | } else if item.has_name(sym::label) && label.is_none() { |
ea8adc8c | 115 | if let Some(label_) = item.value_str() { |
60c5eb7d | 116 | label = parse_value(label_)?; |
ea8adc8c XL |
117 | continue; |
118 | } | |
3dfed10e | 119 | } else if item.has_name(sym::note) && note.is_none() { |
2c00a5a8 | 120 | if let Some(note_) = item.value_str() { |
60c5eb7d XL |
121 | note = parse_value(note_)?; |
122 | continue; | |
123 | } | |
3dfed10e | 124 | } else if item.has_name(sym::enclosing_scope) && enclosing_scope.is_none() { |
60c5eb7d XL |
125 | if let Some(enclosing_scope_) = item.value_str() { |
126 | enclosing_scope = parse_value(enclosing_scope_)?; | |
2c00a5a8 XL |
127 | continue; |
128 | } | |
3dfed10e | 129 | } else if item.has_name(sym::on) |
dfeec247 XL |
130 | && is_root |
131 | && message.is_none() | |
132 | && label.is_none() | |
133 | && note.is_none() | |
ea8adc8c XL |
134 | { |
135 | if let Some(items) = item.meta_item_list() { | |
5e7ed085 FG |
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 | }; | |
dfeec247 | 140 | continue; |
ea8adc8c | 141 | } |
5099ac24 FG |
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 | } | |
ea8adc8c XL |
150 | } |
151 | ||
152 | // nothing found | |
dfeec247 XL |
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 | ); | |
ea8adc8c XL |
160 | } |
161 | ||
5e7ed085 FG |
162 | if let Some(reported) = errored { |
163 | Err(reported) | |
ea8adc8c | 164 | } else { |
60c5eb7d XL |
165 | Ok(OnUnimplementedDirective { |
166 | condition, | |
167 | subcommands, | |
168 | message, | |
169 | label, | |
170 | note, | |
dfeec247 | 171 | enclosing_scope, |
5099ac24 | 172 | append_const_msg, |
60c5eb7d | 173 | }) |
ea8adc8c XL |
174 | } |
175 | } | |
176 | ||
5e7ed085 | 177 | pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> { |
04454e1e | 178 | let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) else { |
ea8adc8c XL |
179 | return Ok(None); |
180 | }; | |
181 | ||
182 | let result = if let Some(items) = attr.meta_item_list() { | |
5e7ed085 | 183 | Self::parse(tcx, item_def_id, &items, attr.span, true).map(Some) |
ea8adc8c XL |
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( | |
dfeec247 | 190 | tcx, |
5e7ed085 | 191 | item_def_id, |
dfeec247 XL |
192 | value, |
193 | attr.span, | |
194 | )?), | |
2c00a5a8 | 195 | note: None, |
60c5eb7d | 196 | enclosing_scope: None, |
5099ac24 | 197 | append_const_msg: None, |
ea8adc8c XL |
198 | })) |
199 | } else { | |
5e7ed085 FG |
200 | let reported = |
201 | tcx.sess.delay_span_bug(DUMMY_SP, "of_item: neither meta_item_list nor value_str"); | |
202 | return Err(reported); | |
ea8adc8c | 203 | }; |
5e7ed085 | 204 | debug!("of_item({:?}) = {:?}", item_def_id, result); |
ea8adc8c XL |
205 | result |
206 | } | |
207 | ||
dc9dc135 XL |
208 | pub fn evaluate( |
209 | &self, | |
210 | tcx: TyCtxt<'tcx>, | |
211 | trait_ref: ty::TraitRef<'tcx>, | |
212 | options: &[(Symbol, Option<String>)], | |
213 | ) -> OnUnimplementedNote { | |
ea8adc8c XL |
214 | let mut message = None; |
215 | let mut label = None; | |
2c00a5a8 | 216 | let mut note = None; |
60c5eb7d | 217 | let mut enclosing_scope = None; |
5099ac24 | 218 | let mut append_const_msg = None; |
2c00a5a8 | 219 | info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options); |
ea8adc8c | 220 | |
5099ac24 FG |
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 | ||
ea8adc8c | 224 | for command in self.subcommands.iter().chain(Some(self)).rev() { |
5e7ed085 FG |
225 | if let Some(ref condition) = command.condition && !attr::eval_condition( |
226 | condition, | |
227 | &tcx.sess.parse_sess, | |
228 | Some(tcx.features()), | |
229 | &mut |c| { | |
230 | c.ident().map_or(false, |ident| { | |
231 | let value = c.value_str().map(|s| { | |
232 | OnUnimplementedFormatString(s).format(tcx, trait_ref, &options_map) | |
233 | }); | |
5099ac24 | 234 | |
5e7ed085 FG |
235 | options.contains(&(ident.name, value)) |
236 | }) | |
237 | }, | |
238 | ) { | |
239 | debug!("evaluate: skipping {:?} due to condition", command); | |
240 | continue; | |
ea8adc8c XL |
241 | } |
242 | debug!("evaluate: {:?} succeeded", command); | |
243 | if let Some(ref message_) = command.message { | |
244 | message = Some(message_.clone()); | |
245 | } | |
246 | ||
247 | if let Some(ref label_) = command.label { | |
248 | label = Some(label_.clone()); | |
249 | } | |
2c00a5a8 XL |
250 | |
251 | if let Some(ref note_) = command.note { | |
252 | note = Some(note_.clone()); | |
253 | } | |
60c5eb7d XL |
254 | |
255 | if let Some(ref enclosing_scope_) = command.enclosing_scope { | |
256 | enclosing_scope = Some(enclosing_scope_.clone()); | |
257 | } | |
5099ac24 | 258 | |
04454e1e | 259 | append_const_msg = command.append_const_msg; |
ea8adc8c XL |
260 | } |
261 | ||
262 | OnUnimplementedNote { | |
5099ac24 FG |
263 | label: label.map(|l| l.format(tcx, trait_ref, &options_map)), |
264 | message: message.map(|m| m.format(tcx, trait_ref, &options_map)), | |
265 | note: note.map(|n| n.format(tcx, trait_ref, &options_map)), | |
266 | enclosing_scope: enclosing_scope.map(|e_s| e_s.format(tcx, trait_ref, &options_map)), | |
267 | append_const_msg, | |
ea8adc8c XL |
268 | } |
269 | } | |
270 | } | |
271 | ||
dc9dc135 XL |
272 | impl<'tcx> OnUnimplementedFormatString { |
273 | fn try_parse( | |
274 | tcx: TyCtxt<'tcx>, | |
5e7ed085 | 275 | item_def_id: DefId, |
e1599b0c | 276 | from: Symbol, |
dc9dc135 | 277 | err_sp: Span, |
5e7ed085 | 278 | ) -> Result<Self, ErrorGuaranteed> { |
ea8adc8c | 279 | let result = OnUnimplementedFormatString(from); |
5e7ed085 | 280 | result.verify(tcx, item_def_id, err_sp)?; |
ea8adc8c XL |
281 | Ok(result) |
282 | } | |
283 | ||
dc9dc135 XL |
284 | fn verify( |
285 | &self, | |
286 | tcx: TyCtxt<'tcx>, | |
5e7ed085 | 287 | item_def_id: DefId, |
dc9dc135 | 288 | span: Span, |
5e7ed085 FG |
289 | ) -> Result<(), ErrorGuaranteed> { |
290 | let trait_def_id = if tcx.is_trait(item_def_id) { | |
291 | item_def_id | |
292 | } else { | |
293 | tcx.trait_id_of_impl(item_def_id) | |
294 | .expect("expected `on_unimplemented` to correspond to a trait") | |
295 | }; | |
296 | let trait_name = tcx.item_name(trait_def_id); | |
297 | let generics = tcx.generics_of(item_def_id); | |
e1599b0c | 298 | let s = self.0.as_str(); |
a2a8927a | 299 | let parser = Parser::new(s, None, None, false, ParseMode::Format); |
ea8adc8c XL |
300 | let mut result = Ok(()); |
301 | for token in parser { | |
302 | match token { | |
303 | Piece::String(_) => (), // Normal string, no need to check it | |
304 | Piece::NextArgument(a) => match a.position { | |
5099ac24 | 305 | Position::ArgumentNamed(s, _) => { |
04454e1e FG |
306 | match Symbol::intern(s) { |
307 | // `{Self}` is allowed | |
308 | kw::SelfUpper => (), | |
309 | // `{ThisTraitsName}` is allowed | |
310 | s if s == trait_name => (), | |
311 | // `{from_method}` is allowed | |
312 | sym::from_method => (), | |
313 | // `{from_desugaring}` is allowed | |
314 | sym::from_desugaring => (), | |
315 | // `{ItemContext}` is allowed | |
316 | sym::ItemContext => (), | |
317 | // `{integral}` and `{integer}` and `{float}` are allowed | |
318 | sym::integral | sym::integer_ | sym::float => (), | |
319 | // So is `{A}` if A is a type parameter | |
320 | s => match generics.params.iter().find(|param| param.name == s) { | |
321 | Some(_) => (), | |
322 | None => { | |
323 | let reported = struct_span_err!( | |
324 | tcx.sess, | |
325 | span, | |
326 | E0230, | |
327 | "there is no parameter `{}` on {}", | |
328 | s, | |
329 | if trait_def_id == item_def_id { | |
330 | format!("trait `{}`", trait_name) | |
331 | } else { | |
332 | "impl".to_string() | |
333 | } | |
334 | ) | |
335 | .emit(); | |
336 | result = Err(reported); | |
337 | } | |
338 | }, | |
ea8adc8c | 339 | } |
dfeec247 | 340 | } |
ea8adc8c | 341 | // `{:1}` and `{}` are not to be used |
abe05a73 | 342 | Position::ArgumentIs(_) | Position::ArgumentImplicitlyIs(_) => { |
5e7ed085 | 343 | let reported = struct_span_err!( |
dfeec247 XL |
344 | tcx.sess, |
345 | span, | |
346 | E0231, | |
347 | "only named substitution parameters are allowed" | |
348 | ) | |
349 | .emit(); | |
5e7ed085 | 350 | result = Err(reported); |
ea8adc8c | 351 | } |
dfeec247 | 352 | }, |
ea8adc8c XL |
353 | } |
354 | } | |
355 | ||
356 | result | |
357 | } | |
358 | ||
dc9dc135 XL |
359 | pub fn format( |
360 | &self, | |
361 | tcx: TyCtxt<'tcx>, | |
362 | trait_ref: ty::TraitRef<'tcx>, | |
363 | options: &FxHashMap<Symbol, String>, | |
364 | ) -> String { | |
ea8adc8c | 365 | let name = tcx.item_name(trait_ref.def_id); |
532ac7d7 | 366 | let trait_str = tcx.def_path_str(trait_ref.def_id); |
ea8adc8c | 367 | let generics = tcx.generics_of(trait_ref.def_id); |
dfeec247 XL |
368 | let generic_map = generics |
369 | .params | |
370 | .iter() | |
371 | .filter_map(|param| { | |
372 | let value = match param.kind { | |
cdc7bbd5 | 373 | GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { |
dfeec247 XL |
374 | trait_ref.substs[param.index as usize].to_string() |
375 | } | |
376 | GenericParamDefKind::Lifetime => return None, | |
377 | }; | |
378 | let name = param.name; | |
379 | Some((name, value)) | |
380 | }) | |
381 | .collect::<FxHashMap<Symbol, String>>(); | |
b7449926 | 382 | let empty_string = String::new(); |
ea8adc8c | 383 | |
e1599b0c | 384 | let s = self.0.as_str(); |
a2a8927a | 385 | let parser = Parser::new(s, None, None, false, ParseMode::Format); |
3dfed10e | 386 | let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string); |
dfeec247 XL |
387 | parser |
388 | .map(|p| match p { | |
ea8adc8c XL |
389 | Piece::String(s) => s, |
390 | Piece::NextArgument(a) => match a.position { | |
04454e1e FG |
391 | Position::ArgumentNamed(s, _) => { |
392 | let s = Symbol::intern(s); | |
393 | match generic_map.get(&s) { | |
394 | Some(val) => val, | |
395 | None if s == name => &trait_str, | |
396 | None => { | |
397 | if let Some(val) = options.get(&s) { | |
398 | val | |
399 | } else if s == sym::from_desugaring || s == sym::from_method { | |
400 | // don't break messages using these two arguments incorrectly | |
401 | &empty_string | |
402 | } else if s == sym::ItemContext { | |
403 | &item_context | |
404 | } else if s == sym::integral { | |
405 | "{integral}" | |
406 | } else if s == sym::integer_ { | |
407 | "{integer}" | |
408 | } else if s == sym::float { | |
409 | "{float}" | |
410 | } else { | |
411 | bug!( | |
412 | "broken on_unimplemented {:?} for {:?}: \ | |
b7449926 | 413 | no argument matching {:?}", |
04454e1e FG |
414 | self.0, |
415 | trait_ref, | |
416 | s | |
417 | ) | |
418 | } | |
b7449926 | 419 | } |
ea8adc8c | 420 | } |
04454e1e | 421 | } |
dfeec247 XL |
422 | _ => bug!("broken on_unimplemented {:?} - bad format arg", self.0), |
423 | }, | |
424 | }) | |
425 | .collect() | |
ea8adc8c XL |
426 | } |
427 | } |