Skip to content

Commit f85b181

Browse files
committed
port over diagnostic::on_unimplemented
1 parent c24fb3b commit f85b181

34 files changed

+763
-672
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3546,6 +3546,7 @@ dependencies = [
35463546
"rustc_lexer",
35473547
"rustc_macros",
35483548
"rustc_parse",
3549+
"rustc_parse_format",
35493550
"rustc_session",
35503551
"rustc_span",
35513552
"rustc_target",

compiler/rustc_attr_parsing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rustc_hir = { path = "../rustc_hir" }
1515
rustc_lexer = { path = "../rustc_lexer" }
1616
rustc_macros = { path = "../rustc_macros" }
1717
rustc_parse = { path = "../rustc_parse" }
18+
rustc_parse_format = { path = "../rustc_parse_format" }
1819
rustc_session = { path = "../rustc_session" }
1920
rustc_span = { path = "../rustc_span" }
2021
rustc_target = { path = "../rustc_target" }
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#![allow(warnings)]
2+
3+
use std::ops::Range;
4+
5+
use rustc_hir::attrs::diagnostic::{FormatArg, FormatString, Piece};
6+
use rustc_hir::lints::FormatWarning;
7+
use rustc_parse_format::{
8+
Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
9+
};
10+
use rustc_span::{InnerSpan, Span, Symbol, kw, sym};
11+
12+
pub mod on_unimplemented;
13+
14+
#[derive(Copy, Clone)]
15+
pub(crate) enum Ctx {
16+
// `#[rustc_on_unimplemented]`
17+
RustcOnUnimplemented,
18+
// `#[diagnostic::...]`
19+
DiagnosticOnUnimplemented,
20+
}
21+
22+
pub(crate) fn parse_format_string(
23+
input: Symbol,
24+
snippet: Option<String>,
25+
span: Span,
26+
ctx: Ctx,
27+
) -> Result<(FormatString, Vec<FormatWarning>), ParseError> {
28+
let s = input.as_str();
29+
let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic);
30+
let pieces: Vec<_> = parser.by_ref().collect();
31+
32+
if let Some(err) = parser.errors.into_iter().next() {
33+
return Err(err);
34+
}
35+
let mut warnings = Vec::new();
36+
37+
let pieces = pieces
38+
.into_iter()
39+
.map(|piece| match piece {
40+
RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)),
41+
RpfPiece::NextArgument(arg) => {
42+
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);
44+
Piece::Arg(arg)
45+
}
46+
})
47+
.collect();
48+
49+
Ok((FormatString { input, pieces, span }, warnings))
50+
}
51+
52+
fn parse_arg(
53+
arg: &Argument<'_>,
54+
ctx: Ctx,
55+
warnings: &mut Vec<FormatWarning>,
56+
input_span: Span,
57+
is_source_literal: bool,
58+
) -> FormatArg {
59+
let span = slice_span(input_span, arg.position_span.clone(), is_source_literal);
60+
61+
match arg.position {
62+
// Something like "hello {name}"
63+
Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
64+
// 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,
68+
// Any attribute can use these
69+
(
70+
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
71+
kw::SelfUpper,
72+
) => FormatArg::SelfUpper,
73+
(
74+
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
75+
generic_param,
76+
) => FormatArg::GenericParam { generic_param, span },
77+
},
78+
79+
// `{:1}` and `{}` are ignored
80+
Position::ArgumentIs(idx) => {
81+
warnings.push(FormatWarning::PositionalArgument {
82+
span,
83+
help: format!("use `{{{idx}}}` to print a number in braces"),
84+
});
85+
FormatArg::AsIs(Symbol::intern(&format!("{{{idx}}}")))
86+
}
87+
Position::ArgumentImplicitlyIs(_) => {
88+
warnings.push(FormatWarning::PositionalArgument {
89+
span,
90+
help: String::from("use `{{}}` to print empty braces"),
91+
});
92+
FormatArg::AsIs(sym::empty_braces)
93+
}
94+
}
95+
}
96+
97+
/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything
98+
/// with specifiers, so emit a warning if they are used.
99+
fn warn_on_format_spec(
100+
spec: &FormatSpec<'_>,
101+
warnings: &mut Vec<FormatWarning>,
102+
input_span: Span,
103+
is_source_literal: bool,
104+
) {
105+
if spec.ty != "" {
106+
let span = spec
107+
.ty_span
108+
.as_ref()
109+
.map(|inner| slice_span(input_span, inner.clone(), is_source_literal))
110+
.unwrap_or(input_span);
111+
warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
112+
}
113+
}
114+
115+
fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal: bool) -> Span {
116+
if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input }
117+
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#![allow(warnings)]
2+
use rustc_hir::attrs::diagnostic::OnUnimplementedDirective;
3+
use rustc_hir::lints::AttributeLintKind;
4+
use rustc_session::lint::builtin::{
5+
MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
6+
};
7+
use thin_vec::thin_vec;
8+
9+
use crate::attributes::diagnostic::*;
10+
use crate::attributes::prelude::*;
11+
use crate::attributes::template;
12+
/// Folds all uses of `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
13+
/// FIXME(mejrs): add an example
14+
#[derive(Default)]
15+
pub struct OnUnimplementedParser {
16+
directive: Option<(Span, OnUnimplementedDirective)>,
17+
}
18+
19+
impl<S: Stage> AttributeParser<S> for OnUnimplementedParser {
20+
const ATTRIBUTES: AcceptMapping<Self, S> = &[
21+
(&[sym::diagnostic, sym::on_unimplemented], template!(Word), |this, cx, args| {
22+
let span = cx.attr_span;
23+
24+
let items = match args {
25+
ArgParser::List(items) => items,
26+
ArgParser::NoArgs => {
27+
cx.emit_lint(
28+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
29+
AttributeLintKind::MissingOptionsForOnUnimplemented,
30+
span,
31+
);
32+
return;
33+
}
34+
ArgParser::NameValue(_) => {
35+
cx.emit_lint(
36+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
37+
AttributeLintKind::MalformedOnUnimplementedAttr { span },
38+
span,
39+
);
40+
return;
41+
}
42+
};
43+
44+
let Some(directive) = parse_directive_items(cx, Ctx::DiagnosticOnUnimplemented, items)
45+
else {
46+
return;
47+
};
48+
merge_directives(cx, &mut this.directive, (span, directive));
49+
}),
50+
// todo (&[sym::rustc_on_unimplemented], template!(Word), |this, cx, args| {}),
51+
];
52+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
53+
54+
fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
55+
self.directive.map(|(span, directive)| AttributeKind::OnUnimplemented {
56+
span,
57+
directive: Some(Box::new(directive)),
58+
})
59+
}
60+
}
61+
62+
fn merge_directives<S: Stage>(
63+
cx: &mut AcceptContext<'_, '_, S>,
64+
first: &mut Option<(Span, OnUnimplementedDirective)>,
65+
later: (Span, OnUnimplementedDirective),
66+
) {
67+
if let Some((first_span, first)) = first {
68+
merge(cx, &mut first.message, later.1.message, "message");
69+
merge(cx, &mut first.label, later.1.label, "label");
70+
first.notes.extend(later.1.notes);
71+
} else {
72+
*first = Some(later);
73+
}
74+
}
75+
76+
fn merge<T, S: Stage>(
77+
cx: &mut AcceptContext<'_, '_, S>,
78+
first: &mut Option<(Span, T)>,
79+
later: Option<(Span, T)>,
80+
option_name: &'static str,
81+
) {
82+
match (first, later) {
83+
(Some(_) | None, None) => {}
84+
(Some((first_span, _)), Some((later_span, _))) => {
85+
cx.emit_lint(
86+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
87+
AttributeLintKind::IgnoredDiagnosticOption {
88+
first_span: *first_span,
89+
later_span,
90+
option_name,
91+
},
92+
later_span,
93+
);
94+
}
95+
(first @ None, Some(later)) => {
96+
first.get_or_insert(later);
97+
}
98+
}
99+
}
100+
101+
fn parse_directive_items<S: Stage>(
102+
cx: &mut AcceptContext<S>,
103+
ctx: Ctx,
104+
items: &MetaItemListParser,
105+
) -> Option<OnUnimplementedDirective> {
106+
let condition = None;
107+
let mut message = None;
108+
let mut label = None;
109+
let mut notes = ThinVec::new();
110+
let mut parent_label = None;
111+
let mut subcommands = ThinVec::new();
112+
let mut append_const_msg = None;
113+
114+
for item in items.mixed() {
115+
// At this point, we are expecting any of:
116+
// message = "..", label = "..", note = ".."
117+
let Some((name, value, value_span)) = (try {
118+
let item = item.meta_item()?;
119+
let name = item.ident()?.name;
120+
let nv = item.args().name_value()?;
121+
let value = nv.value_as_str()?;
122+
(name, value, nv.value_span)
123+
}) else {
124+
let span = item.span();
125+
cx.emit_lint(
126+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
127+
AttributeLintKind::MalformedOnUnimplementedAttr { span },
128+
span,
129+
);
130+
continue;
131+
};
132+
133+
let mut parse = |input| {
134+
let snippet = cx.sess.source_map().span_to_snippet(value_span).ok();
135+
let is_snippet = snippet.is_some();
136+
match parse_format_string(input, snippet, value_span, ctx) {
137+
Ok((f, warnings)) => {
138+
for warning in warnings {
139+
let (FormatWarning::InvalidSpecifier { span, .. }
140+
| FormatWarning::PositionalArgument { span, .. }) = warning;
141+
cx.emit_lint(
142+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
143+
AttributeLintKind::MalformedDiagnosticFormat { warning },
144+
span,
145+
);
146+
}
147+
148+
f
149+
}
150+
Err(e) => {
151+
cx.emit_lint(
152+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
153+
AttributeLintKind::DiagnosticWrappedParserError {
154+
description: e.description,
155+
label: e.label,
156+
span: slice_span(value_span, e.span, is_snippet),
157+
},
158+
value_span,
159+
);
160+
// We could not parse the input, just use it as-is.
161+
FormatString { input, span: value_span, pieces: thin_vec![Piece::Lit(input)] }
162+
}
163+
}
164+
};
165+
match name {
166+
sym::message => {
167+
if message.is_none() {
168+
message.insert((item.span(), parse(value)));
169+
} else {
170+
// warn
171+
}
172+
}
173+
sym::label => {
174+
if label.is_none() {
175+
label.insert((item.span(), parse(value)));
176+
} else {
177+
// warn
178+
}
179+
}
180+
sym::note => notes.push(parse(value)),
181+
_other => {
182+
let span = item.span();
183+
cx.emit_lint(
184+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
185+
AttributeLintKind::MalformedOnUnimplementedAttr { span },
186+
span,
187+
);
188+
continue;
189+
}
190+
}
191+
}
192+
193+
Some(OnUnimplementedDirective {
194+
condition,
195+
subcommands,
196+
message,
197+
label,
198+
notes,
199+
parent_label,
200+
append_const_msg,
201+
})
202+
}

