about summary refs log tree commit diff
path: root/compiler/rustc_builtin_macros/src/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_builtin_macros/src/util.rs')
-rw-r--r--compiler/rustc_builtin_macros/src/util.rs196
1 files changed, 188 insertions, 8 deletions
diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs
index ad6b09ba574..652e34268ea 100644
--- a/compiler/rustc_builtin_macros/src/util.rs
+++ b/compiler/rustc_builtin_macros/src/util.rs
@@ -1,11 +1,16 @@
-use rustc_ast::{attr, AttrStyle, Attribute, MetaItem};
-use rustc_expand::base::{Annotatable, ExtCtxt};
+use crate::errors;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::{self as ast, attr, ptr::P, token, AttrStyle, Attribute, MetaItem};
+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::builtin::DUPLICATE_MACRO_ATTRIBUTES;
-use rustc_parse::validate_attr;
-use rustc_span::Symbol;
+use rustc_lint_defs::{builtin::DUPLICATE_MACRO_ATTRIBUTES, BuiltinLintDiag};
+use rustc_parse::{parser, validate_attr};
+use rustc_session::errors::report_lit_error;
+use rustc_span::{BytePos, Span, Symbol};
 
-pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
+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(
@@ -19,7 +24,7 @@ pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, na
 
 /// 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 fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) {
+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::TraitItem(item) => Some(&item.attrs),
@@ -41,8 +46,183 @@ pub fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name:
                 DUPLICATE_MACRO_ATTRIBUTES,
                 attr.span,
                 ecx.current_expansion.lint_node_id,
-                "duplicated attribute",
+                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<ast::Expr>,
+    err_msg: &'static str,
+) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> {
+    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<ast::Expr>,
+    err_msg: &'static str,
+) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> {
+    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<P<ast::Expr>, 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<Result<Symbol, ErrorGuaranteed>, ()> {
+    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<Result<(Symbol, Span), 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 });
+    }
+    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))
+    })
+}
+
+/// 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<Result<Vec<P<ast::Expr>>, 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))
+}