//! The compiler code necessary to support the cfg! extension, which expands to
//! a literal `true` or `false` based on whether the given cfg matches the
//! current compilation environment.

use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{AttrStyle, token};
use rustc_attr_parsing::parser::{AllowExprMetavar, MetaItemOrLitParser};
use rustc_attr_parsing::{
    self as attr, AttributeParser, AttributeSafety, CFG_TEMPLATE, ParsedDescription, ShouldEmit,
    parse_cfg_entry,
};
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
use rustc_hir::attrs::CfgEntry;
use rustc_hir::{AttrPath, Target};
use rustc_parse::exp;
use rustc_parse::parser::Recovery;
use rustc_span::{ErrorGuaranteed, Span, sym};

use crate::errors;

pub(crate) fn expand_cfg(
    cx: &mut ExtCtxt<'_>,
    sp: Span,
    tts: TokenStream,
) -> MacroExpanderResult<'static> {
    let sp = cx.with_def_site_ctxt(sp);

    ExpandResult::Ready(match parse_cfg(cx, sp, tts) {
        Ok(cfg) => {
            let matches_cfg = attr::eval_config_entry(cx.sess, &cfg).as_bool();

            MacEager::expr(cx.expr_bool(sp, matches_cfg))
        }
        Err(guar) => DummyResult::any(sp, guar),
    })
}

fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result<CfgEntry, ErrorGuaranteed> {
    let mut parser = cx.new_parser_from_tts(tts);
    if parser.token == token::Eof {
        return Err(cx.dcx().emit_err(errors::RequiresCfgPattern { span }));
    }

    let meta = MetaItemOrLitParser::parse_single(
        &mut parser,
        ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
        AllowExprMetavar::Yes,
    )
    .map_err(|diag| diag.emit())?;
    let cfg = AttributeParser::parse_single_args(
        cx.sess,
        span,
        span,
        AttrStyle::Inner,
        AttrPath { segments: vec![sym::cfg].into_boxed_slice(), span },
        None,
        AttributeSafety::Normal,
        ParsedDescription::Macro,
        span,
        cx.current_expansion.lint_node_id,
        // Doesn't matter what the target actually is here.
        Target::Crate,
        Some(cx.ecfg.features),
        ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
        &meta,
        parse_cfg_entry,
        &CFG_TEMPLATE,
    )?;

    let _ = parser.eat(exp!(Comma));

    if !parser.eat(exp!(Eof)) {
        return Err(cx.dcx().emit_err(errors::OneCfgPattern { span }));
    }

    Ok(cfg)
}
