Skip to content

Refactor rustc_on_unimplemented's filter parser #140307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 5, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock
Original file line number Diff line number Diff line change
@@ -4520,7 +4520,6 @@ dependencies = [
"itertools",
"rustc_abi",
"rustc_ast",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",
"rustc_fluent_macro",
1 change: 0 additions & 1 deletion compiler/rustc_trait_selection/Cargo.toml
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ edition = "2024"
itertools = "0.12"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
rustc_attr_parsing = { path = "../rustc_attr_parsing" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
26 changes: 16 additions & 10 deletions compiler/rustc_trait_selection/messages.ftl
Original file line number Diff line number Diff line change
@@ -148,9 +148,6 @@ trait_selection_dtcs_has_req_note = the used `impl` has a `'static` requirement
trait_selection_dtcs_introduces_requirement = calling this method introduces the `impl`'s `'static` requirement
trait_selection_dtcs_suggestion = consider relaxing the implicit `'static` requirement

trait_selection_empty_on_clause_in_rustc_on_unimplemented = empty `on`-clause in `#[rustc_on_unimplemented]`
.label = empty on-clause here

trait_selection_explicit_lifetime_required_sugg_with_ident = add explicit lifetime `{$named}` to the type of `{$simple_ident}`

trait_selection_explicit_lifetime_required_sugg_with_param_type = add explicit lifetime `{$named}` to type
@@ -187,9 +184,6 @@ trait_selection_inherent_projection_normalization_overflow = overflow evaluating
trait_selection_invalid_format_specifier = invalid format specifier
.help = no format specifier are supported in this position

trait_selection_invalid_on_clause_in_rustc_on_unimplemented = invalid `on`-clause in `#[rustc_on_unimplemented]`
.label = invalid on-clause here

trait_selection_label_bad = {$bad_kind ->
*[other] cannot infer type
[more_info] cannot infer {$prefix_kind ->
@@ -237,10 +231,6 @@ trait_selection_negative_positive_conflict = found both positive and negative im
.positive_implementation_here = positive implementation here
.positive_implementation_in_crate = positive implementation in crate `{$positive_impl_cname}`

trait_selection_no_value_in_rustc_on_unimplemented = this attribute must have a valid value
.label = expected value here
.note = eg `#[rustc_on_unimplemented(message="foo")]`

trait_selection_nothing = {""}

trait_selection_oc_cant_coerce_force_inline =
@@ -339,6 +329,22 @@ trait_selection_ril_introduced_by = requirement introduced by this return type
trait_selection_ril_introduced_here = `'static` requirement introduced here
trait_selection_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type

trait_selection_rustc_on_unimplemented_empty_on_clause = empty `on`-clause in `#[rustc_on_unimplemented]`
.label = empty `on`-clause here
trait_selection_rustc_on_unimplemented_expected_identifier = expected an identifier inside this `on`-clause
.label = expected an identifier here, not `{$path}`
trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not = expected a single predicate in `not(..)`
.label = unexpected quantity of predicates here
trait_selection_rustc_on_unimplemented_invalid_flag = invalid flag in `on`-clause
.label = expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `{$invalid_flag}`
trait_selection_rustc_on_unimplemented_invalid_predicate = this predicate is invalid
.label = expected one of `any`, `all` or `not` here, not `{$invalid_pred}`
trait_selection_rustc_on_unimplemented_missing_value = this attribute must have a value
.label = expected value here
.note = e.g. `#[rustc_on_unimplemented(message="foo")]`
trait_selection_rustc_on_unimplemented_unsupported_literal_in_on = literals inside `on`-clauses are not supported
.label = unexpected literal here

trait_selection_source_kind_closure_return =
try giving this closure an explicit return type

Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ use std::path::PathBuf;
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_errors::codes::*;
use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{AttrArgs, Attribute};
use rustc_macros::LintDiagnostic;
@@ -13,17 +14,16 @@ use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKin
use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
use rustc_span::{Span, Symbol, sym};
use tracing::{debug, info};
use {rustc_attr_parsing as attr, rustc_hir as hir};

use super::{ObligationCauseCode, PredicateObligation};
use crate::error_reporting::TypeErrCtxt;
use crate::error_reporting::traits::on_unimplemented_condition::{Condition, ConditionOptions};
use crate::error_reporting::traits::on_unimplemented_condition::{
ConditionOptions, OnUnimplementedCondition,
};
use crate::error_reporting::traits::on_unimplemented_format::{
Ctx, FormatArgs, FormatString, FormatWarning,
};
use crate::errors::{
EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented,
};
use crate::errors::{InvalidOnClause, NoValueInOnUnimplemented};
use crate::infer::InferCtxtExt;

impl<'tcx> TypeErrCtxt<'_, 'tcx> {
@@ -306,21 +306,21 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
#[derive(Clone, Debug)]
pub struct OnUnimplementedFormatString {
/// Symbol of the format string, i.e. `"content"`
pub symbol: Symbol,
symbol: Symbol,
///The span of the format string, i.e. `"content"`
pub span: Span,
pub is_diagnostic_namespace_variant: bool,
span: Span,
is_diagnostic_namespace_variant: bool,
}

#[derive(Debug)]
pub struct OnUnimplementedDirective {
pub condition: Option<Condition>,
pub subcommands: Vec<OnUnimplementedDirective>,
pub message: Option<(Span, OnUnimplementedFormatString)>,
pub label: Option<(Span, OnUnimplementedFormatString)>,
pub notes: Vec<OnUnimplementedFormatString>,
pub parent_label: Option<OnUnimplementedFormatString>,
pub append_const_msg: Option<AppendConstMessage>,
condition: Option<OnUnimplementedCondition>,
subcommands: Vec<OnUnimplementedDirective>,
message: Option<(Span, OnUnimplementedFormatString)>,
label: Option<(Span, OnUnimplementedFormatString)>,
notes: Vec<OnUnimplementedFormatString>,
parent_label: Option<OnUnimplementedFormatString>,
append_const_msg: Option<AppendConstMessage>,
}

/// For the `#[rustc_on_unimplemented]` attribute
@@ -427,18 +427,12 @@ impl<'tcx> OnUnimplementedDirective {
} else {
let cond = item_iter
.next()
.ok_or_else(|| tcx.dcx().emit_err(EmptyOnClauseInOnUnimplemented { span }))?
.meta_item_or_bool()
.ok_or_else(|| tcx.dcx().emit_err(InvalidOnClauseInOnUnimplemented { span }))?;
attr::eval_condition(cond, &tcx.sess, Some(tcx.features()), &mut |cfg| {
if let Some(value) = cfg.value
&& let Err(guar) = parse_value(value, cfg.span)
{
errored = Some(guar);
}
true
});
Some(Condition { inner: cond.clone() })
.ok_or_else(|| tcx.dcx().emit_err(InvalidOnClause::Empty { span }))?;

match OnUnimplementedCondition::parse(cond) {
Ok(condition) => Some(condition),
Err(e) => return Err(tcx.dcx().emit_err(e)),
}
};

let mut message = None;
@@ -724,7 +718,7 @@ impl<'tcx> OnUnimplementedDirective {
result
}

pub fn evaluate(
pub(crate) fn evaluate(
&self,
tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
@@ -744,7 +738,7 @@ impl<'tcx> OnUnimplementedDirective {
for command in self.subcommands.iter().chain(Some(self)).rev() {
debug!(?command);
if let Some(ref condition) = command.condition
&& !condition.matches_predicate(tcx, condition_options)
&& !condition.matches_predicate(condition_options)
{
debug!("evaluate: skipping {:?} due to condition", command);
continue;
Original file line number Diff line number Diff line change
@@ -1,52 +1,251 @@
use rustc_ast::MetaItemInner;
use rustc_attr_parsing as attr;
use rustc_middle::ty::{self, TyCtxt};
use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym};

/// A predicate in an attribute using on, all, any,
/// similar to a cfg predicate.
use crate::errors::InvalidOnClause;

/// Represents the `on` filter in `#[rustc_on_unimplemented]`.
#[derive(Debug)]
pub struct Condition {
pub inner: MetaItemInner,
pub(crate) struct OnUnimplementedCondition {
span: Span,
pred: Predicate,
}

impl Condition {
pub fn span(&self) -> Span {
self.inner.span()
impl OnUnimplementedCondition {
pub(crate) fn span(&self) -> Span {
self.span
}

pub(crate) fn matches_predicate(&self, options: &ConditionOptions) -> bool {
self.pred.eval(&mut |p| match p {
FlagOrNv::Flag(b) => options.has_flag(*b),
FlagOrNv::NameValue(NameValue { name, value }) => {
let value = value.format(&options.generic_args);
options.contains(*name, value)
}
})
}

pub(crate) fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
let span = input.span();
let pred = Predicate::parse(input)?;
Ok(OnUnimplementedCondition { span, pred })
}
}

pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool {
attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| {
let value = cfg.value.map(|v| {
// `with_no_visible_paths` is also used when generating the options,
// so we need to match it here.
ty::print::with_no_visible_paths!({
Parser::new(v.as_str(), None, None, false, ParseMode::Format)
.map(|p| match p {
Piece::Lit(s) => s.to_owned(),
Piece::NextArgument(a) => match a.position {
Position::ArgumentNamed(arg) => {
let s = Symbol::intern(arg);
match options.generic_args.iter().find(|(k, _)| *k == s) {
Some((_, val)) => val.to_string(),
None => format!("{{{arg}}}"),
}
}
Position::ArgumentImplicitlyIs(_) => String::from("{}"),
Position::ArgumentIs(idx) => format!("{{{idx}}}"),
},
})
.collect()
})
/// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`OnUnimplementedCondition`].
///
/// It is similar to the predicate in the `cfg` attribute,
/// and may contain nested predicates.
#[derive(Debug)]
enum Predicate {
/// A condition like `on(crate_local)`.
Flag(Flag),
/// A match, like `on(Rhs = "Whatever")`.
Match(NameValue),
/// Negation, like `on(not($pred))`.
Not(Box<Predicate>),
/// True if all predicates are true, like `on(all($a, $b, $c))`.
All(Vec<Predicate>),
/// True if any predicate is true, like `on(any($a, $b, $c))`.
Any(Vec<Predicate>),
}

impl Predicate {
fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
let meta_item = match input {
MetaItemInner::MetaItem(meta_item) => meta_item,
MetaItemInner::Lit(lit) => {
return Err(InvalidOnClause::UnsupportedLiteral { span: lit.span });
}
};

let Some(predicate) = meta_item.ident() else {
return Err(InvalidOnClause::ExpectedIdentifier {
span: meta_item.path.span,
path: meta_item.path.clone(),
});
};

options.contains(cfg.name, &value)
})
match meta_item.kind {
MetaItemKind::List(ref mis) => match predicate.name {
sym::any => Ok(Predicate::Any(Predicate::parse_sequence(mis)?)),
sym::all => Ok(Predicate::All(Predicate::parse_sequence(mis)?)),
sym::not => match &**mis {
[one] => Ok(Predicate::Not(Box::new(Predicate::parse(one)?))),
[first, .., last] => Err(InvalidOnClause::ExpectedOnePredInNot {
span: first.span().to(last.span()),
}),
[] => Err(InvalidOnClause::ExpectedOnePredInNot { span: meta_item.span }),
},
invalid_pred => {
Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred })
}
},
MetaItemKind::NameValue(MetaItemLit { symbol, .. }) => {
let name = Name::parse(predicate);
let value = FilterFormatString::parse(symbol);
let kv = NameValue { name, value };
Ok(Predicate::Match(kv))
}
MetaItemKind::Word => {
let flag = Flag::parse(predicate)?;
Ok(Predicate::Flag(flag))
}
}
}

fn parse_sequence(sequence: &[MetaItemInner]) -> Result<Vec<Self>, InvalidOnClause> {
sequence.iter().map(Predicate::parse).collect()
}

fn eval(&self, eval: &mut impl FnMut(FlagOrNv<'_>) -> bool) -> bool {
match self {
Predicate::Flag(flag) => eval(FlagOrNv::Flag(flag)),
Predicate::Match(nv) => eval(FlagOrNv::NameValue(nv)),
Predicate::Not(not) => !not.eval(eval),
Predicate::All(preds) => preds.into_iter().all(|pred| pred.eval(eval)),
Predicate::Any(preds) => preds.into_iter().any(|pred| pred.eval(eval)),
}
}
}

/// Used with `Condition::matches_predicate` to test whether the condition applies
/// Represents a `MetaWord` in an `on`-filter.
#[derive(Debug, Clone, Copy)]
enum Flag {
/// Whether the code causing the trait bound to not be fulfilled
/// is part of the user's crate.
CrateLocal,
/// Whether the obligation is user-specified rather than derived.
Direct,
/// Whether we are in some kind of desugaring like
/// `?` or `try { .. }`.
FromDesugaring,
}

impl Flag {
fn parse(Ident { name, span }: Ident) -> Result<Self, InvalidOnClause> {
match name {
sym::crate_local => Ok(Flag::CrateLocal),
sym::direct => Ok(Flag::Direct),
sym::from_desugaring => Ok(Flag::FromDesugaring),
invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }),
}
}
}

/// A `MetaNameValueStr` in an `on`-filter.
///
/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`.
#[derive(Debug, Clone)]
struct NameValue {
name: Name,
/// Something like `"&str"` or `"alloc::string::String"`,
/// in which case it just contains a single string piece.
/// But if it is something like `"&[{A}]"` then it must be formatted later.
value: FilterFormatString,
}

/// The valid names of the `on` filter.
#[derive(Debug, Clone, Copy)]
enum Name {
Cause,
FromDesugaring,
SelfUpper,
GenericArg(Symbol),
}

impl Name {
fn parse(Ident { name, .. }: Ident) -> Self {
match name {
sym::_Self | kw::SelfUpper => Name::SelfUpper,
sym::from_desugaring => Name::FromDesugaring,
sym::cause => Name::Cause,
// FIXME(mejrs) Perhaps we should start checking that
// this actually is a valid generic parameter?
generic => Name::GenericArg(generic),
}
}
}

#[derive(Debug, Clone)]
enum FlagOrNv<'p> {
Flag(&'p Flag),
NameValue(&'p NameValue),
}

/// Represents a value inside an `on` filter.
///
/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`.
/// If it is a simple literal like this then `pieces` will be `[LitOrArg::Lit("value")]`.
/// The `Arg` variant is used when it contains formatting like
/// `#[rustc_on_unimplemented(on(Self = "&[{A}]", message = "hello"))]`.
#[derive(Debug, Clone)]
struct FilterFormatString {
pieces: Vec<LitOrArg>,
}

#[derive(Debug, Clone)]
enum LitOrArg {
Lit(String),
Arg(String),
}

impl FilterFormatString {
fn parse(input: Symbol) -> Self {
let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Format)
.map(|p| match p {
Piece::Lit(s) => LitOrArg::Lit(s.to_owned()),
// We just ignore formatspecs here
Piece::NextArgument(a) => match a.position {
// In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even
// if the integer type has been resolved, to allow targeting all integers.
// `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet,
// from the `Display` impl of `InferTy` to be precise.
//
// Don't try to format these later!
Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => {
LitOrArg::Lit(format!("{{{arg}}}"))
}

// FIXME(mejrs) We should check if these correspond to a generic of the trait.
Position::ArgumentNamed(arg) => LitOrArg::Arg(arg.to_owned()),

// FIXME(mejrs) These should really be warnings/errors
Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(String::from("{}")),
Position::ArgumentIs(idx) => LitOrArg::Lit(format!("{{{idx}}}")),
},
})
.collect();
Self { pieces }
}

fn format(&self, generic_args: &[(Symbol, String)]) -> String {
let mut ret = String::new();

for piece in &self.pieces {
match piece {
LitOrArg::Lit(s) => ret.push_str(s),
LitOrArg::Arg(arg) => {
let s = Symbol::intern(arg);
match generic_args.iter().find(|(k, _)| *k == s) {
Some((_, val)) => ret.push_str(val),
None => {
// FIXME(mejrs) If we start checking as mentioned in
// FilterFormatString::parse then this shouldn't happen
let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}"));
}
}
}
}
}

ret
}
}

/// Used with `OnUnimplementedCondition::matches_predicate` to evaluate the
/// [`OnUnimplementedCondition`].
///
/// For example, given a
/// ```rust,ignore (just an example)
@@ -85,36 +284,34 @@ impl Condition {
/// }
/// ```
#[derive(Debug)]
pub struct ConditionOptions {
pub(crate) struct ConditionOptions {
/// All the self types that may apply.
/// for example
pub self_types: Vec<String>,
pub(crate) self_types: Vec<String>,
// The kind of compiler desugaring.
pub from_desugaring: Option<DesugaringKind>,
/// Match on a variant of [rustc_infer::traits::ObligationCauseCode]
pub cause: Option<String>,
pub crate_local: bool,
pub(crate) from_desugaring: Option<DesugaringKind>,
/// Match on a variant of [rustc_infer::traits::ObligationCauseCode].
pub(crate) cause: Option<String>,
pub(crate) crate_local: bool,
/// Is the obligation "directly" user-specified, rather than derived?
pub direct: bool,
// A list of the generic arguments and their reified types
pub generic_args: Vec<(Symbol, String)>,
pub(crate) direct: bool,
// A list of the generic arguments and their reified types.
pub(crate) generic_args: Vec<(Symbol, String)>,
}

impl ConditionOptions {
pub fn contains(&self, key: Symbol, value: &Option<String>) -> bool {
match (key, value) {
(sym::_Self | kw::SelfUpper, Some(value)) => self.self_types.contains(&value),
// from_desugaring as a flag
(sym::from_desugaring, None) => self.from_desugaring.is_some(),
// from_desugaring as key == value
(sym::from_desugaring, Some(v)) if let Some(ds) = self.from_desugaring => ds.matches(v),
(sym::cause, Some(value)) => self.cause.as_deref() == Some(value),
(sym::crate_local, None) => self.crate_local,
(sym::direct, None) => self.direct,
(other, Some(value)) => {
self.generic_args.iter().any(|(k, v)| *k == other && v == value)
}
_ => false,
fn has_flag(&self, name: Flag) -> bool {
match name {
Flag::CrateLocal => self.crate_local,
Flag::Direct => self.direct,
Flag::FromDesugaring => self.from_desugaring.is_some(),
}
}
fn contains(&self, name: Name, value: String) -> bool {
match name {
Name::SelfUpper => self.self_types.contains(&value),
Name::FromDesugaring => self.from_desugaring.is_some_and(|ds| ds.matches(&value)),
Name::Cause => self.cause == Some(value),
Name::GenericArg(arg) => self.generic_args.contains(&(arg, value)),
}
}
}
56 changes: 42 additions & 14 deletions compiler/rustc_trait_selection/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::path::PathBuf;

use rustc_ast::Path;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_errors::codes::*;
use rustc_errors::{
@@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
}

#[derive(Diagnostic)]
#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
pub struct EmptyOnClauseInOnUnimplemented {
#[primary_span]
#[label]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
pub struct InvalidOnClauseInOnUnimplemented {
#[primary_span]
#[label]
pub span: Span,
pub enum InvalidOnClause {
#[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
Empty {
#[primary_span]
#[label]
span: Span,
},
#[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
ExpectedOnePredInNot {
#[primary_span]
#[label]
span: Span,
},
#[diag(trait_selection_rustc_on_unimplemented_unsupported_literal_in_on, code = E0232)]
UnsupportedLiteral {
#[primary_span]
#[label]
span: Span,
},
#[diag(trait_selection_rustc_on_unimplemented_expected_identifier, code = E0232)]
ExpectedIdentifier {
#[primary_span]
#[label]
span: Span,
path: Path,
},
#[diag(trait_selection_rustc_on_unimplemented_invalid_predicate, code = E0232)]
InvalidPredicate {
#[primary_span]
#[label]
span: Span,
invalid_pred: Symbol,
},
#[diag(trait_selection_rustc_on_unimplemented_invalid_flag, code = E0232)]
InvalidFlag {
#[primary_span]
#[label]
span: Span,
invalid_flag: Symbol,
},
}

#[derive(Diagnostic)]
#[diag(trait_selection_no_value_in_rustc_on_unimplemented, code = E0232)]
#[diag(trait_selection_rustc_on_unimplemented_missing_value, code = E0232)]
#[note]
pub struct NoValueInOnUnimplemented {
#[primary_span]
131 changes: 88 additions & 43 deletions tests/ui/on-unimplemented/bad-annotation.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,109 @@
// ignore-tidy-linelength

#![crate_type = "lib"]
#![feature(rustc_attrs)]

#![allow(unused)]

#[rustc_on_unimplemented = "test error `{Self}` with `{Bar}` `{Baz}` `{Quux}`"]
trait Foo<Bar, Baz, Quux>
{}
trait Foo<Bar, Baz, Quux> {}

#[rustc_on_unimplemented="a collection of type `{Self}` cannot be built from an iterator over elements of type `{A}`"]
#[rustc_on_unimplemented = "a collection of type `{Self}` cannot \
be built from an iterator over elements of type `{A}`"]
trait MyFromIterator<A> {
/// Builds a container with elements from an external iterator.
fn my_from_iter<T: Iterator<Item=A>>(iterator: T) -> Self;
fn my_from_iter<T: Iterator<Item = A>>(iterator: T) -> Self;
}

#[rustc_on_unimplemented]
//~^ ERROR malformed `rustc_on_unimplemented` attribute
trait BadAnnotation1
{}
trait NoContent {}

#[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"]
//~^ ERROR cannot find parameter C on this trait
trait BadAnnotation2<A,B>
{}
trait ParameterNotPresent<A, B> {}

#[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"]
//~^ ERROR positional format arguments are not allowed here
trait BadAnnotation3<A,B>
{}
trait NoPositionalArgs<A, B> {}

#[rustc_on_unimplemented(lorem="")]
//~^ ERROR this attribute must have a valid
trait BadAnnotation4 {}
#[rustc_on_unimplemented(lorem = "")]
//~^ ERROR this attribute must have a value
//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
//~^^^ NOTE expected value here
trait EmptyMessage {}

#[rustc_on_unimplemented(lorem(ipsum(dolor)))]
//~^ ERROR this attribute must have a valid
trait BadAnnotation5 {}

#[rustc_on_unimplemented(message="x", message="y")]
//~^ ERROR this attribute must have a valid
trait BadAnnotation6 {}

#[rustc_on_unimplemented(message="x", on(desugared, message="y"))]
//~^ ERROR this attribute must have a valid
trait BadAnnotation7 {}

#[rustc_on_unimplemented(on(), message="y")]
//~^ ERROR this attribute must have a value
//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
//~^^^ NOTE expected value here
trait Invalid {}

#[rustc_on_unimplemented(message = "x", message = "y")]
//~^ ERROR this attribute must have a value
//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
//~^^^ NOTE expected value here
trait DuplicateMessage {}

#[rustc_on_unimplemented(message = "x", on(desugared, message = "y"))]
//~^ ERROR this attribute must have a value
//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
//~^^^ NOTE expected value here
trait OnInWrongPosition {}

#[rustc_on_unimplemented(on(), message = "y")]
//~^ ERROR empty `on`-clause
trait BadAnnotation8 {}

#[rustc_on_unimplemented(on="x", message="y")]
//~^ ERROR this attribute must have a valid
trait BadAnnotation9 {}

#[rustc_on_unimplemented(on(x="y"), message="y")]
trait BadAnnotation10 {}

#[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message="y")]
//~^ ERROR this attribute must have a valid
trait BadAnnotation11 {}

pub fn main() {
}
//~^^ NOTE empty `on`-clause here
trait EmptyOn {}

#[rustc_on_unimplemented(on = "x", message = "y")]
//~^ ERROR this attribute must have a value
//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
//~^^^ NOTE expected value here
trait ExpectedPredicateInOn {}

#[rustc_on_unimplemented(on(x = "y"), message = "y")]
trait OnWithoutDirectives {}

#[rustc_on_unimplemented(on(from_desugaring, on(from_desugaring, message = "x")), message = "y")]
//~^ ERROR this attribute must have a value
//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
//~^^^ NOTE expected value here
trait NestedOn {}

#[rustc_on_unimplemented(on("y", message = "y"))]
//~^ ERROR literals inside `on`-clauses are not supported
//~^^ NOTE unexpected literal here
trait UnsupportedLiteral {}

#[rustc_on_unimplemented(on(42, message = "y"))]
//~^ ERROR literals inside `on`-clauses are not supported
//~^^ NOTE unexpected literal here
trait UnsupportedLiteral2 {}

#[rustc_on_unimplemented(on(not(a, b), message = "y"))]
//~^ ERROR expected a single predicate in `not(..)` [E0232]
//~^^ NOTE unexpected quantity of predicates here
trait ExpectedOnePattern {}

#[rustc_on_unimplemented(on(not(), message = "y"))]
//~^ ERROR expected a single predicate in `not(..)` [E0232]
//~^^ NOTE unexpected quantity of predicates here
trait ExpectedOnePattern2 {}

#[rustc_on_unimplemented(on(thing::What, message = "y"))]
//~^ ERROR expected an identifier inside this `on`-clause
//~^^ NOTE expected an identifier here, not `thing::What`
trait KeyMustBeIdentifier {}

#[rustc_on_unimplemented(on(thing::What = "value", message = "y"))]
//~^ ERROR expected an identifier inside this `on`-clause
//~^^ NOTE expected an identifier here, not `thing::What`
trait KeyMustBeIdentifier2 {}

#[rustc_on_unimplemented(on(aaaaaaaaaaaaaa(a, b), message = "y"))]
//~^ ERROR this predicate is invalid
//~^^ NOTE expected one of `any`, `all` or `not` here, not `aaaaaaaaaaaaaa`
trait InvalidPredicate {}

#[rustc_on_unimplemented(on(something, message = "y"))]
//~^ ERROR invalid flag in `on`-clause
//~^^ NOTE expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `something`
trait InvalidFlag {}
118 changes: 83 additions & 35 deletions tests/ui/on-unimplemented/bad-annotation.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: malformed `rustc_on_unimplemented` attribute input
--> $DIR/bad-annotation.rs:17:1
--> $DIR/bad-annotation.rs:15:1
|
LL | #[rustc_on_unimplemented]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -12,72 +12,120 @@ LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*
| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

error[E0230]: cannot find parameter C on this trait
--> $DIR/bad-annotation.rs:22:90
--> $DIR/bad-annotation.rs:19:90
|
LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"]
| ^

error[E0231]: positional format arguments are not allowed here
--> $DIR/bad-annotation.rs:27:90
--> $DIR/bad-annotation.rs:23:90
|
LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"]
| ^

error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:32:26
error[E0232]: this attribute must have a value
--> $DIR/bad-annotation.rs:27:26
|
LL | #[rustc_on_unimplemented(lorem="")]
| ^^^^^^^^ expected value here
LL | #[rustc_on_unimplemented(lorem = "")]
| ^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented(message="foo")]`
= note: e.g. `#[rustc_on_unimplemented(message="foo")]`

error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:36:26
error[E0232]: this attribute must have a value
--> $DIR/bad-annotation.rs:33:26
|
LL | #[rustc_on_unimplemented(lorem(ipsum(dolor)))]
| ^^^^^^^^^^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented(message="foo")]`
= note: e.g. `#[rustc_on_unimplemented(message="foo")]`

error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:40:39
error[E0232]: this attribute must have a value
--> $DIR/bad-annotation.rs:39:41
|
LL | #[rustc_on_unimplemented(message="x", message="y")]
| ^^^^^^^^^^^ expected value here
LL | #[rustc_on_unimplemented(message = "x", message = "y")]
| ^^^^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented(message="foo")]`
= note: e.g. `#[rustc_on_unimplemented(message="foo")]`

error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:44:39
error[E0232]: this attribute must have a value
--> $DIR/bad-annotation.rs:45:41
|
LL | #[rustc_on_unimplemented(message="x", on(desugared, message="y"))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
LL | #[rustc_on_unimplemented(message = "x", on(desugared, message = "y"))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
|
= note: eg `#[rustc_on_unimplemented(message="foo")]`
= note: e.g. `#[rustc_on_unimplemented(message="foo")]`

error[E0232]: empty `on`-clause in `#[rustc_on_unimplemented]`
--> $DIR/bad-annotation.rs:48:26
--> $DIR/bad-annotation.rs:51:26
|
LL | #[rustc_on_unimplemented(on(), message = "y")]
| ^^^^ empty `on`-clause here

error[E0232]: this attribute must have a value
--> $DIR/bad-annotation.rs:56:26
|
LL | #[rustc_on_unimplemented(on = "x", message = "y")]
| ^^^^^^^^ expected value here
|
= note: e.g. `#[rustc_on_unimplemented(message="foo")]`

error[E0232]: this attribute must have a value
--> $DIR/bad-annotation.rs:65:46
|
LL | #[rustc_on_unimplemented(on(from_desugaring, on(from_desugaring, message = "x")), message = "y")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
|
= note: e.g. `#[rustc_on_unimplemented(message="foo")]`

error[E0232]: literals inside `on`-clauses are not supported
--> $DIR/bad-annotation.rs:71:29
|
LL | #[rustc_on_unimplemented(on(), message="y")]
| ^^^^ empty on-clause here
LL | #[rustc_on_unimplemented(on("y", message = "y"))]
| ^^^ unexpected literal here

error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:52:26
error[E0232]: literals inside `on`-clauses are not supported
--> $DIR/bad-annotation.rs:76:29
|
LL | #[rustc_on_unimplemented(on="x", message="y")]
| ^^^^^^ expected value here
LL | #[rustc_on_unimplemented(on(42, message = "y"))]
| ^^ unexpected literal here

error[E0232]: expected a single predicate in `not(..)`
--> $DIR/bad-annotation.rs:81:33
|
= note: eg `#[rustc_on_unimplemented(message="foo")]`
LL | #[rustc_on_unimplemented(on(not(a, b), message = "y"))]
| ^^^^ unexpected quantity of predicates here

error[E0232]: this attribute must have a valid value
--> $DIR/bad-annotation.rs:59:40
error[E0232]: expected a single predicate in `not(..)`
--> $DIR/bad-annotation.rs:86:29
|
LL | #[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message="y")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
LL | #[rustc_on_unimplemented(on(not(), message = "y"))]
| ^^^^^ unexpected quantity of predicates here

error[E0232]: expected an identifier inside this `on`-clause
--> $DIR/bad-annotation.rs:91:29
|
LL | #[rustc_on_unimplemented(on(thing::What, message = "y"))]
| ^^^^^^^^^^^ expected an identifier here, not `thing::What`

error[E0232]: expected an identifier inside this `on`-clause
--> $DIR/bad-annotation.rs:96:29
|
LL | #[rustc_on_unimplemented(on(thing::What = "value", message = "y"))]
| ^^^^^^^^^^^ expected an identifier here, not `thing::What`

error[E0232]: this predicate is invalid
--> $DIR/bad-annotation.rs:101:29
|
LL | #[rustc_on_unimplemented(on(aaaaaaaaaaaaaa(a, b), message = "y"))]
| ^^^^^^^^^^^^^^ expected one of `any`, `all` or `not` here, not `aaaaaaaaaaaaaa`

error[E0232]: invalid flag in `on`-clause
--> $DIR/bad-annotation.rs:106:29
|
= note: eg `#[rustc_on_unimplemented(message="foo")]`
LL | #[rustc_on_unimplemented(on(something, message = "y"))]
| ^^^^^^^^^ expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `something`

error: aborting due to 10 previous errors
error: aborting due to 18 previous errors

Some errors have detailed explanations: E0230, E0231, E0232.
For more information about an error, try `rustc --explain E0230`.