Define attribute parser & config evaluator
This commit is contained in:
parent
30f4a9cd53
commit
6133c676d7
5 changed files with 330 additions and 269 deletions
|
|
@ -1,247 +1,291 @@
|
|||
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NodeId};
|
||||
use rustc_ast_pretty::pprust;
|
||||
use rustc_attr_data_structures::RustcVersion;
|
||||
use rustc_feature::{Features, GatedCfg, find_gated_cfg};
|
||||
use rustc_ast::{LitKind, NodeId};
|
||||
use rustc_attr_data_structures::{CfgEntry, RustcVersion};
|
||||
use rustc_feature::{AttributeTemplate, Features, template};
|
||||
use rustc_session::Session;
|
||||
use rustc_session::config::ExpectedValues;
|
||||
use rustc_session::lint::BuiltinLintDiag;
|
||||
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
|
||||
use rustc_session::lint::{BuiltinLintDiag, Lint};
|
||||
use rustc_session::parse::feature_err;
|
||||
use rustc_span::{Span, Symbol, sym};
|
||||
use thin_vec::ThinVec;
|
||||
|
||||
use crate::session_diagnostics::{self, UnsupportedLiteralReason};
|
||||
use crate::{fluent_generated, parse_version};
|
||||
use crate::context::{AcceptContext, Stage};
|
||||
use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
|
||||
use crate::{
|
||||
CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
|
||||
};
|
||||
|
||||
/// Emitter of a builtin lint from `cfg_matches`.
|
||||
///
|
||||
/// Used to support emitting a lint (currently on check-cfg), either:
|
||||
/// - as an early buffered lint (in `rustc`)
|
||||
/// - or has a "normal" lint from HIR (in `rustdoc`)
|
||||
pub trait CfgMatchesLintEmitter {
|
||||
fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag);
|
||||
pub const CFG_TEMPLATE: AttributeTemplate = template!(List: "predicate");
|
||||
|
||||
pub fn parse_cfg_attr<'c, S: Stage>(
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
args: &'c ArgParser<'_>,
|
||||
) -> Option<CfgEntry> {
|
||||
let ArgParser::List(list) = args else {
|
||||
cx.expected_list(cx.attr_span);
|
||||
return None;
|
||||
};
|
||||
let Some(single) = list.single() else {
|
||||
cx.expected_single_argument(list.span);
|
||||
return None;
|
||||
};
|
||||
parse_cfg_entry(cx, single)
|
||||
}
|
||||
|
||||
impl CfgMatchesLintEmitter for NodeId {
|
||||
fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag) {
|
||||
sess.psess.buffer_lint(lint, sp, *self, diag);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Condition {
|
||||
pub name: Symbol,
|
||||
pub name_span: Span,
|
||||
pub value: Option<Symbol>,
|
||||
pub value_span: Option<Span>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// Tests if a cfg-pattern matches the cfg set
|
||||
pub fn cfg_matches(
|
||||
cfg: &MetaItemInner,
|
||||
sess: &Session,
|
||||
lint_emitter: impl CfgMatchesLintEmitter,
|
||||
features: Option<&Features>,
|
||||
) -> bool {
|
||||
eval_condition(cfg, sess, features, &mut |cfg| {
|
||||
try_gate_cfg(cfg.name, cfg.span, sess, features);
|
||||
match sess.psess.check_config.expecteds.get(&cfg.name) {
|
||||
Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
|
||||
lint_emitter.emit_span_lint(
|
||||
sess,
|
||||
UNEXPECTED_CFGS,
|
||||
cfg.span,
|
||||
BuiltinLintDiag::UnexpectedCfgValue(
|
||||
(cfg.name, cfg.name_span),
|
||||
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
|
||||
),
|
||||
);
|
||||
fn parse_cfg_entry<S: Stage>(
|
||||
cx: &mut AcceptContext<'_, '_, S>,
|
||||
item: &MetaItemOrLitParser<'_>,
|
||||
) -> Option<CfgEntry> {
|
||||
Some(match item {
|
||||
MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
|
||||
ArgParser::List(list) => match meta.path().word_sym() {
|
||||
Some(sym::not) => {
|
||||
let Some(single) = list.single() else {
|
||||
cx.expected_single_argument(list.span);
|
||||
return None;
|
||||
};
|
||||
CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
|
||||
}
|
||||
Some(sym::any) => CfgEntry::Any(
|
||||
list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
|
||||
list.span,
|
||||
),
|
||||
Some(sym::all) => CfgEntry::All(
|
||||
list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
|
||||
list.span,
|
||||
),
|
||||
Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
|
||||
Some(sym::version) => parse_cfg_entry_version(cx, list, meta.span())?,
|
||||
_ => {
|
||||
cx.emit_err(session_diagnostics::InvalidPredicate {
|
||||
span: meta.span(),
|
||||
predicate: meta.path().to_string(),
|
||||
});
|
||||
return None;
|
||||
}
|
||||
},
|
||||
a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
|
||||
let Some(name) = meta.path().word_sym() else {
|
||||
cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
|
||||
span: meta.path().span(),
|
||||
});
|
||||
return None;
|
||||
};
|
||||
parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
|
||||
}
|
||||
None if sess.psess.check_config.exhaustive_names => {
|
||||
lint_emitter.emit_span_lint(
|
||||
sess,
|
||||
UNEXPECTED_CFGS,
|
||||
cfg.span,
|
||||
BuiltinLintDiag::UnexpectedCfgName(
|
||||
(cfg.name, cfg.name_span),
|
||||
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
|
||||
),
|
||||
);
|
||||
},
|
||||
MetaItemOrLitParser::Lit(lit) => match lit.kind {
|
||||
LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
|
||||
_ => {
|
||||
cx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: lit.span });
|
||||
return None;
|
||||
}
|
||||
_ => { /* not unexpected */ }
|
||||
}
|
||||
sess.psess.config.contains(&(cfg.name, cfg.value))
|
||||
},
|
||||
MetaItemOrLitParser::Err(_, _) => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
|
||||
let gate = find_gated_cfg(|sym| sym == name);
|
||||
if let (Some(feats), Some(gated_cfg)) = (features, gate) {
|
||||
gate_cfg(gated_cfg, span, sess, feats);
|
||||
}
|
||||
fn parse_cfg_entry_version<S: Stage>(
|
||||
cx: &mut AcceptContext<'_, '_, S>,
|
||||
list: &MetaItemListParser<'_>,
|
||||
meta_span: Span,
|
||||
) -> Option<CfgEntry> {
|
||||
try_gate_cfg(sym::version, meta_span, cx.sess(), Some(cx.features()));
|
||||
let Some(version) = list.single() else {
|
||||
cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span });
|
||||
return None;
|
||||
};
|
||||
let Some(version_lit) = version.lit() else {
|
||||
cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() });
|
||||
return None;
|
||||
};
|
||||
let Some(version_str) = version_lit.value_str() else {
|
||||
cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span });
|
||||
return None;
|
||||
};
|
||||
|
||||
let min_version = parse_version(version_str).or_else(|| {
|
||||
cx.sess()
|
||||
.dcx()
|
||||
.emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span });
|
||||
None
|
||||
});
|
||||
|
||||
Some(CfgEntry::Version(min_version, list.span))
|
||||
}
|
||||
|
||||
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
|
||||
fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
|
||||
let (cfg, feature, has_feature) = gated_cfg;
|
||||
if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
|
||||
let explain = format!("`cfg({cfg})` is experimental and subject to change");
|
||||
feature_err(sess, *feature, cfg_span, explain).emit();
|
||||
fn parse_cfg_entry_target<S: Stage>(
|
||||
cx: &mut AcceptContext<'_, '_, S>,
|
||||
list: &MetaItemListParser<'_>,
|
||||
meta_span: Span,
|
||||
) -> Option<CfgEntry> {
|
||||
if !cx.features().cfg_target_compact() {
|
||||
feature_err(
|
||||
cx.sess(),
|
||||
sym::cfg_target_compact,
|
||||
meta_span,
|
||||
fluent_generated::attr_parsing_unstable_cfg_target_compact,
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
|
||||
/// evaluate individual items.
|
||||
pub fn eval_condition(
|
||||
cfg: &MetaItemInner,
|
||||
sess: &Session,
|
||||
features: Option<&Features>,
|
||||
eval: &mut impl FnMut(Condition) -> bool,
|
||||
) -> bool {
|
||||
let dcx = sess.dcx();
|
||||
let mut result = ThinVec::new();
|
||||
for sub_item in list.mixed() {
|
||||
// First, validate that this is a NameValue item
|
||||
let Some(sub_item) = sub_item.meta_item() else {
|
||||
cx.expected_name_value(sub_item.span(), None);
|
||||
continue;
|
||||
};
|
||||
let Some(nv) = sub_item.args().name_value() else {
|
||||
cx.expected_name_value(sub_item.span(), None);
|
||||
continue;
|
||||
};
|
||||
|
||||
let cfg = match cfg {
|
||||
MetaItemInner::MetaItem(meta_item) => meta_item,
|
||||
MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
|
||||
return *b;
|
||||
}
|
||||
_ => {
|
||||
dcx.emit_err(session_diagnostics::UnsupportedLiteral {
|
||||
span: cfg.span(),
|
||||
reason: UnsupportedLiteralReason::CfgBoolean,
|
||||
is_bytestr: false,
|
||||
start_point_span: sess.source_map().start_point(cfg.span()),
|
||||
// Then, parse it as a name-value item
|
||||
let Some(name) = sub_item.path().word_sym() else {
|
||||
cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
|
||||
span: sub_item.path().span(),
|
||||
});
|
||||
return false;
|
||||
return None;
|
||||
};
|
||||
let name = Symbol::intern(&format!("target_{name}"));
|
||||
if let Some(cfg) =
|
||||
parse_name_value(name, sub_item.path().span(), Some(nv), sub_item.span(), cx)
|
||||
{
|
||||
result.push(cfg);
|
||||
}
|
||||
}
|
||||
Some(CfgEntry::All(result, list.span))
|
||||
}
|
||||
|
||||
fn parse_name_value<S: Stage>(
|
||||
name: Symbol,
|
||||
name_span: Span,
|
||||
value: Option<&NameValueParser>,
|
||||
span: Span,
|
||||
cx: &mut AcceptContext<'_, '_, S>,
|
||||
) -> Option<CfgEntry> {
|
||||
try_gate_cfg(name, span, cx.sess(), cx.features_option());
|
||||
|
||||
let value = match value {
|
||||
None => None,
|
||||
Some(value) => {
|
||||
let Some(value_str) = value.value_as_str() else {
|
||||
cx.expected_string_literal(value.value_span, Some(value.value_as_lit()));
|
||||
return None;
|
||||
};
|
||||
Some((value_str, value.value_span))
|
||||
}
|
||||
};
|
||||
|
||||
match &cfg.kind {
|
||||
MetaItemKind::List(mis) if cfg.has_name(sym::version) => {
|
||||
try_gate_cfg(sym::version, cfg.span, sess, features);
|
||||
let (min_version, span) = match &mis[..] {
|
||||
[MetaItemInner::Lit(MetaItemLit { kind: LitKind::Str(sym, ..), span, .. })] => {
|
||||
(sym, span)
|
||||
}
|
||||
[
|
||||
MetaItemInner::Lit(MetaItemLit { span, .. })
|
||||
| MetaItemInner::MetaItem(MetaItem { span, .. }),
|
||||
] => {
|
||||
dcx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: *span });
|
||||
return false;
|
||||
}
|
||||
[..] => {
|
||||
dcx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral {
|
||||
span: cfg.span,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let Some(min_version) = parse_version(*min_version) else {
|
||||
dcx.emit_warn(session_diagnostics::UnknownVersionLiteral { span: *span });
|
||||
return false;
|
||||
};
|
||||
Some(CfgEntry::NameValue { name, name_span, value, span })
|
||||
}
|
||||
|
||||
// See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
|
||||
if sess.psess.assume_incomplete_release {
|
||||
RustcVersion::current_overridable() > min_version
|
||||
} else {
|
||||
RustcVersion::current_overridable() >= min_version
|
||||
}
|
||||
}
|
||||
MetaItemKind::List(mis) => {
|
||||
for mi in mis.iter() {
|
||||
if mi.meta_item_or_bool().is_none() {
|
||||
dcx.emit_err(session_diagnostics::UnsupportedLiteral {
|
||||
span: mi.span(),
|
||||
reason: UnsupportedLiteralReason::Generic,
|
||||
is_bytestr: false,
|
||||
start_point_span: sess.source_map().start_point(mi.span()),
|
||||
});
|
||||
return false;
|
||||
pub fn eval_config_entry(
|
||||
sess: &Session,
|
||||
cfg_entry: &CfgEntry,
|
||||
id: NodeId,
|
||||
features: Option<&Features>,
|
||||
) -> EvalConfigResult {
|
||||
match cfg_entry {
|
||||
CfgEntry::All(subs, ..) => {
|
||||
let mut all = None;
|
||||
for sub in subs {
|
||||
let res = eval_config_entry(sess, sub, id, features);
|
||||
// We cannot short-circuit because `eval_config_entry` emits some lints
|
||||
if !res.as_bool() {
|
||||
all.get_or_insert(res);
|
||||
}
|
||||
}
|
||||
|
||||
// The unwraps below may look dangerous, but we've already asserted
|
||||
// that they won't fail with the loop above.
|
||||
match cfg.name() {
|
||||
Some(sym::any) => mis
|
||||
.iter()
|
||||
// We don't use any() here, because we want to evaluate all cfg condition
|
||||
// as eval_condition can (and does) extra checks
|
||||
.fold(false, |res, mi| res | eval_condition(mi, sess, features, eval)),
|
||||
Some(sym::all) => mis
|
||||
.iter()
|
||||
// We don't use all() here, because we want to evaluate all cfg condition
|
||||
// as eval_condition can (and does) extra checks
|
||||
.fold(true, |res, mi| res & eval_condition(mi, sess, features, eval)),
|
||||
Some(sym::not) => {
|
||||
let [mi] = mis.as_slice() else {
|
||||
dcx.emit_err(session_diagnostics::ExpectedOneCfgPattern { span: cfg.span });
|
||||
return false;
|
||||
};
|
||||
|
||||
!eval_condition(mi, sess, features, eval)
|
||||
}
|
||||
Some(sym::target) => {
|
||||
if let Some(features) = features
|
||||
&& !features.cfg_target_compact()
|
||||
{
|
||||
feature_err(
|
||||
sess,
|
||||
sym::cfg_target_compact,
|
||||
cfg.span,
|
||||
fluent_generated::attr_parsing_unstable_cfg_target_compact,
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
|
||||
mis.iter().fold(true, |res, mi| {
|
||||
let Some(mut mi) = mi.meta_item().cloned() else {
|
||||
dcx.emit_err(session_diagnostics::CfgPredicateIdentifier {
|
||||
span: mi.span(),
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
if let [seg, ..] = &mut mi.path.segments[..] {
|
||||
seg.ident.name = Symbol::intern(&format!("target_{}", seg.ident.name));
|
||||
}
|
||||
|
||||
res & eval_condition(&MetaItemInner::MetaItem(mi), sess, features, eval)
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
dcx.emit_err(session_diagnostics::InvalidPredicate {
|
||||
span: cfg.span,
|
||||
predicate: pprust::path_to_string(&cfg.path),
|
||||
});
|
||||
false
|
||||
all.unwrap_or_else(|| EvalConfigResult::True)
|
||||
}
|
||||
CfgEntry::Any(subs, span) => {
|
||||
let mut any = None;
|
||||
for sub in subs {
|
||||
let res = eval_config_entry(sess, sub, id, features);
|
||||
// We cannot short-circuit because `eval_config_entry` emits some lints
|
||||
if res.as_bool() {
|
||||
any.get_or_insert(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => {
|
||||
dcx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: cfg.path.span });
|
||||
true
|
||||
}
|
||||
MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
|
||||
dcx.emit_err(session_diagnostics::UnsupportedLiteral {
|
||||
span: lit.span,
|
||||
reason: UnsupportedLiteralReason::CfgString,
|
||||
is_bytestr: lit.kind.is_bytestr(),
|
||||
start_point_span: sess.source_map().start_point(lit.span),
|
||||
});
|
||||
true
|
||||
}
|
||||
MetaItemKind::Word | MetaItemKind::NameValue(..) => {
|
||||
let ident = cfg.ident().expect("multi-segment cfg predicate");
|
||||
eval(Condition {
|
||||
name: ident.name,
|
||||
name_span: ident.span,
|
||||
value: cfg.value_str(),
|
||||
value_span: cfg.name_value_literal_span(),
|
||||
span: cfg.span,
|
||||
any.unwrap_or_else(|| EvalConfigResult::False {
|
||||
reason: cfg_entry.clone(),
|
||||
reason_span: *span,
|
||||
})
|
||||
}
|
||||
CfgEntry::Not(sub, span) => {
|
||||
if eval_config_entry(sess, sub, id, features).as_bool() {
|
||||
EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
|
||||
} else {
|
||||
EvalConfigResult::True
|
||||
}
|
||||
}
|
||||
CfgEntry::Bool(b, span) => {
|
||||
if *b {
|
||||
EvalConfigResult::True
|
||||
} else {
|
||||
EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
|
||||
}
|
||||
}
|
||||
CfgEntry::NameValue { name, name_span, value, span } => {
|
||||
match sess.psess.check_config.expecteds.get(name) {
|
||||
Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => {
|
||||
id.emit_span_lint(
|
||||
sess,
|
||||
UNEXPECTED_CFGS,
|
||||
*span,
|
||||
BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
|
||||
);
|
||||
}
|
||||
None if sess.psess.check_config.exhaustive_names => {
|
||||
id.emit_span_lint(
|
||||
sess,
|
||||
UNEXPECTED_CFGS,
|
||||
*span,
|
||||
BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
|
||||
);
|
||||
}
|
||||
_ => { /* not unexpected */ }
|
||||
}
|
||||
|
||||
if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) {
|
||||
EvalConfigResult::True
|
||||
} else {
|
||||
EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
|
||||
}
|
||||
}
|
||||
CfgEntry::Version(min_version, version_span) => {
|
||||
let Some(min_version) = min_version else {
|
||||
return EvalConfigResult::False {
|
||||
reason: cfg_entry.clone(),
|
||||
reason_span: *version_span,
|
||||
};
|
||||
};
|
||||
// See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
|
||||
let min_version_ok = if sess.psess.assume_incomplete_release {
|
||||
RustcVersion::current_overridable() > *min_version
|
||||
} else {
|
||||
RustcVersion::current_overridable() >= *min_version
|
||||
};
|
||||
if min_version_ok {
|
||||
EvalConfigResult::True
|
||||
} else {
|
||||
EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum EvalConfigResult {
|
||||
True,
|
||||
False { reason: CfgEntry, reason_span: Span },
|
||||
}
|
||||
|
||||
impl EvalConfigResult {
|
||||
pub fn as_bool(&self) -> bool {
|
||||
match self {
|
||||
EvalConfigResult::True => true,
|
||||
EvalConfigResult::False { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ use rustc_ast::{
|
|||
NodeId, NormalAttr,
|
||||
};
|
||||
use rustc_attr_parsing as attr;
|
||||
use rustc_attr_parsing::{
|
||||
AttributeParser, CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr,
|
||||
};
|
||||
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
|
||||
use rustc_feature::{
|
||||
ACCEPTED_LANG_FEATURES, AttributeSafety, EnabledLangFeature, EnabledLibFeature, Features,
|
||||
|
|
@ -18,6 +21,7 @@ use rustc_feature::{
|
|||
};
|
||||
use rustc_lint_defs::BuiltinLintDiag;
|
||||
use rustc_parse::validate_attr;
|
||||
use rustc_parse::validate_attr::deny_builtin_meta_unsafety;
|
||||
use rustc_session::Session;
|
||||
use rustc_session::parse::feature_err;
|
||||
use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym};
|
||||
|
|
@ -161,7 +165,10 @@ pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec
|
|||
attrs
|
||||
.iter()
|
||||
.flat_map(|attr| strip_unconfigured.process_cfg_attr(attr))
|
||||
.take_while(|attr| !is_cfg(attr) || strip_unconfigured.cfg_true(attr).0)
|
||||
.take_while(|attr| {
|
||||
!is_cfg(attr)
|
||||
|| strip_unconfigured.cfg_true(attr, strip_unconfigured.lint_node_id).as_bool()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
@ -394,26 +401,42 @@ impl<'a> StripUnconfigured<'a> {
|
|||
|
||||
/// Determines if a node with the given attributes should be included in this configuration.
|
||||
fn in_cfg(&self, attrs: &[Attribute]) -> bool {
|
||||
attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr).0)
|
||||
attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr, self.lint_node_id).as_bool())
|
||||
}
|
||||
|
||||
pub(crate) fn cfg_true(&self, attr: &Attribute) -> (bool, Option<MetaItem>) {
|
||||
let meta_item = match validate_attr::parse_meta(&self.sess.psess, attr) {
|
||||
Ok(meta_item) => meta_item,
|
||||
pub(crate) fn cfg_true(&self, attr: &Attribute, node: NodeId) -> EvalConfigResult {
|
||||
// We need to run this to do basic validation of the attribute, such as that lits are valid, etc
|
||||
// FIXME(jdonszelmann) this should not be necessary in the future
|
||||
match validate_attr::parse_meta(&self.sess.psess, attr) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
err.emit();
|
||||
return (true, None);
|
||||
return EvalConfigResult::True;
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafety check needs to be done explicitly here because this attribute will be removed before the normal check
|
||||
deny_builtin_meta_unsafety(
|
||||
self.sess.dcx(),
|
||||
attr.get_normal_item().unsafety,
|
||||
&rustc_ast::Path::from_ident(attr.ident().unwrap()),
|
||||
);
|
||||
|
||||
let Some(cfg) = AttributeParser::parse_single(
|
||||
self.sess,
|
||||
attr,
|
||||
attr.span,
|
||||
node,
|
||||
self.features,
|
||||
true,
|
||||
parse_cfg_attr,
|
||||
&CFG_TEMPLATE,
|
||||
) else {
|
||||
// Cfg attribute was not parsable, give up
|
||||
return EvalConfigResult::True;
|
||||
};
|
||||
|
||||
validate_attr::deny_builtin_meta_unsafety(&self.sess.psess, &meta_item);
|
||||
|
||||
(
|
||||
parse_cfg(&meta_item, self.sess).is_none_or(|meta_item| {
|
||||
attr::cfg_matches(meta_item, &self.sess, self.lint_node_id, self.features)
|
||||
}),
|
||||
Some(meta_item),
|
||||
)
|
||||
eval_config_entry(self.sess, &cfg, self.lint_node_id, self.features)
|
||||
}
|
||||
|
||||
/// If attributes are not allowed on expressions, emit an error for `attr`
|
||||
|
|
@ -465,6 +488,7 @@ impl<'a> StripUnconfigured<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// FIXME: Still used by Rustdoc, should be removed after
|
||||
pub fn parse_cfg<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a MetaItemInner> {
|
||||
let span = meta_item.span;
|
||||
match meta_item.meta_item_list() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use rustc_ast::{
|
|||
MetaItemKind, ModKind, NodeId, PatKind, StmtKind, TyKind, token,
|
||||
};
|
||||
use rustc_ast_pretty::pprust;
|
||||
use rustc_attr_parsing::EvalConfigResult;
|
||||
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
|
||||
use rustc_errors::PResult;
|
||||
use rustc_feature::Features;
|
||||
|
|
@ -2166,19 +2167,19 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
|
|||
|
||||
fn expand_cfg_true(
|
||||
&mut self,
|
||||
node: &mut impl HasAttrs,
|
||||
node: &mut (impl HasAttrs + HasNodeId),
|
||||
attr: ast::Attribute,
|
||||
pos: usize,
|
||||
) -> (bool, Option<ast::MetaItem>) {
|
||||
let (res, meta_item) = self.cfg().cfg_true(&attr);
|
||||
if res {
|
||||
) -> EvalConfigResult {
|
||||
let res = self.cfg().cfg_true(&attr, node.node_id());
|
||||
if res.as_bool() {
|
||||
// A trace attribute left in AST in place of the original `cfg` attribute.
|
||||
// It can later be used by lints or other diagnostics.
|
||||
let trace_attr = attr_into_trace(attr, sym::cfg_trace);
|
||||
node.visit_attrs(|attrs| attrs.insert(pos, trace_attr));
|
||||
}
|
||||
|
||||
(res, meta_item)
|
||||
res
|
||||
}
|
||||
|
||||
fn expand_cfg_attr(&self, node: &mut impl HasAttrs, attr: &ast::Attribute, pos: usize) {
|
||||
|
|
@ -2199,20 +2200,21 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
|
|||
return match self.take_first_attr(&mut node) {
|
||||
Some((attr, pos, derives)) => match attr.name() {
|
||||
Some(sym::cfg) => {
|
||||
let (res, meta_item) = self.expand_cfg_true(&mut node, attr, pos);
|
||||
if res {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(meta_item) = meta_item {
|
||||
for ident in node.declared_idents() {
|
||||
self.cx.resolver.append_stripped_cfg_item(
|
||||
self.cx.current_expansion.lint_node_id,
|
||||
ident,
|
||||
meta_item.clone(),
|
||||
)
|
||||
let res = self.expand_cfg_true(&mut node, attr, pos);
|
||||
match res {
|
||||
EvalConfigResult::True => continue,
|
||||
EvalConfigResult::False { reason, reason_span } => {
|
||||
for ident in node.declared_idents() {
|
||||
self.cx.resolver.append_stripped_cfg_item(
|
||||
self.cx.current_expansion.lint_node_id,
|
||||
ident,
|
||||
reason.clone(),
|
||||
reason_span,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Default::default()
|
||||
}
|
||||
Some(sym::cfg_attr) => {
|
||||
|
|
@ -2291,7 +2293,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
|
|||
Some((attr, pos, derives)) => match attr.name() {
|
||||
Some(sym::cfg) => {
|
||||
let span = attr.span;
|
||||
if self.expand_cfg_true(node, attr, pos).0 {
|
||||
if self.expand_cfg_true(node, attr, pos).as_bool() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use rustc_ast::token::Delimiter;
|
|||
use rustc_ast::tokenstream::DelimSpan;
|
||||
use rustc_ast::{
|
||||
self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, NodeId,
|
||||
Safety,
|
||||
Path, Safety,
|
||||
};
|
||||
use rustc_errors::{Applicability, FatalError, PResult};
|
||||
use rustc_errors::{Applicability, DiagCtxtHandle, FatalError, PResult};
|
||||
use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
|
||||
use rustc_session::errors::report_lit_error;
|
||||
use rustc_session::lint::BuiltinLintDiag;
|
||||
|
|
@ -247,14 +247,12 @@ pub fn check_attribute_safety(
|
|||
|
||||
// Called by `check_builtin_meta_item` and code that manually denies
|
||||
// `unsafe(...)` in `cfg`
|
||||
pub fn deny_builtin_meta_unsafety(psess: &ParseSess, meta: &MetaItem) {
|
||||
pub fn deny_builtin_meta_unsafety(diag: DiagCtxtHandle<'_>, unsafety: Safety, name: &Path) {
|
||||
// This only supports denying unsafety right now - making builtin attributes
|
||||
// support unsafety will requite us to thread the actual `Attribute` through
|
||||
// for the nice diagnostics.
|
||||
if let Safety::Unsafe(unsafe_span) = meta.unsafety {
|
||||
psess
|
||||
.dcx()
|
||||
.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: meta.path.clone() });
|
||||
if let Safety::Unsafe(unsafe_span) = unsafety {
|
||||
diag.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: name.clone() });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -326,7 +324,7 @@ pub fn check_builtin_meta_item(
|
|||
}
|
||||
|
||||
if deny_unsafety {
|
||||
deny_builtin_meta_unsafety(psess, meta);
|
||||
deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use rustc_ast::expand::StrippedCfgItem;
|
||||
use rustc_ast::ptr::P;
|
||||
use rustc_ast::visit::{self, Visitor};
|
||||
use rustc_ast::{
|
||||
self as ast, CRATE_NODE_ID, Crate, ItemKind, MetaItemInner, MetaItemKind, ModKind, NodeId, Path,
|
||||
};
|
||||
use rustc_ast::{self as ast, CRATE_NODE_ID, Crate, ItemKind, ModKind, NodeId, Path};
|
||||
use rustc_ast_pretty::pprust;
|
||||
use rustc_attr_data_structures::{self as attr, AttributeKind, Stability, find_attr};
|
||||
use rustc_attr_data_structures::{
|
||||
self as attr, AttributeKind, CfgEntry, Stability, StrippedCfgItem, find_attr,
|
||||
};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_data_structures::unord::{UnordMap, UnordSet};
|
||||
use rustc_errors::codes::*;
|
||||
|
|
@ -2860,17 +2859,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
let note = errors::FoundItemConfigureOut { span: ident.span };
|
||||
err.subdiagnostic(note);
|
||||
|
||||
if let MetaItemKind::List(nested) = &cfg.kind
|
||||
&& let MetaItemInner::MetaItem(meta_item) = &nested[0]
|
||||
&& let MetaItemKind::NameValue(feature_name) = &meta_item.kind
|
||||
{
|
||||
let note = errors::ItemWasBehindFeature {
|
||||
feature: feature_name.symbol,
|
||||
span: meta_item.span,
|
||||
};
|
||||
if let CfgEntry::NameValue { value: Some((feature, _)), .. } = cfg.0 {
|
||||
let note = errors::ItemWasBehindFeature { feature, span: cfg.1 };
|
||||
err.subdiagnostic(note);
|
||||
} else {
|
||||
let note = errors::ItemWasCfgOut { span: cfg.span };
|
||||
let note = errors::ItemWasCfgOut { span: cfg.1 };
|
||||
err.subdiagnostic(note);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue