implement improved on_unimplemented directives

This commit is contained in:
Ariel Ben-Yehuda 2017-08-30 23:40:43 +03:00
parent cf07ebd2a2
commit 6866aea5af
9 changed files with 290 additions and 50 deletions

View file

@ -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

View file

@ -15,7 +15,8 @@ use super::{
Obligation,
ObligationCause,
ObligationCauseCode,
OnUnimplementedInfo,
OnUnimplementedDirective,
OnUnimplementedNote,
OutputTypeParameterMismatch,
TraitNotObjectSafe,
PredicateObligation,
@ -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()
}
}
@ -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());
@ -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);

View file

@ -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};

View file

@ -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>,
}
impl<'a, 'gcx, 'tcx> OnUnimplementedInfo {
pub struct OnUnimplementedNote {
pub message: Option<String>,
pub label: Option<String>,
}
impl OnUnimplementedNote {
pub fn empty() -> Self {
OnUnimplementedNote { message: None, label: 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 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);
@ -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))
}
}
}

View file

@ -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>,

View file

@ -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() {
@ -598,10 +612,10 @@ 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 {
@ -609,7 +623,7 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
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);
@ -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)
}
}
}

View file

@ -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 {}

View file

@ -37,5 +37,29 @@ trait BadAnnotation2<A,B>
trait BadAnnotation3<A,B>
{}
#[rustc_on_unimplemented(lorem="")]
trait BadAnnotation4 {}
#[rustc_on_unimplemented(lorem(ipsum(dolor)))]
trait BadAnnotation5 {}
#[rustc_on_unimplemented(message="x", message="y")]
trait BadAnnotation6 {}
#[rustc_on_unimplemented(message="x", on(desugared, message="y"))]
trait BadAnnotation7 {}
#[rustc_on_unimplemented(on(), message="y")]
trait BadAnnotation8 {}
#[rustc_on_unimplemented(on="x", message="y")]
trait BadAnnotation9 {}
#[rustc_on_unimplemented(on(x="y"), message="y")]
trait BadAnnotation10 {}
#[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message="y")]
trait BadAnnotation11 {}
pub fn main() {
}

View file

@ -1,8 +1,8 @@
error[E0232]: this attribute must have a value
error[E0232]: `#[rustc_on_unimplemented]` requires a value
--> $DIR/bad-annotation.rs:26:1
|
26 | #[rustc_on_unimplemented] //~ ERROR this attribute must have a value
| ^^^^^^^^^^^^^^^^^^^^^^^^^ attribute requires a value
| ^^^^^^^^^^^^^^^^^^^^^^^^^ value required here
|
= note: eg `#[rustc_on_unimplemented = "foo"]`
@ -18,5 +18,59 @@ error[E0231]: only named substitution parameters are allowed
35 | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 3 previous errors
error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:40:26
|
40 | #[rustc_on_unimplemented(lorem="")]
| ^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented = "foo"]`
error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:43:26
|
43 | #[rustc_on_unimplemented(lorem(ipsum(dolor)))]
| ^^^^^^^^^^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented = "foo"]`
error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:46:39
|
46 | #[rustc_on_unimplemented(message="x", message="y")]
| ^^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented = "foo"]`
error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:49:39
|
49 | #[rustc_on_unimplemented(message="x", on(desugared, message="y"))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented = "foo"]`
error[E0232]: empty `on`-clause in `#[rustc_on_unimplemented]`
--> $DIR/bad-annotation.rs:52:26
|
52 | #[rustc_on_unimplemented(on(), message="y")]
| ^^^^ empty on-clause here
error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:55:26
|
55 | #[rustc_on_unimplemented(on="x", message="y")]
| ^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented = "foo"]`
error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:61:40
|
61 | #[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message="y")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented = "foo"]`
error: aborting due to 10 previous errors