| // Copyright 2017 The Rust Project Developers. See the COPYRIGHT |
| // file at the top-level directory of this distribution and at |
| // http://rust-lang.org/COPYRIGHT. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| use fmt_macros::{Parser, Piece, Position}; |
| |
| use hir::def_id::DefId; |
| use ty::{self, TyCtxt, GenericParamDefKind}; |
| use util::common::ErrorReported; |
| use util::nodemap::FxHashMap; |
| |
| use syntax::ast::{MetaItem, NestedMetaItem}; |
| use syntax::attr; |
| use syntax_pos::Span; |
| use syntax_pos::symbol::LocalInternedString; |
| |
| #[derive(Clone, Debug)] |
| pub struct OnUnimplementedFormatString(LocalInternedString); |
| |
| #[derive(Debug)] |
| pub struct OnUnimplementedDirective { |
| pub condition: Option<MetaItem>, |
| pub subcommands: Vec<OnUnimplementedDirective>, |
| pub message: Option<OnUnimplementedFormatString>, |
| pub label: Option<OnUnimplementedFormatString>, |
| pub note: Option<OnUnimplementedFormatString>, |
| } |
| |
| pub struct OnUnimplementedNote { |
| pub message: Option<String>, |
| pub label: Option<String>, |
| pub note: Option<String>, |
| } |
| |
| impl OnUnimplementedNote { |
| pub fn empty() -> Self { |
| OnUnimplementedNote { message: None, label: None, note: None } |
| } |
| } |
| |
| fn parse_error(tcx: TyCtxt<'_, '_, '_>, span: Span, |
| message: &str, |
| label: &str, |
| note: Option<&str>) |
| -> ErrorReported |
| { |
| let mut diag = struct_span_err!( |
| tcx.sess, span, E0232, "{}", message); |
| diag.span_label(span, label); |
| if let Some(note) = note { |
| diag.note(note); |
| } |
| diag.emit(); |
| ErrorReported |
| } |
| |
| impl<'a, 'gcx, 'tcx> OnUnimplementedDirective { |
| pub fn parse(tcx: TyCtxt<'a, 'gcx, 'tcx>, |
| trait_def_id: DefId, |
| items: &[NestedMetaItem], |
| span: Span, |
| is_root: bool) |
| -> Result<Self, ErrorReported> |
| { |
| let mut errored = false; |
| let mut item_iter = items.iter(); |
| |
| let condition = if is_root { |
| None |
| } else { |
| let cond = item_iter.next().ok_or_else(|| |
| parse_error(tcx, span, |
| "empty `on`-clause in `#[rustc_on_unimplemented]`", |
| "empty on-clause here", |
| None) |
| )?.meta_item().ok_or_else(|| |
| parse_error(tcx, span, |
| "invalid `on`-clause in `#[rustc_on_unimplemented]`", |
| "invalid on-clause here", |
| None) |
| )?; |
| attr::eval_condition(cond, &tcx.sess.parse_sess, &mut |_| true); |
| Some(cond.clone()) |
| }; |
| |
| let mut message = None; |
| let mut label = None; |
| let mut note = None; |
| let mut subcommands = vec![]; |
| for item in item_iter { |
| if item.check_name("message") && message.is_none() { |
| if let Some(message_) = item.value_str() { |
| message = Some(OnUnimplementedFormatString::try_parse( |
| tcx, trait_def_id, message_.as_str(), span)?); |
| continue; |
| } |
| } else if item.check_name("label") && label.is_none() { |
| if let Some(label_) = item.value_str() { |
| label = Some(OnUnimplementedFormatString::try_parse( |
| tcx, trait_def_id, label_.as_str(), span)?); |
| continue; |
| } |
| } else if item.check_name("note") && note.is_none() { |
| if let Some(note_) = item.value_str() { |
| note = Some(OnUnimplementedFormatString::try_parse( |
| tcx, trait_def_id, note_.as_str(), span)?); |
| continue; |
| } |
| } else if item.check_name("on") && is_root && |
| message.is_none() && label.is_none() && note.is_none() |
| { |
| if let Some(items) = item.meta_item_list() { |
| if let Ok(subcommand) = |
| Self::parse(tcx, trait_def_id, &items, item.span, false) |
| { |
| subcommands.push(subcommand); |
| } else { |
| errored = true; |
| } |
| continue |
| } |
| } |
| |
| // nothing found |
| parse_error(tcx, item.span, |
| "this attribute must have a valid value", |
| "expected value here", |
| Some(r#"eg `#[rustc_on_unimplemented(message="foo")]`"#)); |
| } |
| |
| if errored { |
| Err(ErrorReported) |
| } else { |
| Ok(OnUnimplementedDirective { condition, message, label, subcommands, note }) |
| } |
| } |
| |
| |
| pub fn of_item(tcx: TyCtxt<'a, 'gcx, 'tcx>, |
| trait_def_id: DefId, |
| impl_def_id: DefId) |
| -> Result<Option<Self>, ErrorReported> |
| { |
| let attrs = tcx.get_attrs(impl_def_id); |
| |
| let attr = if let Some(item) = attr::find_by_name(&attrs, "rustc_on_unimplemented") { |
| item |
| } else { |
| return Ok(None); |
| }; |
| |
| let result = if let Some(items) = attr.meta_item_list() { |
| Self::parse(tcx, trait_def_id, &items, attr.span, true).map(Some) |
| } else if let Some(value) = attr.value_str() { |
| Ok(Some(OnUnimplementedDirective { |
| condition: None, |
| message: None, |
| subcommands: vec![], |
| label: Some(OnUnimplementedFormatString::try_parse( |
| tcx, trait_def_id, value.as_str(), attr.span)?), |
| note: None, |
| })) |
| } else { |
| return Err(parse_error(tcx, attr.span, |
| "`#[rustc_on_unimplemented]` requires a value", |
| "value required here", |
| Some(r#"eg `#[rustc_on_unimplemented(message="foo")]`"#))); |
| }; |
| debug!("of_item({:?}/{:?}) = {:?}", trait_def_id, impl_def_id, result); |
| result |
| } |
| |
| pub fn evaluate(&self, |
| tcx: TyCtxt<'a, 'gcx, 'tcx>, |
| trait_ref: ty::TraitRef<'tcx>, |
| options: &[(String, Option<String>)]) |
| -> OnUnimplementedNote |
| { |
| let mut message = None; |
| let mut label = None; |
| let mut note = None; |
| info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options); |
| |
| for command in self.subcommands.iter().chain(Some(self)).rev() { |
| if let Some(ref condition) = command.condition { |
| if !attr::eval_condition(condition, &tcx.sess.parse_sess, &mut |c| { |
| options.contains(&( |
| c.name().as_str().to_string(), |
| c.value_str().map(|s| s.as_str().to_string()) |
| )) |
| }) { |
| debug!("evaluate: skipping {:?} due to condition", command); |
| continue |
| } |
| } |
| debug!("evaluate: {:?} succeeded", command); |
| if let Some(ref message_) = command.message { |
| message = Some(message_.clone()); |
| } |
| |
| if let Some(ref label_) = command.label { |
| label = Some(label_.clone()); |
| } |
| |
| if let Some(ref note_) = command.note { |
| note = Some(note_.clone()); |
| } |
| } |
| |
| let options: FxHashMap<String, String> = options.into_iter() |
| .filter_map(|(k, v)| v.as_ref().map(|v| (k.to_owned(), v.to_owned()))) |
| .collect(); |
| OnUnimplementedNote { |
| label: label.map(|l| l.format(tcx, trait_ref, &options)), |
| message: message.map(|m| m.format(tcx, trait_ref, &options)), |
| note: note.map(|n| n.format(tcx, trait_ref, &options)), |
| } |
| } |
| } |
| |
| impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString { |
| pub fn try_parse(tcx: TyCtxt<'a, 'gcx, 'tcx>, |
| trait_def_id: DefId, |
| from: LocalInternedString, |
| err_sp: Span) |
| -> Result<Self, ErrorReported> |
| { |
| let result = OnUnimplementedFormatString(from); |
| result.verify(tcx, trait_def_id, err_sp)?; |
| Ok(result) |
| } |
| |
| fn verify(&self, |
| tcx: TyCtxt<'a, 'gcx, 'tcx>, |
| trait_def_id: DefId, |
| span: Span) |
| -> Result<(), ErrorReported> |
| { |
| let name = tcx.item_name(trait_def_id); |
| let generics = tcx.generics_of(trait_def_id); |
| let parser = Parser::new(&self.0, None); |
| let mut result = Ok(()); |
| for token in parser { |
| match token { |
| Piece::String(_) => (), // Normal string, no need to check it |
| Piece::NextArgument(a) => match a.position { |
| // `{Self}` is allowed |
| Position::ArgumentNamed(s) if s == "Self" => (), |
| // `{ThisTraitsName}` is allowed |
| Position::ArgumentNamed(s) if s == name => (), |
| // `{from_method}` is allowed |
| Position::ArgumentNamed(s) if s == "from_method" => (), |
| // `{from_desugaring}` is allowed |
| Position::ArgumentNamed(s) if s == "from_desugaring" => (), |
| // So is `{A}` if A is a type parameter |
| Position::ArgumentNamed(s) => match generics.params.iter().find(|param| |
| param.name == s |
| ) { |
| Some(_) => (), |
| None => { |
| span_err!(tcx.sess, span, E0230, |
| "there is no parameter `{}` on trait `{}`", s, name); |
| result = Err(ErrorReported); |
| } |
| }, |
| // `{:1}` and `{}` are not to be used |
| Position::ArgumentIs(_) | Position::ArgumentImplicitlyIs(_) => { |
| span_err!(tcx.sess, span, E0231, |
| "only named substitution parameters are allowed"); |
| result = Err(ErrorReported); |
| } |
| } |
| } |
| } |
| |
| result |
| } |
| |
| pub fn format(&self, |
| tcx: TyCtxt<'a, 'gcx, 'tcx>, |
| trait_ref: ty::TraitRef<'tcx>, |
| options: &FxHashMap<String, String>) |
| -> String |
| { |
| let name = tcx.item_name(trait_ref.def_id); |
| let trait_str = tcx.item_path_str(trait_ref.def_id); |
| let generics = tcx.generics_of(trait_ref.def_id); |
| let generic_map = generics.params.iter().filter_map(|param| { |
| let value = match param.kind { |
| GenericParamDefKind::Type {..} => { |
| trait_ref.substs[param.index as usize].to_string() |
| }, |
| GenericParamDefKind::Lifetime => return None |
| }; |
| let name = param.name.to_string(); |
| Some((name, value)) |
| }).collect::<FxHashMap<String, String>>(); |
| let empty_string = String::new(); |
| |
| let parser = Parser::new(&self.0, None); |
| parser.map(|p| |
| match p { |
| Piece::String(s) => s, |
| Piece::NextArgument(a) => match a.position { |
| Position::ArgumentNamed(s) => match generic_map.get(s) { |
| Some(val) => val, |
| None if s == name => { |
| &trait_str |
| } |
| None => { |
| if let Some(val) = options.get(s) { |
| val |
| } else if s == "from_desugaring" || s == "from_method" { |
| // don't break messages using these two arguments incorrectly |
| &empty_string |
| } else { |
| bug!("broken on_unimplemented {:?} for {:?}: \ |
| no argument matching {:?}", |
| self.0, trait_ref, s) |
| } |
| } |
| }, |
| _ => bug!("broken on_unimplemented {:?} - bad format arg", self.0) |
| } |
| } |
| ).collect() |
| } |
| } |