Skip to content

Commit 3e1d86b

Browse files
committed
Port rustc_on_unimplemented attribute
1 parent 669c081 commit 3e1d86b

19 files changed

Lines changed: 558 additions & 445 deletions

File tree

compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs

Lines changed: 193 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
#![allow(warnings)]
2-
32
use std::ops::Range;
43

5-
use rustc_hir::attrs::diagnostic::{FormatArg, FormatString, Piece};
4+
use rustc_errors::E0232;
5+
use rustc_hir::AttrPath;
6+
use rustc_hir::attrs::diagnostic::{
7+
FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue,
8+
OnUnimplementedCondition, Piece, Predicate,
9+
};
610
use rustc_hir::lints::FormatWarning;
11+
use rustc_macros::Diagnostic;
712
use rustc_parse_format::{
813
Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
914
};
10-
use rustc_span::{InnerSpan, Span, Symbol, kw, sym};
15+
use rustc_span::{Ident, InnerSpan, Span, Symbol, kw, sym};
16+
use thin_vec::ThinVec;
17+
18+
use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser};
1119

1220
pub mod on_unimplemented;
1321

1422
#[derive(Copy, Clone)]
15-
pub(crate) enum Ctx {
23+
pub(crate) enum Mode {
1624
// `#[rustc_on_unimplemented]`
1725
RustcOnUnimplemented,
1826
// `#[diagnostic::...]`
@@ -23,7 +31,7 @@ pub(crate) fn parse_format_string(
2331
input: Symbol,
2432
snippet: Option<String>,
2533
span: Span,
26-
ctx: Ctx,
34+
mode: Mode,
2735
) -> Result<(FormatString, Vec<FormatWarning>), ParseError> {
2836
let s = input.as_str();
2937
let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic);
@@ -40,7 +48,7 @@ pub(crate) fn parse_format_string(
4048
RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)),
4149
RpfPiece::NextArgument(arg) => {
4250
warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal);
43-
let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal);
51+
let arg = parse_arg(&arg, mode, &mut warnings, span, parser.is_source_literal);
4452
Piece::Arg(arg)
4553
}
4654
})
@@ -51,7 +59,7 @@ pub(crate) fn parse_format_string(
5159

5260
fn parse_arg(
5361
arg: &Argument<'_>,
54-
ctx: Ctx,
62+
mode: Mode,
5563
warnings: &mut Vec<FormatWarning>,
5664
input_span: Span,
5765
is_source_literal: bool,
@@ -60,18 +68,18 @@ fn parse_arg(
6068

6169
match arg.position {
6270
// Something like "hello {name}"
63-
Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
71+
Position::ArgumentNamed(name) => match (mode, Symbol::intern(name)) {
6472
// Only `#[rustc_on_unimplemented]` can use these
65-
(Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
66-
(Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
67-
(Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
73+
(Mode::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
74+
(Mode::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
75+
(Mode::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
6876
// Any attribute can use these
6977
(
70-
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
78+
Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnimplemented { .. },
7179
kw::SelfUpper,
7280
) => FormatArg::SelfUpper,
7381
(
74-
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
82+
Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnimplemented { .. },
7583
generic_param,
7684
) => FormatArg::GenericParam { generic_param, span },
7785
},
@@ -115,3 +123,175 @@ fn warn_on_format_spec(
115123
fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal: bool) -> Span {
116124
if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input }
117125
}
126+
127+
pub(crate) fn parse_condition(
128+
input: &MetaItemOrLitParser,
129+
) -> Result<OnUnimplementedCondition, InvalidOnClause> {
130+
let span = input.span();
131+
let pred = parse_predicate(input)?;
132+
Ok(OnUnimplementedCondition { span, pred })
133+
}
134+
135+
fn parse_predicate(input: &MetaItemOrLitParser) -> Result<Predicate, InvalidOnClause> {
136+
let Some(meta_item) = input.meta_item() else {
137+
return Err(InvalidOnClause::UnsupportedLiteral { span: input.span() });
138+
};
139+
140+
let Some(predicate) = meta_item.ident() else {
141+
return Err(InvalidOnClause::ExpectedIdentifier {
142+
span: meta_item.path().span(),
143+
path: meta_item.path().get_attribute_path(),
144+
});
145+
};
146+
147+
match meta_item.args() {
148+
ArgParser::List(mis) => match predicate.name {
149+
sym::any => Ok(Predicate::Any(parse_predicate_sequence(mis)?)),
150+
sym::all => Ok(Predicate::All(parse_predicate_sequence(mis)?)),
151+
sym::not => {
152+
if let Some(single) = mis.single() {
153+
Ok(Predicate::Not(Box::new(parse_predicate(single)?)))
154+
} else {
155+
Err(InvalidOnClause::ExpectedOnePredInNot { span: mis.span })
156+
}
157+
}
158+
invalid_pred => {
159+
Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred })
160+
}
161+
},
162+
ArgParser::NameValue(p) => {
163+
let Some(value) = p.value_as_ident() else {
164+
return Err(InvalidOnClause::UnsupportedLiteral { span: p.args_span() });
165+
};
166+
let name = parse_name(predicate);
167+
let value = parse_filter(value.name);
168+
let kv = NameValue { name, value };
169+
Ok(Predicate::Match(kv))
170+
}
171+
ArgParser::NoArgs => {
172+
let flag = parse_flag(predicate)?;
173+
Ok(Predicate::Flag(flag))
174+
}
175+
}
176+
}
177+
178+
fn parse_predicate_sequence(
179+
sequence: &MetaItemListParser,
180+
) -> Result<ThinVec<Predicate>, InvalidOnClause> {
181+
sequence.mixed().map(parse_predicate).collect()
182+
}
183+
184+
fn parse_flag(Ident { name, span }: Ident) -> Result<Flag, InvalidOnClause> {
185+
match name {
186+
sym::crate_local => Ok(Flag::CrateLocal),
187+
sym::direct => Ok(Flag::Direct),
188+
sym::from_desugaring => Ok(Flag::FromDesugaring),
189+
invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }),
190+
}
191+
}
192+
193+
fn parse_name(Ident { name, span }: Ident) -> Name {
194+
match name {
195+
kw::SelfUpper => Name::SelfUpper,
196+
sym::from_desugaring => Name::FromDesugaring,
197+
sym::cause => Name::Cause,
198+
generic => Name::GenericArg(generic),
199+
}
200+
}
201+
202+
fn parse_filter(input: Symbol) -> FilterFormatString {
203+
let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic)
204+
.map(|p| match p {
205+
RpfPiece::Lit(s) => LitOrArg::Lit(Symbol::intern(s)),
206+
// We just ignore formatspecs here
207+
RpfPiece::NextArgument(a) => match a.position {
208+
// In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even
209+
// if the integer type has been resolved, to allow targeting all integers.
210+
// `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet,
211+
// from the `Display` impl of `InferTy` to be precise.
212+
//
213+
// Don't try to format these later!
214+
Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => {
215+
LitOrArg::Lit(Symbol::intern(&format!("{{{arg}}}")))
216+
}
217+
218+
// FIXME(mejrs) We should check if these correspond to a generic of the trait.
219+
Position::ArgumentNamed(arg) => LitOrArg::Arg(Symbol::intern(arg)),
220+
221+
// FIXME(mejrs) These should really be warnings/errors
222+
Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(sym::empty_braces),
223+
Position::ArgumentIs(idx) => LitOrArg::Lit(Symbol::intern(&format!("{{{idx}}}"))),
224+
},
225+
})
226+
.collect();
227+
FilterFormatString { pieces }
228+
}
229+
230+
#[derive(Diagnostic)]
231+
pub(crate) enum InvalidOnClause {
232+
#[diag("empty `on`-clause in `#[rustc_on_unimplemented]`", code = E0232)]
233+
Empty {
234+
#[primary_span]
235+
#[label("empty `on`-clause here")]
236+
span: Span,
237+
},
238+
#[diag("expected a single predicate in `not(..)`", code = E0232)]
239+
ExpectedOnePredInNot {
240+
#[primary_span]
241+
#[label("unexpected quantity of predicates here")]
242+
span: Span,
243+
},
244+
#[diag("literals inside `on`-clauses are not supported", code = E0232)]
245+
UnsupportedLiteral {
246+
#[primary_span]
247+
#[label("unexpected literal here")]
248+
span: Span,
249+
},
250+
#[diag("expected an identifier inside this `on`-clause", code = E0232)]
251+
ExpectedIdentifier {
252+
#[primary_span]
253+
#[label("expected an identifier here, not `{$path}`")]
254+
span: Span,
255+
path: AttrPath,
256+
},
257+
#[diag("this predicate is invalid", code = E0232)]
258+
InvalidPredicate {
259+
#[primary_span]
260+
#[label("expected one of `any`, `all` or `not` here, not `{$invalid_pred}`")]
261+
span: Span,
262+
invalid_pred: Symbol,
263+
},
264+
#[diag("invalid flag in `on`-clause", code = E0232)]
265+
InvalidFlag {
266+
#[primary_span]
267+
#[label(
268+
"expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `{$invalid_flag}`"
269+
)]
270+
span: Span,
271+
invalid_flag: Symbol,
272+
},
273+
#[diag("invalid name in `on`-clause", code = E0232)]
274+
InvalidName {
275+
#[primary_span]
276+
#[label(
277+
"expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `{$invalid_name}`"
278+
)]
279+
span: Span,
280+
invalid_name: Symbol,
281+
},
282+
}
283+
284+
#[derive(Diagnostic)]
285+
#[diag("this attribute must have a value", code = E0232)]
286+
#[note("e.g. `#[rustc_on_unimplemented(message=\"foo\")]`")]
287+
pub struct NoValueInOnUnimplemented {
288+
#[primary_span]
289+
#[label("expected value here")]
290+
pub span: Span,
291+
}
292+
293+
#[derive(Diagnostic)]
294+
#[diag(
295+
"using multiple `rustc_on_unimplemented` (or mixing it with `diagnostic::on_unimplemented`) is not supported"
296+
)]
297+
pub struct DupesNotAllowed;

0 commit comments

Comments
 (0)