use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token}; use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt}; use rustc_expand::expand::AstFragment; use rustc_feature::AttributeTemplate; use rustc_lint_defs::BuiltinLintDiag; use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES; use rustc_parse::{parser, validate_attr}; use rustc_session::errors::report_lit_error; use rustc_span::{BytePos, Span, Symbol}; use crate::errors; pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) { // All the built-in macro attributes are "words" at the moment. let template = AttributeTemplate { word: true, ..Default::default() }; validate_attr::check_builtin_meta_item( &ecx.sess.psess, meta_item, AttrStyle::Outer, name, template, true, ); } /// Emit a warning if the item is annotated with the given attribute. This is used to diagnose when /// an attribute may have been mistakenly duplicated. pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) { let attrs: Option<&[Attribute]> = match item { Annotatable::Item(item) => Some(&item.attrs), Annotatable::AssocItem(item, _) => Some(&item.attrs), Annotatable::ForeignItem(item) => Some(&item.attrs), Annotatable::Expr(expr) => Some(&expr.attrs), Annotatable::Arm(arm) => Some(&arm.attrs), Annotatable::ExprField(field) => Some(&field.attrs), Annotatable::PatField(field) => Some(&field.attrs), Annotatable::GenericParam(param) => Some(¶m.attrs), Annotatable::Param(param) => Some(¶m.attrs), Annotatable::FieldDef(def) => Some(&def.attrs), Annotatable::Variant(variant) => Some(&variant.attrs), _ => None, }; if let Some(attrs) = attrs { if let Some(attr) = attr::find_by_name(attrs, name) { ecx.psess().buffer_lint( DUPLICATE_MACRO_ATTRIBUTES, attr.span, ecx.current_expansion.lint_node_id, BuiltinLintDiag::DuplicateMacroAttribute, ); } } } /// `Ok` represents successfully retrieving the string literal at the correct /// position, e.g., `println("abc")`. type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>; /// - `Ok` is returned when the conversion to a string literal is unsuccessful, /// but another type of expression is obtained instead. /// - `Err` is returned when the conversion process fails. type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>; /// Extracts a string literal from the macro expanded version of `expr`, /// returning a diagnostic error of `err_msg` if `expr` is not a string literal. /// The returned bool indicates whether an applicable suggestion has already been /// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))` /// indicates that an ast error was encountered. // FIXME(Nilstrieb) Make this function setup translatable #[allow(rustc::untranslatable_diagnostic)] pub(crate) fn expr_to_spanned_string<'a>( cx: &'a mut ExtCtxt<'_>, expr: P, err_msg: &'static str, ) -> ExpandResult, ()> { if !cx.force_mode && let ast::ExprKind::MacCall(m) = &expr.kind && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() { return ExpandResult::Retry(()); } // Perform eager expansion on the expression. // We want to be able to handle e.g., `concat!("foo", "bar")`. let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); ExpandResult::Ready(Err(match expr.kind { ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) { Ok(ast::LitKind::Str(s, style)) => { return ExpandResult::Ready(Ok((s, style, expr.span))); } Ok(ast::LitKind::ByteStr(..)) => { let mut err = cx.dcx().struct_span_err(expr.span, err_msg); let span = expr.span.shrink_to_lo(); err.span_suggestion( span.with_hi(span.lo() + BytePos(1)), "consider removing the leading `b`", "", Applicability::MaybeIncorrect, ); Ok((err, true)) } Ok(ast::LitKind::Err(guar)) => Err(guar), Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)), _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), }, ast::ExprKind::Err(guar) => Err(guar), ast::ExprKind::Dummy => { cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`") } _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), })) } /// Extracts a string literal from the macro expanded version of `expr`, /// emitting `err_msg` if `expr` is not a string literal. This does not stop /// compilation on error, merely emits a non-fatal error and returns `Err`. pub(crate) fn expr_to_string( cx: &mut ExtCtxt<'_>, expr: P, err_msg: &'static str, ) -> ExpandResult, ()> { expr_to_spanned_string(cx, expr, err_msg).map(|res| { res.map_err(|err| match err { Ok((err, _)) => err.emit(), Err(guar) => guar, }) .map(|(symbol, style, _)| (symbol, style)) }) } /// Non-fatally assert that `tts` is empty. Note that this function /// returns even when `tts` is non-empty, macros that *need* to stop /// compilation should call `cx.diagnostic().abort_if_errors()` /// (this should be done as rarely as possible). pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) { if !tts.is_empty() { cx.dcx().emit_err(errors::TakesNoArguments { span, name }); } } /// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`. pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result, ErrorGuaranteed> { let guar = match p.parse_expr() { Ok(expr) => return Ok(expr), Err(err) => err.emit(), }; while p.token != token::Eof { p.bump(); } Err(guar) } /// Interpreting `tts` as a comma-separated sequence of expressions, /// expect exactly one string literal, or emit an error and return `Err`. pub(crate) fn get_single_str_from_tts( cx: &mut ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str, ) -> ExpandResult, ()> { get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s)) } pub(crate) fn get_single_str_spanned_from_tts( cx: &mut ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str, ) -> ExpandResult, ()> { let ExpandResult::Ready(ret) = get_single_expr_from_tts(cx, span, tts, name) else { return ExpandResult::Retry(()); }; let ret = match ret { Ok(ret) => ret, Err(e) => return ExpandResult::Ready(Err(e)), }; expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| { res.map_err(|err| match err { Ok((err, _)) => err.emit(), Err(guar) => guar, }) .map(|(symbol, _style, span)| (symbol, span)) }) } /// Interpreting `tts` as a comma-separated sequence of expressions, /// expect exactly one expression, or emit an error and return `Err`. pub(crate) fn get_single_expr_from_tts( cx: &mut ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str, ) -> ExpandResult, ErrorGuaranteed>, ()> { let mut p = cx.new_parser_from_tts(tts); if p.token == token::Eof { let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); return ExpandResult::Ready(Err(guar)); } let ret = match parse_expr(&mut p) { Ok(ret) => ret, Err(guar) => return ExpandResult::Ready(Err(guar)), }; let _ = p.eat(&token::Comma); if p.token != token::Eof { cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); } ExpandResult::Ready(Ok(ret)) } /// Extracts comma-separated expressions from `tts`. /// On error, emit it, and return `Err`. pub(crate) fn get_exprs_from_tts( cx: &mut ExtCtxt<'_>, tts: TokenStream, ) -> ExpandResult>, ErrorGuaranteed>, ()> { let mut p = cx.new_parser_from_tts(tts); let mut es = Vec::new(); while p.token != token::Eof { let expr = match parse_expr(&mut p) { Ok(expr) => expr, Err(guar) => return ExpandResult::Ready(Err(guar)), }; if !cx.force_mode && let ast::ExprKind::MacCall(m) = &expr.kind && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() { return ExpandResult::Retry(()); } // Perform eager expansion on the expression. // We want to be able to handle e.g., `concat!("foo", "bar")`. let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); es.push(expr); if p.eat(&token::Comma) { continue; } if p.token != token::Eof { let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span }); return ExpandResult::Ready(Err(guar)); } } ExpandResult::Ready(Ok(es)) }