| use std::convert::identity; | |
| use rustc_ast::token::Delimiter; | |
| use rustc_ast::tokenstream::DelimSpan; | |
| use rustc_ast::{AttrItem, Attribute, LitKind, ast, token}; | |
| use rustc_errors::{Applicability, Diagnostic, PResult, msg}; | |
| use rustc_feature::{Features, GatedCfg, find_gated_cfg}; | |
| use rustc_hir::attrs::CfgEntry; | |
| use rustc_hir::{AttrPath, RustcVersion, Target}; | |
| use rustc_parse::parser::{ForceCollect, Parser, Recovery}; | |
| use rustc_parse::{exp, parse_in}; | |
| use rustc_session::Session; | |
| use rustc_session::config::ExpectedValues; | |
| use rustc_session::errors::feature_err; | |
| use rustc_session::lint::builtin::UNEXPECTED_CFGS; | |
| use rustc_session::parse::ParseSess; | |
| use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; | |
| use thin_vec::ThinVec; | |
| use crate::attributes::AttributeSafety; | |
| use crate::context::{AcceptContext, ShouldEmit}; | |
| use crate::parser::{ | |
| AllowExprMetavar, ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser, | |
| }; | |
| use crate::session_diagnostics::{ | |
| AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg, | |
| ParsedDescription, | |
| }; | |
| use crate::{ | |
| AttrSuggestionStyle, AttributeParser, AttributeTemplate, check_cfg, parse_version, | |
| session_diagnostics, template, | |
| }; | |
| pub const CFG_TEMPLATE: AttributeTemplate = template!( | |
| List: &["predicate"], | |
| "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute" | |
| ); | |
| const CFG_ATTR_TEMPLATE: AttributeTemplate = template!( | |
| List: &["predicate, attr1, attr2, ..."], | |
| "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute" | |
| ); | |
| pub fn parse_cfg(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<CfgEntry> { | |
| let list = cx.expect_list(args, cx.attr_span)?; | |
| let Some(single) = list.as_single() else { | |
| let target = cx.target; | |
| let mut adcx = cx.adcx(); | |
| if list.is_empty() { | |
| // `#[cfg()]` | |
| let message = format!("if the {target} should be disabled, use `#[cfg(false)]`"); | |
| adcx.push_suggestion(message, list.span, "(false)".to_string()); | |
| } else { | |
| // `#[cfg(foo, bar)]` | |
| if let Ok(args) = adcx | |
| .sess() | |
| .source_map() | |
| .span_to_source(list.span, |src, start, end| Ok(src[start..end].to_string())) | |
| { | |
| let all = format!("(all{args})"); | |
| let any = format!("(any{args})"); | |
| let all_msg = format!( | |
| "if the {target} should be enabled when all these predicates are, wrap them in `all`" | |
| ); | |
| let any_msg = format!( | |
| "alternately, if the {target} should be enabled when any of these predicates are, wrap them in `any`" | |
| ); | |
| adcx.push_suggestion(all_msg, list.span, all); | |
| adcx.push_suggestion(any_msg, list.span, any); | |
| } | |
| } | |
| adcx.expected_single_argument(list.span, list.len()); | |
| return None; | |
| }; | |
| parse_cfg_entry(cx, single).ok() | |
| } | |
| pub fn parse_cfg_entry( | |
| cx: &mut AcceptContext<'_, '_>, | |
| item: &MetaItemOrLitParser, | |
| ) -> Result<CfgEntry, ErrorGuaranteed> { | |
| Ok(match item { | |
| MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() { | |
| ArgParser::List(list) => match meta.path().word_sym() { | |
| Some(sym::not) => { | |
| let Some(single) = list.as_single() else { | |
| return Err(cx.adcx().expected_single_argument(list.span, list.len())); | |
| }; | |
| 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())?, | |
| _ => { | |
| let mut possibilities = vec![sym::any, sym::all, sym::not, sym::target]; | |
| if cx.features_option().is_some_and(Features::cfg_version) { | |
| possibilities.push(sym::version); | |
| } | |
| return Err(cx.adcx().expected_specific_argument(meta.span(), &possibilities)); | |
| } | |
| }, | |
| a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => { | |
| let Some(name) = meta.path().word_sym().filter(|s| !s.is_path_segment_keyword()) | |
| else { | |
| return Err(cx.adcx().expected_identifier(meta.path().span())); | |
| }; | |
| parse_name_value(name, meta.path().span(), a.as_name_value(), meta.span(), cx)? | |
| } | |
| }, | |
| MetaItemOrLitParser::Lit(lit) => match lit.kind { | |
| LitKind::Bool(b) => CfgEntry::Bool(b, lit.span), | |
| _ => return Err(cx.adcx().expected_identifier(lit.span)), | |
| }, | |
| }) | |
| } | |
| fn parse_cfg_entry_version( | |
| cx: &mut AcceptContext<'_, '_>, | |
| list: &MetaItemListParser, | |
| meta_span: Span, | |
| ) -> Result<CfgEntry, ErrorGuaranteed> { | |
| try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option()); | |
| let Some(version) = list.as_single() else { | |
| return Err( | |
| cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span }) | |
| ); | |
| }; | |
| let Some(version_lit) = version.as_lit() else { | |
| return Err( | |
| cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() }) | |
| ); | |
| }; | |
| let Some(version_str) = version_lit.value_as_str() else { | |
| return Err( | |
| cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span }) | |
| ); | |
| }; | |
| let min_version = parse_version(version_str).or_else(|| { | |
| cx.sess() | |
| .dcx() | |
| .emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span }); | |
| None | |
| }); | |
| Ok(CfgEntry::Version(min_version, list.span)) | |
| } | |
| fn parse_cfg_entry_target( | |
| cx: &mut AcceptContext<'_, '_>, | |
| list: &MetaItemListParser, | |
| meta_span: Span, | |
| ) -> Result<CfgEntry, ErrorGuaranteed> { | |
| if let Some(features) = cx.features_option() | |
| && !features.cfg_target_compact() | |
| { | |
| feature_err( | |
| cx.sess(), | |
| sym::cfg_target_compact, | |
| meta_span, | |
| msg!("compact `cfg(target(..))` is experimental and subject to change"), | |
| ) | |
| .emit(); | |
| } | |
| let mut result = ThinVec::new(); | |
| for sub_item in list.mixed() { | |
| // First, validate that this is a NameValue item | |
| let Some((name, value)) = cx.expect_name_value(sub_item, sub_item.span(), None) else { | |
| continue; | |
| }; | |
| // Then, parse it as a name-value item | |
| if name.is_path_segment_keyword() { | |
| return Err(cx.adcx().expected_identifier(name.span)); | |
| } | |
| let name = Symbol::intern(&format!("target_{name}")); | |
| if let Ok(cfg) = parse_name_value(name, sub_item.span(), Some(value), sub_item.span(), cx) { | |
| result.push(cfg); | |
| } | |
| } | |
| Ok(CfgEntry::All(result, list.span)) | |
| } | |
| pub(crate) fn parse_name_value( | |
| name: Symbol, | |
| name_span: Span, | |
| value: Option<&NameValueParser>, | |
| span: Span, | |
| cx: &mut AcceptContext<'_, '_>, | |
| ) -> Result<CfgEntry, ErrorGuaranteed> { | |
| 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 { | |
| return Err(cx | |
| .adcx() | |
| .expected_string_literal(value.value_span, Some(value.value_as_lit()))); | |
| }; | |
| Some((value_str, value.value_span)) | |
| } | |
| }; | |
| match cx.sess.check_config.expecteds.get(&name) { | |
| Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => cx | |
| .emit_lint_with_sess( | |
| UNEXPECTED_CFGS, | |
| move |dcx, level, sess| { | |
| check_cfg::unexpected_cfg_value(sess, (name, name_span), value) | |
| .into_diag(dcx, level) | |
| }, | |
| span, | |
| ), | |
| None if cx.sess.check_config.exhaustive_names => cx.emit_lint_with_sess( | |
| UNEXPECTED_CFGS, | |
| move |dcx, level, sess| { | |
| check_cfg::unexpected_cfg_name(sess, (name, name_span), value).into_diag(dcx, level) | |
| }, | |
| span, | |
| ), | |
| _ => { /* not unexpected */ } | |
| } | |
| Ok(CfgEntry::NameValue { name, value: value.map(|(v, _)| v), span }) | |
| } | |
| pub fn eval_config_entry(sess: &Session, cfg_entry: &CfgEntry) -> EvalConfigResult { | |
| match cfg_entry { | |
| CfgEntry::All(subs, ..) => { | |
| for sub in subs { | |
| let res = eval_config_entry(sess, sub); | |
| if !res.as_bool() { | |
| return res; | |
| } | |
| } | |
| EvalConfigResult::True | |
| } | |
| CfgEntry::Any(subs, span) => { | |
| for sub in subs { | |
| let res = eval_config_entry(sess, sub); | |
| if res.as_bool() { | |
| return res; | |
| } | |
| } | |
| EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } | |
| } | |
| CfgEntry::Not(sub, span) => { | |
| if eval_config_entry(sess, sub).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, value, span } => { | |
| if sess.config.contains(&(*name, *value)) { | |
| 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.opts.unstable_opts.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, | |
| } | |
| } | |
| } | |
| pub fn parse_cfg_attr( | |
| cfg_attr: &Attribute, | |
| sess: &Session, | |
| features: Option<&Features>, | |
| lint_node_id: ast::NodeId, | |
| ) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> { | |
| match cfg_attr.get_normal_item().args.unparsed_ref().unwrap() { | |
| ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => { | |
| check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim); | |
| match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| { | |
| parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr) | |
| }) { | |
| Ok(r) => return Some(r), | |
| Err(e) => { | |
| let suggestions = CFG_ATTR_TEMPLATE.suggestions( | |
| AttrSuggestionStyle::Attribute(cfg_attr.style), | |
| cfg_attr.get_normal_item().unsafety, | |
| sym::cfg_attr, | |
| ); | |
| e.with_span_suggestions( | |
| cfg_attr.span, | |
| "must be of the form", | |
| suggestions, | |
| Applicability::HasPlaceholders, | |
| ) | |
| .with_note(format!( | |
| "for more information, visit <{}>", | |
| CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs") | |
| )) | |
| .emit(); | |
| } | |
| } | |
| } | |
| _ => { | |
| let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) = | |
| cfg_attr.get_normal_item().args.unparsed_ref()? | |
| { | |
| (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument) | |
| } else { | |
| (cfg_attr.span, AttributeParseErrorReason::ExpectedList) | |
| }; | |
| sess.dcx().emit_err(AttributeParseError { | |
| span, | |
| attr_span: cfg_attr.span, | |
| template: CFG_ATTR_TEMPLATE, | |
| path: AttrPath::from_ast(&cfg_attr.get_normal_item().path, identity), | |
| description: ParsedDescription::Attribute, | |
| reason, | |
| suggestions: session_diagnostics::AttributeParseErrorSuggestions::CreatedByTemplate( | |
| CFG_ATTR_TEMPLATE.suggestions( | |
| AttrSuggestionStyle::Attribute(cfg_attr.style), | |
| cfg_attr.get_normal_item().unsafety, | |
| sym::cfg_attr, | |
| ), | |
| ), | |
| }); | |
| } | |
| } | |
| None | |
| } | |
| fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) { | |
| if let Delimiter::Parenthesis = delim { | |
| return; | |
| } | |
| psess.dcx().emit_err(CfgAttrBadDelim { | |
| span: span.entire(), | |
| sugg: MetaBadDelimSugg { open: span.open, close: span.close }, | |
| }); | |
| } | |
| /// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited. | |
| fn parse_cfg_attr_internal<'a>( | |
| parser: &mut Parser<'a>, | |
| sess: &'a Session, | |
| features: Option<&Features>, | |
| lint_node_id: ast::NodeId, | |
| attribute: &Attribute, | |
| ) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> { | |
| // Parse cfg predicate | |
| let pred_start = parser.token.span; | |
| let meta = MetaItemOrLitParser::parse_single( | |
| parser, | |
| ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, | |
| AllowExprMetavar::Yes, | |
| )?; | |
| let pred_span = pred_start.with_hi(parser.token.span.hi()); | |
| let cfg_predicate = AttributeParser::parse_single_args( | |
| sess, | |
| attribute.span, | |
| attribute.get_normal_item().span(), | |
| attribute.style, | |
| AttrPath { segments: attribute.path().into_boxed_slice(), span: attribute.span }, | |
| Some(attribute.get_normal_item().unsafety), | |
| AttributeSafety::Normal, | |
| ParsedDescription::Attribute, | |
| pred_span, | |
| lint_node_id, | |
| Target::Crate, | |
| features, | |
| ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, | |
| &meta, | |
| parse_cfg_entry, | |
| &CFG_ATTR_TEMPLATE, | |
| ) | |
| .map_err(|_err: ErrorGuaranteed| { | |
| // We have an `ErrorGuaranteed` so this delayed bug cannot fail, but we need a `Diag` for the `PResult` so we make one anyways | |
| let mut diag = sess.dcx().struct_err( | |
| "cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.", | |
| ); | |
| diag.downgrade_to_delayed_bug(); | |
| diag | |
| })?; | |
| parser.expect(exp!(Comma))?; | |
| // Presumably, the majority of the time there will only be one attr. | |
| let mut expanded_attrs = Vec::with_capacity(1); | |
| while parser.token != token::Eof { | |
| let lo = parser.token.span; | |
| let item = parser.parse_attr_item(ForceCollect::Yes)?; | |
| expanded_attrs.push((item, lo.to(parser.prev_token.span))); | |
| if !parser.eat(exp!(Comma)) { | |
| break; | |
| } | |
| } | |
| Ok((cfg_predicate, expanded_attrs)) | |
| } | |
| 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 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(); | |
| } | |
| } |