rustc_attr_parsing/context.rs
1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::ops::Deref;
4use std::sync::LazyLock;
5
6use rustc_ast as ast;
7use rustc_attr_data_structures::AttributeKind;
8use rustc_errors::{DiagCtxtHandle, Diagnostic};
9use rustc_feature::Features;
10use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId};
11use rustc_session::Session;
12use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
13
14use crate::attributes::allow_unstable::{AllowConstFnUnstableParser, AllowInternalUnstableParser};
15use crate::attributes::confusables::ConfusablesParser;
16use crate::attributes::deprecation::DeprecationParser;
17use crate::attributes::repr::ReprParser;
18use crate::attributes::stability::{
19 BodyStabilityParser, ConstStabilityIndirectParser, ConstStabilityParser, StabilityParser,
20};
21use crate::attributes::transparency::TransparencyParser;
22use crate::attributes::{AttributeParser as _, Combine, Single};
23use crate::parser::{ArgParser, MetaItemParser};
24
25macro_rules! attribute_groups {
26 (
27 pub(crate) static $name: ident = [$($names: ty),* $(,)?];
28 ) => {
29 type Accepts = BTreeMap<
30 &'static [Symbol],
31 Box<dyn Send + Sync + Fn(&AcceptContext<'_>, &ArgParser<'_>)>
32 >;
33 type Finalizes = Vec<
34 Box<dyn Send + Sync + Fn(&FinalizeContext<'_>) -> Option<AttributeKind>>
35 >;
36 pub(crate) static $name: LazyLock<(Accepts, Finalizes)> = LazyLock::new(|| {
37 let mut accepts = Accepts::new();
38 let mut finalizes = Finalizes::new();
39 $(
40 {
41 thread_local! {
42 static STATE_OBJECT: RefCell<$names> = RefCell::new(<$names>::default());
43 };
44
45 for (k, v) in <$names>::ATTRIBUTES {
46 let old = accepts.insert(*k, Box::new(|cx, args| {
47 STATE_OBJECT.with_borrow_mut(|s| {
48 v(s, cx, args)
49 })
50 }));
51 assert!(old.is_none());
52 }
53
54 finalizes.push(Box::new(|cx| {
55 let state = STATE_OBJECT.take();
56 state.finalize(cx)
57 }));
58 }
59 )*
60
61 (accepts, finalizes)
62 });
63 };
64}
65
66attribute_groups!(
67 pub(crate) static ATTRIBUTE_MAPPING = [
68 // tidy-alphabetical-start
69 BodyStabilityParser,
70 ConfusablesParser,
71 ConstStabilityParser,
72 StabilityParser,
73 // tidy-alphabetical-end
74
75 // tidy-alphabetical-start
76 Combine<AllowConstFnUnstableParser>,
77 Combine<AllowInternalUnstableParser>,
78 Combine<ReprParser>,
79 // tidy-alphabetical-end
80
81 // tidy-alphabetical-start
82 Single<ConstStabilityIndirectParser>,
83 Single<DeprecationParser>,
84 Single<TransparencyParser>,
85 // tidy-alphabetical-end
86 ];
87);
88
89/// Context given to every attribute parser when accepting
90///
91/// Gives [`AttributeParser`]s enough information to create errors, for example.
92pub(crate) struct AcceptContext<'a> {
93 pub(crate) group_cx: &'a FinalizeContext<'a>,
94 /// The span of the attribute currently being parsed
95 pub(crate) attr_span: Span,
96}
97
98impl<'a> AcceptContext<'a> {
99 pub(crate) fn emit_err(&self, diag: impl Diagnostic<'a>) -> ErrorGuaranteed {
100 if self.limit_diagnostics {
101 self.dcx().create_err(diag).delay_as_bug()
102 } else {
103 self.dcx().emit_err(diag)
104 }
105 }
106}
107
108impl<'a> Deref for AcceptContext<'a> {
109 type Target = FinalizeContext<'a>;
110
111 fn deref(&self) -> &Self::Target {
112 &self.group_cx
113 }
114}
115
116/// Context given to every attribute parser during finalization.
117///
118/// Gives [`AttributeParser`](crate::attributes::AttributeParser)s enough information to create
119/// errors, for example.
120pub(crate) struct FinalizeContext<'a> {
121 /// The parse context, gives access to the session and the
122 /// diagnostics context.
123 pub(crate) cx: &'a AttributeParser<'a>,
124 /// The span of the syntactical component this attribute was applied to
125 pub(crate) target_span: Span,
126}
127
128impl<'a> Deref for FinalizeContext<'a> {
129 type Target = AttributeParser<'a>;
130
131 fn deref(&self) -> &Self::Target {
132 &self.cx
133 }
134}
135
136#[derive(PartialEq, Clone, Copy, Debug)]
137pub enum OmitDoc {
138 Lower,
139 Skip,
140}
141
142/// Context created once, for example as part of the ast lowering
143/// context, through which all attributes can be lowered.
144pub struct AttributeParser<'sess> {
145 #[expect(dead_code)] // FIXME(jdonszelmann): needed later to verify we parsed all attributes
146 tools: Vec<Symbol>,
147 sess: &'sess Session,
148 features: Option<&'sess Features>,
149
150 /// *Only* parse attributes with this symbol.
151 ///
152 /// Used in cases where we want the lowering infrastructure for parse just a single attribute.
153 parse_only: Option<Symbol>,
154
155 /// Can be used to instruct parsers to reduce the number of diagnostics it emits.
156 /// Useful when using `parse_limited` and you know the attr will be reparsed later.
157 pub(crate) limit_diagnostics: bool,
158}
159
160impl<'sess> AttributeParser<'sess> {
161 /// This method allows you to parse attributes *before* you have access to features or tools.
162 /// One example where this is necessary, is to parse `feature` attributes themselves for
163 /// example.
164 ///
165 /// Try to use this as little as possible. Attributes *should* be lowered during
166 /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
167 /// crash if you tried to do so through [`parse_limited`](Self::parse_limited).
168 ///
169 /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
170 /// that symbol are picked out of the list of instructions and parsed. Those are returned.
171 pub fn parse_limited(
172 sess: &'sess Session,
173 attrs: &[ast::Attribute],
174 sym: Symbol,
175 target_span: Span,
176 limit_diagnostics: bool,
177 ) -> Option<Attribute> {
178 let mut parsed = Self {
179 sess,
180 features: None,
181 tools: Vec::new(),
182 parse_only: Some(sym),
183 limit_diagnostics,
184 }
185 .parse_attribute_list(attrs, target_span, OmitDoc::Skip, std::convert::identity);
186
187 assert!(parsed.len() <= 1);
188
189 parsed.pop()
190 }
191
192 pub fn new(sess: &'sess Session, features: &'sess Features, tools: Vec<Symbol>) -> Self {
193 Self { sess, features: Some(features), tools, parse_only: None, limit_diagnostics: false }
194 }
195
196 pub(crate) fn sess(&self) -> &'sess Session {
197 self.sess
198 }
199
200 pub(crate) fn features(&self) -> &'sess Features {
201 self.features.expect("features not available at this point in the compiler")
202 }
203
204 pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> {
205 self.sess.dcx()
206 }
207
208 /// Parse a list of attributes.
209 ///
210 /// `target_span` is the span of the thing this list of attributes is applied to,
211 /// and when `omit_doc` is set, doc attributes are filtered out.
212 pub fn parse_attribute_list<'a>(
213 &'a self,
214 attrs: &'a [ast::Attribute],
215 target_span: Span,
216 omit_doc: OmitDoc,
217
218 lower_span: impl Copy + Fn(Span) -> Span,
219 ) -> Vec<Attribute> {
220 let mut attributes = Vec::new();
221
222 let group_cx = FinalizeContext { cx: self, target_span };
223
224 for attr in attrs {
225 // If we're only looking for a single attribute, skip all the ones we don't care about.
226 if let Some(expected) = self.parse_only {
227 if !attr.has_name(expected) {
228 continue;
229 }
230 }
231
232 // Sometimes, for example for `#![doc = include_str!("readme.md")]`,
233 // doc still contains a non-literal. You might say, when we're lowering attributes
234 // that's expanded right? But no, sometimes, when parsing attributes on macros,
235 // we already use the lowering logic and these are still there. So, when `omit_doc`
236 // is set we *also* want to ignore these.
237 if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) {
238 continue;
239 }
240
241 match &attr.kind {
242 ast::AttrKind::DocComment(comment_kind, symbol) => {
243 if omit_doc == OmitDoc::Skip {
244 continue;
245 }
246
247 attributes.push(Attribute::Parsed(AttributeKind::DocComment {
248 style: attr.style,
249 kind: *comment_kind,
250 span: lower_span(attr.span),
251 comment: *symbol,
252 }))
253 }
254 // // FIXME: make doc attributes go through a proper attribute parser
255 // ast::AttrKind::Normal(n) if n.has_name(sym::doc) => {
256 // let p = GenericMetaItemParser::from_attr(&n, self.dcx());
257 //
258 // attributes.push(Attribute::Parsed(AttributeKind::DocComment {
259 // style: attr.style,
260 // kind: CommentKind::Line,
261 // span: attr.span,
262 // comment: p.args().name_value(),
263 // }))
264 // }
265 ast::AttrKind::Normal(n) => {
266 let parser = MetaItemParser::from_attr(n, self.dcx());
267 let (path, args) = parser.deconstruct();
268 let parts = path.segments().map(|i| i.name).collect::<Vec<_>>();
269
270 if let Some(accept) = ATTRIBUTE_MAPPING.0.get(parts.as_slice()) {
271 let cx =
272 AcceptContext { group_cx: &group_cx, attr_span: lower_span(attr.span) };
273
274 accept(&cx, &args)
275 } else {
276 // If we're here, we must be compiling a tool attribute... Or someone
277 // forgot to parse their fancy new attribute. Let's warn them in any case.
278 // If you are that person, and you really think your attribute should
279 // remain unparsed, carefully read the documentation in this module and if
280 // you still think so you can add an exception to this assertion.
281
282 // FIXME(jdonszelmann): convert other attributes, and check with this that
283 // we caught em all
284 // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg];
285 // assert!(
286 // self.tools.contains(&parts[0]) || true,
287 // // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]),
288 // "attribute {path} wasn't parsed and isn't a know tool attribute",
289 // );
290
291 attributes.push(Attribute::Unparsed(Box::new(AttrItem {
292 path: AttrPath::from_ast(&n.item.path),
293 args: self.lower_attr_args(&n.item.args, lower_span),
294 id: HashIgnoredAttrId { attr_id: attr.id },
295 style: attr.style,
296 span: lower_span(attr.span),
297 })));
298 }
299 }
300 }
301 }
302
303 let mut parsed_attributes = Vec::new();
304 for f in &ATTRIBUTE_MAPPING.1 {
305 if let Some(attr) = f(&group_cx) {
306 parsed_attributes.push(Attribute::Parsed(attr));
307 }
308 }
309
310 attributes.extend(parsed_attributes);
311
312 attributes
313 }
314
315 fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs {
316 match args {
317 ast::AttrArgs::Empty => AttrArgs::Empty,
318 ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()),
319 // This is an inert key-value attribute - it will never be visible to macros
320 // after it gets lowered to HIR. Therefore, we can extract literals to handle
321 // nonterminals in `#[doc]` (e.g. `#[doc = $e]`).
322 ast::AttrArgs::Eq { eq_span, expr } => {
323 // In valid code the value always ends up as a single literal. Otherwise, a dummy
324 // literal suffices because the error is handled elsewhere.
325 let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind
326 && let Ok(lit) =
327 ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span))
328 {
329 lit
330 } else {
331 let guar = self.dcx().span_delayed_bug(
332 args.span().unwrap_or(DUMMY_SP),
333 "expr in place where literal is expected (builtin attr parsing)",
334 );
335 ast::MetaItemLit {
336 symbol: sym::dummy,
337 suffix: None,
338 kind: ast::LitKind::Err(guar),
339 span: DUMMY_SP,
340 }
341 };
342 AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit }
343 }
344 }
345 }
346}