rustc_trait_selection/error_reporting/traits/
on_unimplemented_condition.rs

1use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit};
2use rustc_parse_format::{ParseMode, Parser, Piece, Position};
3use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym};
4
5use crate::errors::InvalidOnClause;
6
7/// Represents the `on` filter in `#[rustc_on_unimplemented]`.
8#[derive(Debug)]
9pub(crate) struct OnUnimplementedCondition {
10    span: Span,
11    pred: Predicate,
12}
13
14impl OnUnimplementedCondition {
15    pub(crate) fn span(&self) -> Span {
16        self.span
17    }
18
19    pub(crate) fn matches_predicate(&self, options: &ConditionOptions) -> bool {
20        self.pred.eval(&mut |p| match p {
21            FlagOrNv::Flag(b) => options.has_flag(*b),
22            FlagOrNv::NameValue(NameValue { name, value }) => {
23                let value = value.format(&options.generic_args);
24                options.contains(*name, value)
25            }
26        })
27    }
28
29    pub(crate) fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
30        let span = input.span();
31        let pred = Predicate::parse(input)?;
32        Ok(OnUnimplementedCondition { span, pred })
33    }
34}
35
36/// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`OnUnimplementedCondition`].
37///
38/// It is similar to the predicate in the `cfg` attribute,
39/// and may contain nested predicates.
40#[derive(Debug)]
41enum Predicate {
42    /// A condition like `on(crate_local)`.
43    Flag(Flag),
44    /// A match, like `on(Rhs = "Whatever")`.
45    Match(NameValue),
46    /// Negation, like `on(not($pred))`.
47    Not(Box<Predicate>),
48    /// True if all predicates are true, like `on(all($a, $b, $c))`.
49    All(Vec<Predicate>),
50    /// True if any predicate is true, like `on(any($a, $b, $c))`.
51    Any(Vec<Predicate>),
52}
53
54impl Predicate {
55    fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
56        let meta_item = match input {
57            MetaItemInner::MetaItem(meta_item) => meta_item,
58            MetaItemInner::Lit(lit) => {
59                return Err(InvalidOnClause::UnsupportedLiteral { span: lit.span });
60            }
61        };
62
63        let Some(predicate) = meta_item.ident() else {
64            return Err(InvalidOnClause::ExpectedIdentifier {
65                span: meta_item.path.span,
66                path: meta_item.path.clone(),
67            });
68        };
69
70        match meta_item.kind {
71            MetaItemKind::List(ref mis) => match predicate.name {
72                sym::any => Ok(Predicate::Any(Predicate::parse_sequence(mis)?)),
73                sym::all => Ok(Predicate::All(Predicate::parse_sequence(mis)?)),
74                sym::not => match &**mis {
75                    [one] => Ok(Predicate::Not(Box::new(Predicate::parse(one)?))),
76                    [first, .., last] => Err(InvalidOnClause::ExpectedOnePredInNot {
77                        span: first.span().to(last.span()),
78                    }),
79                    [] => Err(InvalidOnClause::ExpectedOnePredInNot { span: meta_item.span }),
80                },
81                invalid_pred => {
82                    Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred })
83                }
84            },
85            MetaItemKind::NameValue(MetaItemLit { symbol, .. }) => {
86                let name = Name::parse(predicate);
87                let value = FilterFormatString::parse(symbol);
88                let kv = NameValue { name, value };
89                Ok(Predicate::Match(kv))
90            }
91            MetaItemKind::Word => {
92                let flag = Flag::parse(predicate)?;
93                Ok(Predicate::Flag(flag))
94            }
95        }
96    }
97
98    fn parse_sequence(sequence: &[MetaItemInner]) -> Result<Vec<Self>, InvalidOnClause> {
99        sequence.iter().map(Predicate::parse).collect()
100    }
101
102    fn eval(&self, eval: &mut impl FnMut(FlagOrNv<'_>) -> bool) -> bool {
103        match self {
104            Predicate::Flag(flag) => eval(FlagOrNv::Flag(flag)),
105            Predicate::Match(nv) => eval(FlagOrNv::NameValue(nv)),
106            Predicate::Not(not) => !not.eval(eval),
107            Predicate::All(preds) => preds.into_iter().all(|pred| pred.eval(eval)),
108            Predicate::Any(preds) => preds.into_iter().any(|pred| pred.eval(eval)),
109        }
110    }
111}
112
113/// Represents a `MetaWord` in an `on`-filter.
114#[derive(Debug, Clone, Copy)]
115enum Flag {
116    /// Whether the code causing the trait bound to not be fulfilled
117    /// is part of the user's crate.
118    CrateLocal,
119    /// Whether the obligation is user-specified rather than derived.
120    Direct,
121    /// Whether we are in some kind of desugaring like
122    /// `?` or `try { .. }`.
123    FromDesugaring,
124}
125
126impl Flag {
127    fn parse(Ident { name, span }: Ident) -> Result<Self, InvalidOnClause> {
128        match name {
129            sym::crate_local => Ok(Flag::CrateLocal),
130            sym::direct => Ok(Flag::Direct),
131            sym::from_desugaring => Ok(Flag::FromDesugaring),
132            invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }),
133        }
134    }
135}
136
137/// A `MetaNameValueStr` in an `on`-filter.
138///
139/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`.
140#[derive(Debug, Clone)]
141struct NameValue {
142    name: Name,
143    /// Something like `"&str"` or `"alloc::string::String"`,
144    /// in which case it just contains a single string piece.
145    /// But if it is something like `"&[{A}]"` then it must be formatted later.
146    value: FilterFormatString,
147}
148
149/// The valid names of the `on` filter.
150#[derive(Debug, Clone, Copy)]
151enum Name {
152    Cause,
153    FromDesugaring,
154    SelfUpper,
155    GenericArg(Symbol),
156}
157
158impl Name {
159    fn parse(Ident { name, .. }: Ident) -> Self {
160        match name {
161            sym::_Self | kw::SelfUpper => Name::SelfUpper,
162            sym::from_desugaring => Name::FromDesugaring,
163            sym::cause => Name::Cause,
164            // FIXME(mejrs) Perhaps we should start checking that
165            // this actually is a valid generic parameter?
166            generic => Name::GenericArg(generic),
167        }
168    }
169}
170
171#[derive(Debug, Clone)]
172enum FlagOrNv<'p> {
173    Flag(&'p Flag),
174    NameValue(&'p NameValue),
175}
176
177/// Represents a value inside an `on` filter.
178///
179/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`.
180/// If it is a simple literal like this then `pieces` will be `[LitOrArg::Lit("value")]`.
181/// The `Arg` variant is used when it contains formatting like
182/// `#[rustc_on_unimplemented(on(Self = "&[{A}]", message = "hello"))]`.
183#[derive(Debug, Clone)]
184struct FilterFormatString {
185    pieces: Vec<LitOrArg>,
186}
187
188#[derive(Debug, Clone)]
189enum LitOrArg {
190    Lit(String),
191    Arg(String),
192}
193
194impl FilterFormatString {
195    fn parse(input: Symbol) -> Self {
196        let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Format)
197            .map(|p| match p {
198                Piece::Lit(s) => LitOrArg::Lit(s.to_owned()),
199                // We just ignore formatspecs here
200                Piece::NextArgument(a) => match a.position {
201                    // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even
202                    // if the integer type has been resolved, to allow targeting all integers.
203                    // `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet,
204                    // from the `Display` impl of `InferTy` to be precise.
205                    //
206                    // Don't try to format these later!
207                    Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => {
208                        LitOrArg::Lit(format!("{{{arg}}}"))
209                    }
210
211                    // FIXME(mejrs) We should check if these correspond to a generic of the trait.
212                    Position::ArgumentNamed(arg) => LitOrArg::Arg(arg.to_owned()),
213
214                    // FIXME(mejrs) These should really be warnings/errors
215                    Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(String::from("{}")),
216                    Position::ArgumentIs(idx) => LitOrArg::Lit(format!("{{{idx}}}")),
217                },
218            })
219            .collect();
220        Self { pieces }
221    }
222
223    fn format(&self, generic_args: &[(Symbol, String)]) -> String {
224        let mut ret = String::new();
225
226        for piece in &self.pieces {
227            match piece {
228                LitOrArg::Lit(s) => ret.push_str(s),
229                LitOrArg::Arg(arg) => {
230                    let s = Symbol::intern(arg);
231                    match generic_args.iter().find(|(k, _)| *k == s) {
232                        Some((_, val)) => ret.push_str(val),
233                        None => {
234                            // FIXME(mejrs) If we start checking as mentioned in
235                            // FilterFormatString::parse then this shouldn't happen
236                            let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}"));
237                        }
238                    }
239                }
240            }
241        }
242
243        ret
244    }
245}
246
247/// Used with `OnUnimplementedCondition::matches_predicate` to evaluate the
248/// [`OnUnimplementedCondition`].
249///
250/// For example, given a
251/// ```rust,ignore (just an example)
252/// #[rustc_on_unimplemented(
253///     on(all(from_desugaring = "QuestionMark"),
254///         message = "the `?` operator can only be used in {ItemContext} \
255///                     that returns `Result` or `Option` \
256///                     (or another type that implements `{FromResidual}`)",
257///         label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`",
258///         parent_label = "this function should return `Result` or `Option` to accept `?`"
259///     ),
260/// )]
261/// pub trait FromResidual<R = <Self as Try>::Residual> {
262///    ...
263/// }
264///
265/// async fn an_async_function() -> u32 {
266///     let x: Option<u32> = None;
267///     x?; //~ ERROR the `?` operator
268///     22
269/// }
270///  ```
271/// it will look like this:
272///
273/// ```rust,ignore (just an example)
274/// ConditionOptions {
275///     self_types: ["u32", "{integral}"],
276///     from_desugaring: Some("QuestionMark"),
277///     cause: None,
278///     crate_local: false,
279///     direct: true,
280///     generic_args: [("Self","u32"),
281///         ("R", "core::option::Option<core::convert::Infallible>"),
282///         ("R", "core::option::Option<T>" ),
283///     ],
284/// }
285/// ```
286#[derive(Debug)]
287pub(crate) struct ConditionOptions {
288    /// All the self types that may apply.
289    pub(crate) self_types: Vec<String>,
290    // The kind of compiler desugaring.
291    pub(crate) from_desugaring: Option<DesugaringKind>,
292    /// Match on a variant of [rustc_infer::traits::ObligationCauseCode].
293    pub(crate) cause: Option<String>,
294    pub(crate) crate_local: bool,
295    /// Is the obligation "directly" user-specified, rather than derived?
296    pub(crate) direct: bool,
297    // A list of the generic arguments and their reified types.
298    pub(crate) generic_args: Vec<(Symbol, String)>,
299}
300
301impl ConditionOptions {
302    fn has_flag(&self, name: Flag) -> bool {
303        match name {
304            Flag::CrateLocal => self.crate_local,
305            Flag::Direct => self.direct,
306            Flag::FromDesugaring => self.from_desugaring.is_some(),
307        }
308    }
309    fn contains(&self, name: Name, value: String) -> bool {
310        match name {
311            Name::SelfUpper => self.self_types.contains(&value),
312            Name::FromDesugaring => self.from_desugaring.is_some_and(|ds| ds.matches(&value)),
313            Name::Cause => self.cause == Some(value),
314            Name::GenericArg(arg) => self.generic_args.contains(&(arg, value)),
315        }
316    }
317}