Skip to content

Commit

Permalink
implement improved on_unimplemented directives
Browse files Browse the repository at this point in the history
  • Loading branch information
arielb1 committed Sep 3, 2017
1 parent cf07ebd commit 6866aea
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 50 deletions.
6 changes: 3 additions & 3 deletions src/librustc/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2012,9 +2012,9 @@ register_diagnostics! {
// E0102, // replaced with E0282
// E0134,
// E0135,
// E0271, // on_unimplemented #0
// E0272, // on_unimplemented #1
// E0273, // on_unimplemented #2
// E0272, // on_unimplemented #0
// E0273, // on_unimplemented #1
// E0274, // on_unimplemented #2
E0278, // requirement is not satisfied
E0279, // requirement is not satisfied
E0280, // requirement is not satisfied
Expand Down
43 changes: 27 additions & 16 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use super::{
Obligation,
ObligationCause,
ObligationCauseCode,
OnUnimplementedInfo,
OnUnimplementedDirective,
OnUnimplementedNote,
OutputTypeParameterMismatch,
TraitNotObjectSafe,
PredicateObligation,
Expand Down Expand Up @@ -316,18 +317,22 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
}
}

fn on_unimplemented_note(&self,
trait_ref: ty::PolyTraitRef<'tcx>,
obligation: &PredicateObligation<'tcx>) -> Option<String> {
fn on_unimplemented_note(
&self,
trait_ref: ty::PolyTraitRef<'tcx>,
obligation: &PredicateObligation<'tcx>) ->
OnUnimplementedNote
{
let def_id = self.impl_similar_to(trait_ref, obligation)
.unwrap_or(trait_ref.def_id());
let trait_ref = trait_ref.skip_binder();
let trait_ref = *trait_ref.skip_binder();

match OnUnimplementedInfo::of_item(
self.tcx, trait_ref.def_id, def_id, obligation.cause.span
if let Ok(Some(command)) = OnUnimplementedDirective::of_item(
self.tcx, trait_ref.def_id, def_id
) {
Ok(Some(info)) => Some(info.label.format(self.tcx, *trait_ref)),
_ => None
command.evaluate(self.tcx, trait_ref, &[])
} else {
OnUnimplementedNote::empty()
}
}

Expand Down Expand Up @@ -519,17 +524,23 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
let (post_message, pre_message) =
self.get_parent_trait_ref(&obligation.cause.code)
.map(|t| (format!(" in `{}`", t), format!("within `{}`, ", t)))
.unwrap_or((String::new(), String::new()));
.unwrap_or((String::new(), String::new()));

let OnUnimplementedNote { message, label }
= self.on_unimplemented_note(trait_ref, obligation);
let have_alt_message = message.is_some() || label.is_some();

let mut err = struct_span_err!(
self.tcx.sess,
span,
E0277,
"the trait bound `{}` is not satisfied{}",
trait_ref.to_predicate(),
post_message);
"{}",
message.unwrap_or_else(|| {
format!("the trait bound `{}` is not satisfied{}",
trait_ref.to_predicate(), post_message)
}));

let unimplemented_note = self.on_unimplemented_note(trait_ref, obligation);
if let Some(ref s) = unimplemented_note {
if let Some(ref s) = label {
// If it has a custom "#[rustc_on_unimplemented]"
// error message, let's display it as the label!
err.span_label(span, s.as_str());
Expand Down Expand Up @@ -557,7 +568,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
// which is somewhat confusing.
err.help(&format!("consider adding a `where {}` bound",
trait_ref.to_predicate()));
} else if unimplemented_note.is_none() {
} else if !have_alt_message {
// Can't show anything else useful, try to find similar impls.
let impl_candidates = self.find_similar_impl_candidates(trait_ref);
self.report_similar_impl_candidates(impl_candidates, &mut err);
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub use self::project::{normalize, normalize_projection_type, Normalized};
pub use self::project::{ProjectionCache, ProjectionCacheSnapshot, Reveal};
pub use self::object_safety::ObjectSafetyViolation;
pub use self::object_safety::MethodViolationCode;
pub use self::on_unimplemented::OnUnimplementedInfo;
pub use self::on_unimplemented::{OnUnimplementedDirective, OnUnimplementedNote};
pub use self::select::{EvaluationCache, SelectionContext, SelectionCache};
pub use self::specialize::{OverlapError, specialization_graph, translate_substs};
pub use self::specialize::{SpecializesCache, find_associated_item};
Expand Down
174 changes: 157 additions & 17 deletions src/librustc/traits/on_unimplemented.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,127 @@ use ty::{self, TyCtxt};
use util::common::ErrorReported;
use util::nodemap::FxHashMap;

use syntax::ast::{MetaItem, NestedMetaItem};
use syntax::attr;
use syntax_pos::Span;
use syntax_pos::symbol::InternedString;

#[derive(Clone, Debug)]
pub struct OnUnimplementedFormatString(InternedString);
pub struct OnUnimplementedInfo {
pub label: OnUnimplementedFormatString

#[derive(Debug)]
pub struct OnUnimplementedDirective {
pub condition: Option<MetaItem>,
pub subcommands: Vec<OnUnimplementedDirective>,
pub message: Option<OnUnimplementedFormatString>,
pub label: Option<OnUnimplementedFormatString>,
}

pub struct OnUnimplementedNote {
pub message: Option<String>,
pub label: Option<String>,
}

impl OnUnimplementedNote {
pub fn empty() -> Self {
OnUnimplementedNote { message: None, label: None }
}
}

impl<'a, 'gcx, 'tcx> OnUnimplementedInfo {
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 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("on") && is_root &&
message.is_none() && label.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 = "foo"]`"#));
}

if errored {
Err(ErrorReported)
} else {
Ok(OnUnimplementedDirective { condition, message, label, subcommands })
}
}


pub fn of_item(tcx: TyCtxt<'a, 'gcx, 'tcx>,
trait_def_id: DefId,
impl_def_id: DefId,
span: Span)
impl_def_id: DefId)
-> Result<Option<Self>, ErrorReported>
{
let attrs = tcx.get_attrs(impl_def_id);
Expand All @@ -40,20 +148,52 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedInfo {
return Ok(None);
};

let span = attr.span.substitute_dummy(span);
if let Some(label) = attr.value_str() {
Ok(Some(OnUnimplementedInfo {
label: OnUnimplementedFormatString::try_parse(
tcx, trait_def_id, label.as_str(), span)?
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)?)
}))
} else {
struct_span_err!(
tcx.sess, span, E0232,
"this attribute must have a value")
.span_label(attr.span, "attribute requires a value")
.note(&format!("eg `#[rustc_on_unimplemented = \"foo\"]`"))
.emit();
Err(ErrorReported)
return Err(parse_error(tcx, attr.span,
"`#[rustc_on_unimplemented]` requires a value",
"value required here",
Some(r#"eg `#[rustc_on_unimplemented = "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: &[&str])
-> OnUnimplementedNote
{
let mut message = None;
let mut label = None;

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.iter().any(|o| c.check_name(o))
}) {
debug!("evaluate: skipping {:?} due to condition", command);
continue
}
}
debug!("evaluate: {:?} succeeded", command);
message = command.message.clone();
label = command.label.clone();
}

OnUnimplementedNote {
label: label.map(|l| l.format(tcx, trait_ref)),
message: message.map(|m| m.format(tcx, trait_ref))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1218,8 +1218,8 @@ fn check_on_unimplemented<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
item: &hir::Item) {
let item_def_id = tcx.hir.local_def_id(item.id);
// an error would be reported if this fails.
let _ = traits::OnUnimplementedInfo::of_item(
tcx, trait_def_id, item_def_id, item.span);
let _ = traits::OnUnimplementedDirective::of_item(
tcx, trait_def_id, item_def_id);
}

fn report_forbidden_specialization<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
Expand Down
25 changes: 18 additions & 7 deletions src/libsyntax/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,20 @@ pub fn requests_inline(attrs: &[Attribute]) -> bool {

/// Tests if a cfg-pattern matches the cfg set
pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Features>) -> bool {
eval_condition(cfg, sess, &mut |cfg| {
if let (Some(feats), Some(gated_cfg)) = (features, GatedCfg::gate(cfg)) {
gated_cfg.check_and_emit(sess, feats);
}
sess.config.contains(&(cfg.name(), cfg.value_str()))
})
}

/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
/// evaluate individual items.
pub fn eval_condition<F>(cfg: &ast::MetaItem, sess: &ParseSess, eval: &mut F)
-> bool
where F: FnMut(&ast::MetaItem) -> bool
{
match cfg.node {
ast::MetaItemKind::List(ref mis) => {
for mi in mis.iter() {
Expand All @@ -598,18 +612,18 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
// that they won't fail with the loop above.
match &*cfg.name.as_str() {
"any" => mis.iter().any(|mi| {
cfg_matches(mi.meta_item().unwrap(), sess, features)
eval_condition(mi.meta_item().unwrap(), sess, eval)
}),
"all" => mis.iter().all(|mi| {
cfg_matches(mi.meta_item().unwrap(), sess, features)
eval_condition(mi.meta_item().unwrap(), sess, eval)
}),
"not" => {
if mis.len() != 1 {
span_err!(sess.span_diagnostic, cfg.span, E0536, "expected 1 cfg-pattern");
return false;
}

!cfg_matches(mis[0].meta_item().unwrap(), sess, features)
!eval_condition(mis[0].meta_item().unwrap(), sess, eval)
},
p => {
span_err!(sess.span_diagnostic, cfg.span, E0537, "invalid predicate `{}`", p);
Expand All @@ -618,10 +632,7 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
}
},
ast::MetaItemKind::Word | ast::MetaItemKind::NameValue(..) => {
if let (Some(feats), Some(gated_cfg)) = (features, GatedCfg::gate(cfg)) {
gated_cfg.check_and_emit(sess, feats);
}
sess.config.contains(&(cfg.name(), cfg.value_str()))
eval(cfg)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/compile-fail/E0232.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

#[rustc_on_unimplemented]
//~^ ERROR E0232
//~| NOTE attribute requires a value
//~| NOTE value required here
//~| NOTE eg `#[rustc_on_unimplemented = "foo"]`
trait Bar {}

Expand Down
Loading

0 comments on commit 6866aea

Please sign in to comment.