Skip to content

Commit 78948ac

Browse files
committed
Auto merge of #138515 - petrochenkov:cfgtrace, r=nnethercote
expand: Leave traces when expanding `cfg_attr` attributes Currently `cfg_trace` just disappears during expansion, but after this PR `#[cfg_attr(some tokens)]` will leave a `#[cfg_attr_trace(some tokens)]` attribute instead of itself in AST after expansion (the new attribute is built-in and inert, its inner tokens are the same as in the original attribute). This trace attribute can then be used by lints or other diagnostics, #133823 has some examples. Tokens in these trace attributes are set to an empty token stream, so the traces are non-existent for proc macros and cannot affect any user-observable behavior. This is also a weakness, because if a proc macro processes some code with the trace attributes, they will be lost, so the traces are best effort rather than precise. The next step is to do the same thing with `cfg` attributes (`#[cfg(TRUE)]` currently remains in both AST and tokens after expanding, it should be replaced with a trace instead). The idea belongs to `@estebank.`
2 parents d8e44b7 + 9dd4e4c commit 78948ac

File tree

10 files changed

+136
-19
lines changed

10 files changed

+136
-19
lines changed

compiler/rustc_ast_passes/src/ast_validation.rs

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ impl<'a> AstValidator<'a> {
336336
sym::allow,
337337
sym::cfg,
338338
sym::cfg_attr,
339+
sym::cfg_attr_trace,
339340
sym::deny,
340341
sym::expect,
341342
sym::forbid,

compiler/rustc_ast_pretty/src/pprust/state.rs

+13-6
Original file line numberDiff line numberDiff line change
@@ -578,11 +578,12 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
578578
let mut printed = false;
579579
for attr in attrs {
580580
if attr.style == kind {
581-
self.print_attribute_inline(attr, is_inline);
582-
if is_inline {
583-
self.nbsp();
581+
if self.print_attribute_inline(attr, is_inline) {
582+
if is_inline {
583+
self.nbsp();
584+
}
585+
printed = true;
584586
}
585-
printed = true;
586587
}
587588
}
588589
if printed && trailing_hardbreak && !is_inline {
@@ -591,7 +592,12 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
591592
printed
592593
}
593594

594-
fn print_attribute_inline(&mut self, attr: &ast::Attribute, is_inline: bool) {
595+
fn print_attribute_inline(&mut self, attr: &ast::Attribute, is_inline: bool) -> bool {
596+
if attr.has_name(sym::cfg_attr_trace) {
597+
// It's not a valid identifier, so avoid printing it
598+
// to keep the printed code reasonably parse-able.
599+
return false;
600+
}
595601
if !is_inline {
596602
self.hardbreak_if_not_bol();
597603
}
@@ -610,6 +616,7 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
610616
self.hardbreak()
611617
}
612618
}
619+
true
613620
}
614621

615622
fn print_attr_item(&mut self, item: &ast::AttrItem, span: Span) {
@@ -2047,7 +2054,7 @@ impl<'a> State<'a> {
20472054
}
20482055

20492056
fn print_attribute(&mut self, attr: &ast::Attribute) {
2050-
self.print_attribute_inline(attr, false)
2057+
self.print_attribute_inline(attr, false);
20512058
}
20522059

20532060
fn print_meta_list_item(&mut self, item: &ast::MetaItemInner) {

compiler/rustc_expand/src/config.rs

+25-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
//! Conditional compilation stripping.
22
3+
use std::iter;
4+
35
use rustc_ast::ptr::P;
46
use rustc_ast::token::{Delimiter, Token, TokenKind};
57
use rustc_ast::tokenstream::{
68
AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree,
79
};
810
use rustc_ast::{
9-
self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, MetaItemInner, NodeId,
11+
self as ast, AttrKind, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, MetaItemInner,
12+
NodeId, NormalAttr,
1013
};
1114
use rustc_attr_parsing as attr;
1215
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
@@ -275,10 +278,23 @@ impl<'a> StripUnconfigured<'a> {
275278
pub(crate) fn expand_cfg_attr(&self, cfg_attr: &Attribute, recursive: bool) -> Vec<Attribute> {
276279
validate_attr::check_attribute_safety(&self.sess.psess, AttributeSafety::Normal, &cfg_attr);
277280

281+
// A trace attribute left in AST in place of the original `cfg_attr` attribute.
282+
// It can later be used by lints or other diagnostics.
283+
let mut trace_attr = cfg_attr.clone();
284+
match &mut trace_attr.kind {
285+
AttrKind::Normal(normal) => {
286+
let NormalAttr { item, tokens } = &mut **normal;
287+
item.path.segments[0].ident.name = sym::cfg_attr_trace;
288+
// This makes the trace attributes unobservable to token-based proc macros.
289+
*tokens = Some(LazyAttrTokenStream::new(AttrTokenStream::default()));
290+
}
291+
AttrKind::DocComment(..) => unreachable!(),
292+
}
293+
278294
let Some((cfg_predicate, expanded_attrs)) =
279295
rustc_parse::parse_cfg_attr(cfg_attr, &self.sess.psess)
280296
else {
281-
return vec![];
297+
return vec![trace_attr];
282298
};
283299

284300
// Lint on zero attributes in source.
@@ -292,22 +308,21 @@ impl<'a> StripUnconfigured<'a> {
292308
}
293309

294310
if !attr::cfg_matches(&cfg_predicate, &self.sess, self.lint_node_id, self.features) {
295-
return vec![];
311+
return vec![trace_attr];
296312
}
297313

298314
if recursive {
299315
// We call `process_cfg_attr` recursively in case there's a
300316
// `cfg_attr` inside of another `cfg_attr`. E.g.
301317
// `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
302-
expanded_attrs
318+
let expanded_attrs = expanded_attrs
303319
.into_iter()
304-
.flat_map(|item| self.process_cfg_attr(&self.expand_cfg_attr_item(cfg_attr, item)))
305-
.collect()
320+
.flat_map(|item| self.process_cfg_attr(&self.expand_cfg_attr_item(cfg_attr, item)));
321+
iter::once(trace_attr).chain(expanded_attrs).collect()
306322
} else {
307-
expanded_attrs
308-
.into_iter()
309-
.map(|item| self.expand_cfg_attr_item(cfg_attr, item))
310-
.collect()
323+
let expanded_attrs =
324+
expanded_attrs.into_iter().map(|item| self.expand_cfg_attr_item(cfg_attr, item));
325+
iter::once(trace_attr).chain(expanded_attrs).collect()
311326
}
312327
}
313328

compiler/rustc_feature/src/builtin_attrs.rs

+8
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
752752
template!(Word, List: r#""...""#), DuplicatesOk,
753753
EncodeCrossCrate::Yes, INTERNAL_UNSTABLE
754754
),
755+
// Trace that is left when a `cfg_attr` attribute is expanded.
756+
// The attribute is not gated, to avoid stability errors, but it cannot be used in stable or
757+
// unstable code directly because `sym::cfg_attr_trace` is not a valid identifier, it can only
758+
// be generated by the compiler.
759+
ungated!(
760+
cfg_attr_trace, Normal, template!(Word /* irrelevant */), DuplicatesOk,
761+
EncodeCrossCrate::No
762+
),
755763

756764
// ==========================================================================
757765
// Internal attributes, Diagnostics related:

compiler/rustc_parse/src/validate_attr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_span::{Span, Symbol, sym};
1616
use crate::{errors, parse_in};
1717

1818
pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
19-
if attr.is_doc_comment() {
19+
if attr.is_doc_comment() || attr.has_name(sym::cfg_attr_trace) {
2020
return;
2121
}
2222

compiler/rustc_passes/src/check_attr.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
272272
| sym::forbid
273273
| sym::cfg
274274
| sym::cfg_attr
275+
| sym::cfg_attr_trace
275276
// need to be fixed
276277
| sym::cfi_encoding // FIXME(cfi_encoding)
277278
| sym::pointee // FIXME(derive_coerce_pointee)
@@ -575,6 +576,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
575576
// conditional compilation
576577
sym::cfg,
577578
sym::cfg_attr,
579+
sym::cfg_attr_trace,
578580
// testing (allowed here so better errors can be generated in `rustc_builtin_macros::test`)
579581
sym::test,
580582
sym::ignore,
@@ -2641,7 +2643,7 @@ impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {
26412643
// only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed
26422644
// if we allow more attributes (e.g., tool attributes and `allow/deny/warn`)
26432645
// in where clauses. After that, only `self.check_attributes` should be enough.
2644-
const ATTRS_ALLOWED: &[Symbol] = &[sym::cfg, sym::cfg_attr];
2646+
const ATTRS_ALLOWED: &[Symbol] = &[sym::cfg, sym::cfg_attr, sym::cfg_attr_trace];
26452647
let spans = self
26462648
.tcx
26472649
.hir_attrs(where_predicate.hir_id)

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,7 @@ symbols! {
591591
cfg_accessible,
592592
cfg_attr,
593593
cfg_attr_multi,
594+
cfg_attr_trace: "<cfg_attr>", // must not be a valid identifier
594595
cfg_boolean_literals,
595596
cfg_contract_checks,
596597
cfg_doctest,

src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ fn check_duplicated_attr(
3636
}
3737
let Some(ident) = attr.ident() else { return };
3838
let name = ident.name;
39-
if name == sym::doc || name == sym::cfg_attr || name == sym::rustc_on_unimplemented || name == sym::reason {
39+
if name == sym::doc
40+
|| name == sym::cfg_attr
41+
|| name == sym::cfg_attr_trace
42+
|| name == sym::rustc_on_unimplemented
43+
|| name == sym::reason {
4044
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
4145
// conditions are the same.
4246
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.

tests/ui/proc-macro/cfg-attr-trace.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Ensure that `cfg_attr_trace` attributes aren't observable by proc-macros.
2+
3+
//@ check-pass
4+
//@ proc-macro: test-macros.rs
5+
6+
#![feature(cfg_eval)]
7+
8+
#[macro_use]
9+
extern crate test_macros;
10+
11+
#[cfg_eval]
12+
#[test_macros::print_attr]
13+
#[cfg_attr(FALSE, test_macros::print_attr)]
14+
#[cfg_attr(all(), test_macros::print_attr)]
15+
struct S;
16+
17+
fn main() {}
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
PRINT-ATTR INPUT (DISPLAY): #[test_macros::print_attr] struct S;
2+
PRINT-ATTR DEEP-RE-COLLECTED (DISPLAY): #[test_macros :: print_attr] struct S;
3+
PRINT-ATTR INPUT (DEBUG): TokenStream [
4+
Punct {
5+
ch: '#',
6+
spacing: Alone,
7+
span: #0 bytes(271..272),
8+
},
9+
Group {
10+
delimiter: Bracket,
11+
stream: TokenStream [
12+
Ident {
13+
ident: "test_macros",
14+
span: #0 bytes(289..300),
15+
},
16+
Punct {
17+
ch: ':',
18+
spacing: Joint,
19+
span: #0 bytes(300..301),
20+
},
21+
Punct {
22+
ch: ':',
23+
spacing: Alone,
24+
span: #0 bytes(301..302),
25+
},
26+
Ident {
27+
ident: "print_attr",
28+
span: #0 bytes(302..312),
29+
},
30+
],
31+
span: #0 bytes(272..314),
32+
},
33+
Ident {
34+
ident: "struct",
35+
span: #0 bytes(315..321),
36+
},
37+
Ident {
38+
ident: "S",
39+
span: #0 bytes(322..323),
40+
},
41+
Punct {
42+
ch: ';',
43+
spacing: Alone,
44+
span: #0 bytes(323..324),
45+
},
46+
]
47+
PRINT-ATTR INPUT (DISPLAY): struct S;
48+
PRINT-ATTR INPUT (DEBUG): TokenStream [
49+
Ident {
50+
ident: "struct",
51+
span: #0 bytes(315..321),
52+
},
53+
Ident {
54+
ident: "S",
55+
span: #0 bytes(322..323),
56+
},
57+
Punct {
58+
ch: ';',
59+
spacing: Alone,
60+
span: #0 bytes(323..324),
61+
},
62+
]

0 commit comments

Comments
 (0)