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