compiler/rustc_attr_parsing/src/attributes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub(crate) mod confusables;
3939
pub(crate) mod crate_level;
4040
pub(crate) mod debugger;
4141
pub(crate) mod deprecation;
42+
pub(crate) mod diagnostic;
4243
pub(crate) mod do_not_recommend;
4344
pub(crate) mod doc;
4445
pub(crate) mod dummy;

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::attributes::confusables::*;
2626
use crate::attributes::crate_level::*;
2727
use crate::attributes::debugger::*;
2828
use crate::attributes::deprecation::*;
29+
use crate::attributes::diagnostic::on_unimplemented::*;
2930
use crate::attributes::do_not_recommend::*;
3031
use crate::attributes::doc::*;
3132
use crate::attributes::dummy::*;
@@ -147,6 +148,7 @@ attribute_parsers!(
147148
DocParser,
148149
MacroUseParser,
149150
NakedParser,
151+
OnUnimplementedParser,
150152
StabilityParser,
151153
UsedParser,
152154
// tidy-alphabetical-end

compiler/rustc_attr_parsing/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
#![feature(decl_macro)]
8181
#![feature(if_let_guard)]
8282
#![feature(iter_intersperse)]
83+
#![feature(try_blocks)]
8384
#![recursion_limit = "256"]
8485
// tidy-alphabetical-end
8586

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,4 @@
1-
The `#[rustc_on_unimplemented]` attribute lets you specify a custom error
2-
message for when a particular trait isn't implemented on a type placed in a
3-
position that needs that trait. For example, when the following code is
4-
compiled:
1+
#### Note: this error code is no longer emitted by the compiler.
52

6-
```compile_fail,E0230
7-
#![feature(rustc_attrs)]
8-
#![allow(internal_features)]
9-
10-
#[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{B}>`"] // error
11-
trait BadAnnotation<A> {}
12-
```
13-
14-
There will be an error about `bool` not implementing `Index<u8>`, followed by a
15-
note saying "the type `bool` cannot be indexed by `u8`".
16-
17-
As you can see, you can specify type parameters in curly braces for
18-
instantiation with the actual types (using the regular format string syntax) in
19-
a given situation. Furthermore, `{Self}` will be instantiated to the type (in
20-
this case, `bool`) that we tried to use.
21-
22-
This error appears when the curly braces contain an identifier which doesn't
23-
match with any of the type parameters or the string `Self`. This might happen
24-
if you misspelled a type parameter, or if you intended to use literal curly
25-
braces. If it is the latter, escape the curly braces with a second curly brace
26-
of the same type; e.g., a literal `{` is `{{`.
3+
The `#[rustc_on_unimplemented]` attribute used to raise this error for various
4+
misuses of the attribute; these are now warnings.

0 commit comments

Comments
 (0)