rustc_attr_parsing/interface.rs
1use std::convert::identity;
2
3use rustc_ast as ast;
4use rustc_ast::token::DocFragmentKind;
5use rustc_ast::{AttrStyle, NodeId, Safety};
6use rustc_errors::DiagCtxtHandle;
7use rustc_feature::{AttributeTemplate, Features};
8use rustc_hir::attrs::AttributeKind;
9use rustc_hir::lints::AttributeLint;
10use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, Target};
11use rustc_session::Session;
12use rustc_session::lint::BuiltinLintDiag;
13use rustc_span::{DUMMY_SP, Span, Symbol, sym};
14
15use crate::context::{AcceptContext, FinalizeContext, SharedContext, Stage};
16use crate::parser::{ArgParser, PathParser, RefPathParser};
17use crate::session_diagnostics::ParsedDescription;
18use crate::{Early, Late, OmitDoc, ShouldEmit};
19
20/// Context created once, for example as part of the ast lowering
21/// context, through which all attributes can be lowered.
22pub struct AttributeParser<'sess, S: Stage = Late> {
23 pub(crate) tools: Vec<Symbol>,
24 pub(crate) features: Option<&'sess Features>,
25 pub(crate) sess: &'sess Session,
26 pub(crate) stage: S,
27
28 /// *Only* parse attributes with this symbol.
29 ///
30 /// Used in cases where we want the lowering infrastructure for parse just a single attribute.
31 parse_only: Option<Symbol>,
32}
33
34impl<'sess> AttributeParser<'sess, Early> {
35 /// This method allows you to parse attributes *before* you have access to features or tools.
36 /// One example where this is necessary, is to parse `feature` attributes themselves for
37 /// example.
38 ///
39 /// Try to use this as little as possible. Attributes *should* be lowered during
40 /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
41 /// crash if you tried to do so through [`parse_limited`](Self::parse_limited).
42 ///
43 /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
44 /// that symbol are picked out of the list of instructions and parsed. Those are returned.
45 ///
46 /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while
47 /// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed
48 /// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors
49 pub fn parse_limited(
50 sess: &'sess Session,
51 attrs: &[ast::Attribute],
52 sym: Symbol,
53 target_span: Span,
54 target_node_id: NodeId,
55 features: Option<&'sess Features>,
56 ) -> Option<Attribute> {
57 Self::parse_limited_should_emit(
58 sess,
59 attrs,
60 sym,
61 target_span,
62 target_node_id,
63 features,
64 ShouldEmit::Nothing,
65 )
66 }
67
68 /// This does the same as `parse_limited`, except it has a `should_emit` parameter which allows it to emit errors.
69 /// Usually you want `parse_limited`, which emits no errors.
70 pub fn parse_limited_should_emit(
71 sess: &'sess Session,
72 attrs: &[ast::Attribute],
73 sym: Symbol,
74 target_span: Span,
75 target_node_id: NodeId,
76 features: Option<&'sess Features>,
77 should_emit: ShouldEmit,
78 ) -> Option<Attribute> {
79 let mut parsed = Self::parse_limited_all(
80 sess,
81 attrs,
82 Some(sym),
83 Target::Crate, // Does not matter, we're not going to emit errors anyways
84 target_span,
85 target_node_id,
86 features,
87 should_emit,
88 );
89 assert!(parsed.len() <= 1);
90 parsed.pop()
91 }
92
93 /// This method allows you to parse a list of attributes *before* `rustc_ast_lowering`.
94 /// This can be used for attributes that would be removed before `rustc_ast_lowering`, such as attributes on macro calls.
95 ///
96 /// Try to use this as little as possible. Attributes *should* be lowered during
97 /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
98 /// crash if you tried to do so through [`parse_limited_all`](Self::parse_limited_all).
99 /// Therefore, if `parse_only` is None, then features *must* be provided.
100 pub fn parse_limited_all(
101 sess: &'sess Session,
102 attrs: &[ast::Attribute],
103 parse_only: Option<Symbol>,
104 target: Target,
105 target_span: Span,
106 target_node_id: NodeId,
107 features: Option<&'sess Features>,
108 emit_errors: ShouldEmit,
109 ) -> Vec<Attribute> {
110 let mut p =
111 Self { features, tools: Vec::new(), parse_only, sess, stage: Early { emit_errors } };
112 p.parse_attribute_list(
113 attrs,
114 target_span,
115 target_node_id,
116 target,
117 OmitDoc::Skip,
118 std::convert::identity,
119 |lint| {
120 sess.psess.buffer_lint(
121 lint.lint_id.lint,
122 lint.span,
123 lint.id,
124 BuiltinLintDiag::AttributeLint(lint.kind),
125 )
126 },
127 )
128 }
129
130 /// This method parses a single attribute, using `parse_fn`.
131 /// This is useful if you already know what exact attribute this is, and want to parse it.
132 pub fn parse_single<T>(
133 sess: &'sess Session,
134 attr: &ast::Attribute,
135 target_span: Span,
136 target_node_id: NodeId,
137 features: Option<&'sess Features>,
138 emit_errors: ShouldEmit,
139 parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser) -> Option<T>,
140 template: &AttributeTemplate,
141 ) -> Option<T> {
142 let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
143 panic!("parse_single called on a doc attr")
144 };
145 let parts =
146 normal_attr.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
147
148 let path = AttrPath::from_ast(&normal_attr.item.path, identity);
149 let args =
150 ArgParser::from_attr_args(&normal_attr.item.args, &parts, &sess.psess, emit_errors)?;
151 Self::parse_single_args(
152 sess,
153 attr.span,
154 normal_attr.item.span(),
155 attr.style,
156 path,
157 Some(normal_attr.item.unsafety),
158 ParsedDescription::Attribute,
159 target_span,
160 target_node_id,
161 features,
162 emit_errors,
163 &args,
164 parse_fn,
165 template,
166 )
167 }
168
169 /// This method is equivalent to `parse_single`, but parses arguments using `parse_fn` using manually created `args`.
170 /// This is useful when you want to parse other things than attributes using attribute parsers.
171 pub fn parse_single_args<T, I>(
172 sess: &'sess Session,
173 attr_span: Span,
174 inner_span: Span,
175 attr_style: AttrStyle,
176 attr_path: AttrPath,
177 attr_safety: Option<Safety>,
178 parsed_description: ParsedDescription,
179 target_span: Span,
180 target_node_id: NodeId,
181 features: Option<&'sess Features>,
182 emit_errors: ShouldEmit,
183 args: &I,
184 parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &I) -> T,
185 template: &AttributeTemplate,
186 ) -> T {
187 let mut parser = Self {
188 features,
189 tools: Vec::new(),
190 parse_only: None,
191 sess,
192 stage: Early { emit_errors },
193 };
194 let mut emit_lint = |lint: AttributeLint<NodeId>| {
195 sess.psess.buffer_lint(
196 lint.lint_id.lint,
197 lint.span,
198 lint.id,
199 BuiltinLintDiag::AttributeLint(lint.kind),
200 )
201 };
202 if let Some(safety) = attr_safety {
203 parser.check_attribute_safety(
204 &attr_path,
205 inner_span,
206 safety,
207 &mut emit_lint,
208 target_node_id,
209 )
210 }
211 let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext {
212 shared: SharedContext {
213 cx: &mut parser,
214 target_span,
215 target_id: target_node_id,
216 emit_lint: &mut emit_lint,
217 },
218 attr_span,
219 inner_span,
220 attr_style,
221 parsed_description,
222 template,
223 attr_path,
224 };
225 parse_fn(&mut cx, args)
226 }
227}
228
229impl<'sess, S: Stage> AttributeParser<'sess, S> {
230 pub fn new(
231 sess: &'sess Session,
232 features: &'sess Features,
233 tools: Vec<Symbol>,
234 stage: S,
235 ) -> Self {
236 Self { features: Some(features), tools, parse_only: None, sess, stage }
237 }
238
239 pub(crate) fn sess(&self) -> &'sess Session {
240 &self.sess
241 }
242
243 pub(crate) fn features(&self) -> &'sess Features {
244 self.features.expect("features not available at this point in the compiler")
245 }
246
247 pub(crate) fn features_option(&self) -> Option<&'sess Features> {
248 self.features
249 }
250
251 pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> {
252 self.sess().dcx()
253 }
254
255 /// Parse a list of attributes.
256 ///
257 /// `target_span` is the span of the thing this list of attributes is applied to,
258 /// and when `omit_doc` is set, doc attributes are filtered out.
259 pub fn parse_attribute_list(
260 &mut self,
261 attrs: &[ast::Attribute],
262 target_span: Span,
263 target_id: S::Id,
264 target: Target,
265 omit_doc: OmitDoc,
266
267 lower_span: impl Copy + Fn(Span) -> Span,
268 mut emit_lint: impl FnMut(AttributeLint<S::Id>),
269 ) -> Vec<Attribute> {
270 let mut attributes = Vec::new();
271 let mut attr_paths: Vec<RefPathParser<'_>> = Vec::new();
272
273 for attr in attrs {
274 // If we're only looking for a single attribute, skip all the ones we don't care about.
275 if let Some(expected) = self.parse_only {
276 if !attr.has_name(expected) {
277 continue;
278 }
279 }
280
281 // Sometimes, for example for `#![doc = include_str!("readme.md")]`,
282 // doc still contains a non-literal. You might say, when we're lowering attributes
283 // that's expanded right? But no, sometimes, when parsing attributes on macros,
284 // we already use the lowering logic and these are still there. So, when `omit_doc`
285 // is set we *also* want to ignore these.
286 let is_doc_attribute = attr.has_name(sym::doc);
287 if omit_doc == OmitDoc::Skip && is_doc_attribute {
288 continue;
289 }
290
291 match &attr.kind {
292 ast::AttrKind::DocComment(comment_kind, symbol) => {
293 if omit_doc == OmitDoc::Skip {
294 continue;
295 }
296
297 attributes.push(Attribute::Parsed(AttributeKind::DocComment {
298 style: attr.style,
299 kind: DocFragmentKind::Sugared(*comment_kind),
300 span: lower_span(attr.span),
301 comment: *symbol,
302 }))
303 }
304 ast::AttrKind::Normal(n) => {
305 attr_paths.push(PathParser(&n.item.path));
306 let attr_path = AttrPath::from_ast(&n.item.path, lower_span);
307
308 self.check_attribute_safety(
309 &attr_path,
310 lower_span(n.item.span()),
311 n.item.unsafety,
312 &mut emit_lint,
313 target_id,
314 );
315
316 let parts =
317 n.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
318
319 if let Some(accepts) = S::parsers().accepters.get(parts.as_slice()) {
320 let Some(args) = ArgParser::from_attr_args(
321 &n.item.args,
322 &parts,
323 &self.sess.psess,
324 self.stage.should_emit(),
325 ) else {
326 continue;
327 };
328
329 // Special-case handling for `#[doc = "..."]`: if we go through with
330 // `DocParser`, the order of doc comments will be messed up because `///`
331 // doc comments are added into `attributes` whereas attributes parsed with
332 // `DocParser` are added into `parsed_attributes` which are then appended
333 // to `attributes`. So if you have:
334 //
335 // /// bla
336 // #[doc = "a"]
337 // /// blob
338 //
339 // You would get:
340 //
341 // bla
342 // blob
343 // a
344 if is_doc_attribute
345 && let ArgParser::NameValue(nv) = &args
346 // If not a string key/value, it should emit an error, but to make
347 // things simpler, it's handled in `DocParser` because it's simpler to
348 // emit an error with `AcceptContext`.
349 && let Some(comment) = nv.value_as_str()
350 {
351 attributes.push(Attribute::Parsed(AttributeKind::DocComment {
352 style: attr.style,
353 kind: DocFragmentKind::Raw(nv.value_span),
354 span: attr.span,
355 comment,
356 }));
357 continue;
358 }
359
360 for accept in accepts {
361 let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
362 shared: SharedContext {
363 cx: self,
364 target_span,
365 target_id,
366 emit_lint: &mut emit_lint,
367 },
368 attr_span: lower_span(attr.span),
369 inner_span: lower_span(n.item.span()),
370 attr_style: attr.style,
371 parsed_description: ParsedDescription::Attribute,
372 template: &accept.template,
373 attr_path: attr_path.clone(),
374 };
375
376 (accept.accept_fn)(&mut cx, &args);
377 if !matches!(cx.stage.should_emit(), ShouldEmit::Nothing) {
378 Self::check_target(&accept.allowed_targets, target, &mut cx);
379 }
380 }
381 } else {
382 // If we're here, we must be compiling a tool attribute... Or someone
383 // forgot to parse their fancy new attribute. Let's warn them in any case.
384 // If you are that person, and you really think your attribute should
385 // remain unparsed, carefully read the documentation in this module and if
386 // you still think so you can add an exception to this assertion.
387
388 // FIXME(jdonszelmann): convert other attributes, and check with this that
389 // we caught em all
390 // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg];
391 // assert!(
392 // self.tools.contains(&parts[0]) || true,
393 // // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]),
394 // "attribute {path} wasn't parsed and isn't a know tool attribute",
395 // );
396
397 attributes.push(Attribute::Unparsed(Box::new(AttrItem {
398 path: attr_path.clone(),
399 args: self.lower_attr_args(&n.item.args, lower_span),
400 id: HashIgnoredAttrId { attr_id: attr.id },
401 style: attr.style,
402 span: lower_span(attr.span),
403 })));
404 }
405 }
406 }
407 }
408
409 let mut parsed_attributes = Vec::new();
410 for f in &S::parsers().finalizers {
411 if let Some(attr) = f(&mut FinalizeContext {
412 shared: SharedContext {
413 cx: self,
414 target_span,
415 target_id,
416 emit_lint: &mut emit_lint,
417 },
418 all_attrs: &attr_paths,
419 }) {
420 parsed_attributes.push(Attribute::Parsed(attr));
421 }
422 }
423
424 attributes.extend(parsed_attributes);
425
426 attributes
427 }
428
429 /// Returns whether there is a parser for an attribute with this name
430 pub fn is_parsed_attribute(path: &[Symbol]) -> bool {
431 Late::parsers().accepters.contains_key(path)
432 }
433
434 fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs {
435 match args {
436 ast::AttrArgs::Empty => AttrArgs::Empty,
437 ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()),
438 // This is an inert key-value attribute - it will never be visible to macros
439 // after it gets lowered to HIR. Therefore, we can extract literals to handle
440 // nonterminals in `#[doc]` (e.g. `#[doc = $e]`).
441 ast::AttrArgs::Eq { eq_span, expr } => {
442 // In valid code the value always ends up as a single literal. Otherwise, a dummy
443 // literal suffices because the error is handled elsewhere.
444 let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind
445 && let Ok(lit) =
446 ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span))
447 {
448 lit
449 } else {
450 let guar = self.dcx().span_delayed_bug(
451 args.span().unwrap_or(DUMMY_SP),
452 "expr in place where literal is expected (builtin attr parsing)",
453 );
454 ast::MetaItemLit {
455 symbol: sym::dummy,
456 suffix: None,
457 kind: ast::LitKind::Err(guar),
458 span: DUMMY_SP,
459 }
460 };
461 AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit }
462 }
463 }
464 }
465}