1use std::iter;
4
5use rustc_ast::token::{Delimiter, Token, TokenKind};
6use rustc_ast::tokenstream::{
7 AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree,
8};
9use rustc_ast::{
10 self as ast, AttrItemKind, AttrKind, AttrStyle, Attribute, EarlyParsedAttribute, HasAttrs,
11 HasTokens, MetaItem, MetaItemInner, NodeId, NormalAttr,
12};
13use rustc_attr_parsing as attr;
14use rustc_attr_parsing::{
15 AttributeParser, CFG_TEMPLATE, EvalConfigResult, ShouldEmit, eval_config_entry, parse_cfg,
16};
17use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
18use rustc_feature::{
19 ACCEPTED_LANG_FEATURES, EnabledLangFeature, EnabledLibFeature, Features, REMOVED_LANG_FEATURES,
20 UNSTABLE_LANG_FEATURES,
21};
22use rustc_hir::Target;
23use rustc_parse::parser::Recovery;
24use rustc_session::Session;
25use rustc_session::parse::feature_err;
26use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym};
27use thin_vec::ThinVec;
28use tracing::instrument;
29
30use crate::errors::{
31 CrateNameInCfgAttr, CrateTypeInCfgAttr, FeatureNotAllowed, FeatureRemoved,
32 FeatureRemovedReason, InvalidCfg, MalformedFeatureAttribute, MalformedFeatureAttributeHelp,
33 RemoveExprNotSupported,
34};
35
36pub struct StripUnconfigured<'a> {
38 pub sess: &'a Session,
39 pub features: Option<&'a Features>,
40 pub config_tokens: bool,
44 pub lint_node_id: NodeId,
45}
46
47pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) -> Features {
48 fn feature_list(attr: &Attribute) -> ThinVec<ast::MetaItemInner> {
49 if attr.has_name(sym::feature)
50 && let Some(list) = attr.meta_item_list()
51 {
52 list
53 } else {
54 ThinVec::new()
55 }
56 }
57
58 let mut features = Features::default();
59
60 for attr in krate_attrs {
62 for mi in feature_list(attr) {
63 let name = match mi.ident() {
64 Some(ident) if mi.is_word() => ident.name,
65 Some(ident) => {
66 sess.dcx().emit_err(MalformedFeatureAttribute {
67 span: mi.span(),
68 help: MalformedFeatureAttributeHelp::Suggestion {
69 span: mi.span(),
70 suggestion: ident.name,
71 },
72 });
73 continue;
74 }
75 None => {
76 sess.dcx().emit_err(MalformedFeatureAttribute {
77 span: mi.span(),
78 help: MalformedFeatureAttributeHelp::Label { span: mi.span() },
79 });
80 continue;
81 }
82 };
83
84 if let Some(f) = REMOVED_LANG_FEATURES.iter().find(|f| name == f.feature.name) {
86 let pull_note = if let Some(pull) = f.pull {
87 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("; see <https://github.com/rust-lang/rust/pull/{0}> for more information",
pull))
})format!(
88 "; see <https://github.com/rust-lang/rust/pull/{}> for more information",
89 pull
90 )
91 } else {
92 "".to_owned()
93 };
94 sess.dcx().emit_err(FeatureRemoved {
95 span: mi.span(),
96 reason: f.reason.map(|reason| FeatureRemovedReason { reason }),
97 removed_rustc_version: f.feature.since,
98 pull_note,
99 });
100 continue;
101 }
102
103 if let Some(f) = ACCEPTED_LANG_FEATURES.iter().find(|f| name == f.name) {
105 features.set_enabled_lang_feature(EnabledLangFeature {
106 gate_name: name,
107 attr_sp: mi.span(),
108 stable_since: Some(Symbol::intern(f.since)),
109 });
110 continue;
111 }
112
113 if let Some(allowed) = sess.opts.unstable_opts.allow_features.as_ref() {
117 if allowed.iter().all(|f| name.as_str() != f) {
118 sess.dcx().emit_err(FeatureNotAllowed { span: mi.span(), name });
119 continue;
120 }
121 }
122
123 if UNSTABLE_LANG_FEATURES.iter().find(|f| name == f.name).is_some() {
125 if features.internal(name) && !STDLIB_STABLE_CRATES.contains(&crate_name) {
130 sess.using_internal_features.store(true, std::sync::atomic::Ordering::Relaxed);
131 }
132
133 features.set_enabled_lang_feature(EnabledLangFeature {
134 gate_name: name,
135 attr_sp: mi.span(),
136 stable_since: None,
137 });
138 continue;
139 }
140
141 features
144 .set_enabled_lib_feature(EnabledLibFeature { gate_name: name, attr_sp: mi.span() });
145
146 if features.internal(name) && !STDLIB_STABLE_CRATES.contains(&crate_name) {
149 sess.using_internal_features.store(true, std::sync::atomic::Ordering::Relaxed);
150 }
151 }
152 }
153
154 features
155}
156
157pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec {
158 let strip_unconfigured = StripUnconfigured {
159 sess,
160 features: None,
161 config_tokens: false,
162 lint_node_id: ast::CRATE_NODE_ID,
163 };
164 attrs
165 .iter()
166 .flat_map(|attr| strip_unconfigured.process_cfg_attr(attr))
167 .take_while(|attr| {
168 !is_cfg(attr) || strip_unconfigured.cfg_true(attr, ShouldEmit::Nothing).as_bool()
169 })
170 .collect()
171}
172
173pub(crate) fn attr_into_trace(mut attr: Attribute, trace_name: Symbol) -> Attribute {
174 match &mut attr.kind {
175 AttrKind::Normal(normal) => {
176 let NormalAttr { item, tokens } = &mut **normal;
177 item.path.segments[0].ident.name = trace_name;
178 *tokens = Some(LazyAttrTokenStream::new_direct(AttrTokenStream::default()));
180 }
181 AttrKind::DocComment(..) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
182 }
183 attr
184}
185
186#[macro_export]
187macro_rules! configure {
188 ($this:ident, $node:ident) => {
189 match $this.configure($node) {
190 Some(node) => node,
191 None => return Default::default(),
192 }
193 };
194}
195
196impl<'a> StripUnconfigured<'a> {
197 pub fn configure<T: HasAttrs + HasTokens>(&self, mut node: T) -> Option<T> {
198 self.process_cfg_attrs(&mut node);
199 self.in_cfg(node.attrs()).then(|| {
200 self.try_configure_tokens(&mut node);
201 node
202 })
203 }
204
205 fn try_configure_tokens<T: HasTokens>(&self, node: &mut T) {
206 if self.config_tokens {
207 if let Some(Some(tokens)) = node.tokens_mut() {
208 let attr_stream = tokens.to_attr_token_stream();
209 *tokens = LazyAttrTokenStream::new_direct(self.configure_tokens(&attr_stream));
210 }
211 }
212 }
213
214 fn configure_tokens(&self, stream: &AttrTokenStream) -> AttrTokenStream {
219 fn can_skip(stream: &AttrTokenStream) -> bool {
220 stream.0.iter().all(|tree| match tree {
221 AttrTokenTree::AttrsTarget(_) => false,
222 AttrTokenTree::Token(..) => true,
223 AttrTokenTree::Delimited(.., inner) => can_skip(inner),
224 })
225 }
226
227 if can_skip(stream) {
228 return stream.clone();
229 }
230
231 let trees: Vec<_> = stream
232 .0
233 .iter()
234 .filter_map(|tree| match tree.clone() {
235 AttrTokenTree::AttrsTarget(mut target) => {
236 target.attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr));
238
239 if self.in_cfg(&target.attrs) {
240 target.tokens = LazyAttrTokenStream::new_direct(
241 self.configure_tokens(&target.tokens.to_attr_token_stream()),
242 );
243 Some(AttrTokenTree::AttrsTarget(target))
244 } else {
245 None
248 }
249 }
250 AttrTokenTree::Delimited(sp, spacing, delim, mut inner) => {
251 inner = self.configure_tokens(&inner);
252 Some(AttrTokenTree::Delimited(sp, spacing, delim, inner))
253 }
254 AttrTokenTree::Token(Token { kind, .. }, _) if kind.is_delim() => {
255 {
::core::panicking::panic_fmt(format_args!("Should be `AttrTokenTree::Delimited`, not delim tokens: {0:?}",
tree));
};panic!("Should be `AttrTokenTree::Delimited`, not delim tokens: {:?}", tree);
256 }
257 AttrTokenTree::Token(token, spacing) => Some(AttrTokenTree::Token(token, spacing)),
258 })
259 .collect();
260 AttrTokenStream::new(trees)
261 }
262
263 fn process_cfg_attrs<T: HasAttrs>(&self, node: &mut T) {
270 node.visit_attrs(|attrs| {
271 attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr));
272 });
273 }
274
275 fn process_cfg_attr(&self, attr: &Attribute) -> Vec<Attribute> {
276 if attr.has_name(sym::cfg_attr) {
277 self.expand_cfg_attr(attr, true)
278 } else {
279 <[_]>::into_vec(::alloc::boxed::box_new([attr.clone()]))vec![attr.clone()]
280 }
281 }
282
283 pub(crate) fn expand_cfg_attr(&self, cfg_attr: &Attribute, recursive: bool) -> Vec<Attribute> {
291 let mut trace_attr = cfg_attr.clone();
294 trace_attr.replace_args(AttrItemKind::Parsed(EarlyParsedAttribute::CfgAttrTrace));
295 let trace_attr = attr_into_trace(trace_attr, sym::cfg_attr_trace);
296
297 let Some((cfg_predicate, expanded_attrs)) =
298 rustc_attr_parsing::parse_cfg_attr(cfg_attr, &self.sess, self.features)
299 else {
300 return <[_]>::into_vec(::alloc::boxed::box_new([trace_attr]))vec![trace_attr];
301 };
302
303 if expanded_attrs.is_empty() {
305 self.sess.psess.buffer_lint(
306 rustc_lint_defs::builtin::UNUSED_ATTRIBUTES,
307 cfg_attr.span,
308 ast::CRATE_NODE_ID,
309 crate::errors::CfgAttrNoAttributes,
310 );
311 }
312
313 if !attr::eval_config_entry(self.sess, &cfg_predicate).as_bool() {
314 return <[_]>::into_vec(::alloc::boxed::box_new([trace_attr]))vec![trace_attr];
315 }
316
317 if recursive {
318 let expanded_attrs = expanded_attrs
322 .into_iter()
323 .flat_map(|item| self.process_cfg_attr(&self.expand_cfg_attr_item(cfg_attr, item)));
324 iter::once(trace_attr).chain(expanded_attrs).collect()
325 } else {
326 let expanded_attrs =
327 expanded_attrs.into_iter().map(|item| self.expand_cfg_attr_item(cfg_attr, item));
328 iter::once(trace_attr).chain(expanded_attrs).collect()
329 }
330 }
331
332 fn expand_cfg_attr_item(
333 &self,
334 cfg_attr: &Attribute,
335 (item, item_span): (ast::AttrItem, Span),
336 ) -> Attribute {
337 let mut orig_trees = cfg_attr.token_trees().into_iter();
341 let Some(TokenTree::Token(pound_token @ Token { kind: TokenKind::Pound, .. }, _)) =
342 orig_trees.next()
343 else {
344 {
::core::panicking::panic_fmt(format_args!("Bad tokens for attribute {0:?}",
cfg_attr));
};panic!("Bad tokens for attribute {cfg_attr:?}");
345 };
346
347 let mut trees = if cfg_attr.style == AttrStyle::Inner {
349 let Some(TokenTree::Token(bang_token @ Token { kind: TokenKind::Bang, .. }, _)) =
350 orig_trees.next()
351 else {
352 {
::core::panicking::panic_fmt(format_args!("Bad tokens for attribute {0:?}",
cfg_attr));
};panic!("Bad tokens for attribute {cfg_attr:?}");
353 };
354 <[_]>::into_vec(::alloc::boxed::box_new([AttrTokenTree::Token(pound_token,
Spacing::Joint),
AttrTokenTree::Token(bang_token, Spacing::JointHidden)]))vec![
355 AttrTokenTree::Token(pound_token, Spacing::Joint),
356 AttrTokenTree::Token(bang_token, Spacing::JointHidden),
357 ]
358 } else {
359 <[_]>::into_vec(::alloc::boxed::box_new([AttrTokenTree::Token(pound_token,
Spacing::JointHidden)]))vec![AttrTokenTree::Token(pound_token, Spacing::JointHidden)]
360 };
361
362 let Some(TokenTree::Delimited(delim_span, delim_spacing, Delimiter::Bracket, _)) =
364 orig_trees.next()
365 else {
366 {
::core::panicking::panic_fmt(format_args!("Bad tokens for attribute {0:?}",
cfg_attr));
};panic!("Bad tokens for attribute {cfg_attr:?}");
367 };
368 trees.push(AttrTokenTree::Delimited(
369 delim_span,
370 delim_spacing,
371 Delimiter::Bracket,
372 item.tokens
373 .as_ref()
374 .unwrap_or_else(|| {
::core::panicking::panic_fmt(format_args!("Missing tokens for {0:?}",
item));
}panic!("Missing tokens for {item:?}"))
375 .to_attr_token_stream(),
376 ));
377
378 let tokens = Some(LazyAttrTokenStream::new_direct(AttrTokenStream::new(trees)));
379 let attr = ast::attr::mk_attr_from_item(
380 &self.sess.psess.attr_id_generator,
381 item,
382 tokens,
383 cfg_attr.style,
384 item_span,
385 );
386 if attr.has_name(sym::crate_type) {
387 self.sess.dcx().emit_err(CrateTypeInCfgAttr { span: attr.span });
388 }
389 if attr.has_name(sym::crate_name) {
390 self.sess.dcx().emit_err(CrateNameInCfgAttr { span: attr.span });
391 }
392 attr
393 }
394
395 fn in_cfg(&self, attrs: &[Attribute]) -> bool {
397 attrs.iter().all(|attr| {
398 !is_cfg(attr)
399 || self
400 .cfg_true(attr, ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed })
401 .as_bool()
402 })
403 }
404
405 pub(crate) fn cfg_true(&self, attr: &Attribute, emit_errors: ShouldEmit) -> EvalConfigResult {
406 let Some(cfg) = AttributeParser::parse_single(
407 self.sess,
408 attr,
409 attr.span,
410 self.lint_node_id,
411 Target::Crate,
413 self.features,
414 emit_errors,
415 parse_cfg,
416 &CFG_TEMPLATE,
417 ) else {
418 return EvalConfigResult::True;
420 };
421
422 eval_config_entry(self.sess, &cfg)
423 }
424
425 #[allow(clippy :: suspicious_else_formatting)]
{
let __tracing_attr_span;
let __tracing_attr_guard;
if ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() ||
{ false } {
__tracing_attr_span =
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("maybe_emit_expr_attr_err",
"rustc_expand::config", ::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("compiler/rustc_expand/src/config.rs"),
::tracing_core::__macro_support::Option::Some(426u32),
::tracing_core::__macro_support::Option::Some("rustc_expand::config"),
::tracing_core::field::FieldSet::new(&["attr"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::TRACE <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() }
&&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta,
&{
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = meta.fields().iter();
meta.fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&::tracing::field::debug(&attr)
as &dyn Value))])
})
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
};
__tracing_attr_guard = __tracing_attr_span.enter();
}
#[warn(clippy :: suspicious_else_formatting)]
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy ::
needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: () = loop {};
return __tracing_attr_fake_return;
}
{
if self.features.is_some_and(|features|
!features.stmt_expr_attributes()) &&
!attr.span.allows_unstable(sym::stmt_expr_attributes) {
let mut err =
feature_err(&self.sess, sym::stmt_expr_attributes,
attr.span,
crate::fluent_generated::expand_attributes_on_expressions_experimental);
if attr.is_doc_comment() {
err.help(if attr.style == AttrStyle::Outer {
crate::fluent_generated::expand_help_outer_doc
} else { crate::fluent_generated::expand_help_inner_doc });
}
err.emit();
}
}
}
}#[instrument(level = "trace", skip(self))]
427 pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) {
428 if self.features.is_some_and(|features| !features.stmt_expr_attributes())
429 && !attr.span.allows_unstable(sym::stmt_expr_attributes)
430 {
431 let mut err = feature_err(
432 &self.sess,
433 sym::stmt_expr_attributes,
434 attr.span,
435 crate::fluent_generated::expand_attributes_on_expressions_experimental,
436 );
437
438 if attr.is_doc_comment() {
439 err.help(if attr.style == AttrStyle::Outer {
440 crate::fluent_generated::expand_help_outer_doc
441 } else {
442 crate::fluent_generated::expand_help_inner_doc
443 });
444 }
445
446 err.emit();
447 }
448 }
449
450 #[allow(clippy :: suspicious_else_formatting)]
{
let __tracing_attr_span;
let __tracing_attr_guard;
if ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() ||
{ false } {
__tracing_attr_span =
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("configure_expr",
"rustc_expand::config", ::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("compiler/rustc_expand/src/config.rs"),
::tracing_core::__macro_support::Option::Some(450u32),
::tracing_core::__macro_support::Option::Some("rustc_expand::config"),
::tracing_core::field::FieldSet::new(&["expr",
"method_receiver"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::TRACE <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() }
&&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta,
&{
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = meta.fields().iter();
meta.fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&::tracing::field::debug(&expr)
as &dyn Value)),
(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&method_receiver as
&dyn Value))])
})
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
};
__tracing_attr_guard = __tracing_attr_span.enter();
}
#[warn(clippy :: suspicious_else_formatting)]
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy ::
needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: () = loop {};
return __tracing_attr_fake_return;
}
{
if !method_receiver {
for attr in expr.attrs.iter() {
self.maybe_emit_expr_attr_err(attr);
}
}
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
self.sess.dcx().emit_err(RemoveExprNotSupported {
span: attr.span,
});
}
self.process_cfg_attrs(expr);
self.try_configure_tokens(&mut *expr);
}
}
}#[instrument(level = "trace", skip(self))]
451 pub fn configure_expr(&self, expr: &mut ast::Expr, method_receiver: bool) {
452 if !method_receiver {
453 for attr in expr.attrs.iter() {
454 self.maybe_emit_expr_attr_err(attr);
455 }
456 }
457
458 if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
466 self.sess.dcx().emit_err(RemoveExprNotSupported { span: attr.span });
467 }
468
469 self.process_cfg_attrs(expr);
470 self.try_configure_tokens(&mut *expr);
471 }
472}
473
474pub fn parse_cfg_old<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a MetaItemInner> {
476 let span = meta_item.span;
477 match meta_item.meta_item_list() {
478 None => {
479 sess.dcx().emit_err(InvalidCfg::NotFollowedByParens { span });
480 None
481 }
482 Some([]) => {
483 sess.dcx().emit_err(InvalidCfg::NoPredicate { span });
484 None
485 }
486 Some([_, .., l]) => {
487 sess.dcx().emit_err(InvalidCfg::MultiplePredicates { span: l.span() });
488 None
489 }
490 Some([single]) => match single.meta_item_or_bool() {
491 Some(meta_item) => Some(meta_item),
492 None => {
493 sess.dcx().emit_err(InvalidCfg::PredicateLiteral { span: single.span() });
494 None
495 }
496 },
497 }
498}
499
500fn is_cfg(attr: &Attribute) -> bool {
501 attr.has_name(sym::cfg)
502}