Skip to content

Commit a1841a0

Browse files
committed
port over diagnostic::on_unimplemented
1 parent 27fbcfb commit a1841a0

35 files changed

Lines changed: 771 additions & 648 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3548,6 +3548,7 @@ dependencies = [
35483548
"rustc_lexer",
35493549
"rustc_macros",
35503550
"rustc_parse",
3551+
"rustc_parse_format",
35513552
"rustc_session",
35523553
"rustc_span",
35533554
"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: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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+
/// TODO: 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+
match parse_format_string(input, snippet, value_span, ctx) {
136+
Ok((f, warnings)) => {
137+
for warning in warnings {
138+
let (FormatWarning::InvalidSpecifier { span, .. }
139+
| FormatWarning::PositionalArgument { span, .. }) = warning;
140+
cx.emit_lint(
141+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
142+
AttributeLintKind::MalformedDiagnosticFormat { warning },
143+
span,
144+
);
145+
}
146+
147+
f
148+
}
149+
Err(e) => {
150+
cx.emit_lint(
151+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
152+
AttributeLintKind::DiagnosticWrappedParserError {
153+
description: e.description,
154+
label: e.label,
155+
},
156+
value_span,
157+
);
158+
// We could not parse the input, just use it as-is.
159+
FormatString { input, span: value_span, pieces: thin_vec![Piece::Lit(input)] }
160+
}
161+
}
162+
};
163+
match name {
164+
sym::message => {
165+
if message.is_none() {
166+
message.insert((item.span(), parse(value)));
167+
} else {
168+
// warn
169+
}
170+
}
171+
sym::label => {
172+
if label.is_none() {
173+
label.insert((item.span(), parse(value)));
174+
} else {
175+
// warn
176+
}
177+
}
178+
sym::note => notes.push(parse(value)),
179+
_other => {
180+
let span = item.span();
181+
cx.emit_lint(
182+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
183+
AttributeLintKind::MalformedOnUnimplementedAttr { span },
184+
span,
185+
);
186+
continue;
187+
}
188+
}
189+
}
190+
191+
Some(OnUnimplementedDirective {
192+
condition,
193+
subcommands,
194+
message,
195+
label,
196+
notes,
197+
parent_label,
198+
append_const_msg,
199+
})
200+
}

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
@@ -35,6 +35,7 @@ use crate::attributes::crate_level::{
3535
};
3636
use crate::attributes::debugger::DebuggerViualizerParser;
3737
use crate::attributes::deprecation::DeprecationParser;
38+
use crate::attributes::diagnostic::on_unimplemented::OnUnimplementedParser;
3839
use crate::attributes::do_not_recommend::DoNotRecommendParser;
3940
use crate::attributes::doc::DocParser;
4041
use crate::attributes::dummy::DummyParser;
@@ -186,6 +187,7 @@ attribute_parsers!(
186187
DocParser,
187188
MacroUseParser,
188189
NakedParser,
190+
OnUnimplementedParser,
189191
StabilityParser,
190192
UsedParser,
191193
// 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

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
1515
pub use rustc_target::spec::SanitizerSet;
1616
use thin_vec::ThinVec;
1717

18+
use crate::attrs::diagnostic::*;
1819
use crate::attrs::pretty_printing::PrintAttribute;
1920
use crate::limit::Limit;
2021
use crate::{DefaultBodyStability, PartialConstStability, RustcVersion, Stability};
@@ -939,6 +940,20 @@ pub enum AttributeKind {
939940
/// Represents `#[non_exhaustive]`
940941
NonExhaustive(Span),
941942

943+
/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
944+
OnUnimplemented {
945+
span: Span,
946+
/// None if the directive was malformed in some way.
947+
directive: Option<Box<OnUnimplementedDirective>>,
948+
},
949+
950+
/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
951+
OnUnimplemented {
952+
span: Span,
953+
/// None if the directive was malformed in some way.
954+
directive: Option<Box<OnUnimplementedDirective>>,
955+
},
956+
942957
/// Represents `#[optimize(size|speed)]`
943958
Optimize(OptimizeAttr, Span),
944959

0 commit comments

Comments
 (0)