rustc_builtin_macros/
util.rs

1use rustc_ast::ptr::P;
2use rustc_ast::tokenstream::TokenStream;
3use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token};
4use rustc_errors::{Applicability, Diag, ErrorGuaranteed};
5use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt};
6use rustc_expand::expand::AstFragment;
7use rustc_feature::AttributeTemplate;
8use rustc_lint_defs::BuiltinLintDiag;
9use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES;
10use rustc_parse::{exp, parser, validate_attr};
11use rustc_session::errors::report_lit_error;
12use rustc_span::{BytePos, Span, Symbol};
13
14use crate::errors;
15
16pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
17    // All the built-in macro attributes are "words" at the moment.
18    let template = AttributeTemplate { word: true, ..Default::default() };
19    validate_attr::check_builtin_meta_item(
20        &ecx.sess.psess,
21        meta_item,
22        AttrStyle::Outer,
23        name,
24        template,
25        true,
26    );
27}
28
29/// Emit a warning if the item is annotated with the given attribute. This is used to diagnose when
30/// an attribute may have been mistakenly duplicated.
31pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) {
32    let attrs: Option<&[Attribute]> = match item {
33        Annotatable::Item(item) => Some(&item.attrs),
34        Annotatable::AssocItem(item, _) => Some(&item.attrs),
35        Annotatable::ForeignItem(item) => Some(&item.attrs),
36        Annotatable::Expr(expr) => Some(&expr.attrs),
37        Annotatable::Arm(arm) => Some(&arm.attrs),
38        Annotatable::ExprField(field) => Some(&field.attrs),
39        Annotatable::PatField(field) => Some(&field.attrs),
40        Annotatable::GenericParam(param) => Some(&param.attrs),
41        Annotatable::Param(param) => Some(&param.attrs),
42        Annotatable::FieldDef(def) => Some(&def.attrs),
43        Annotatable::Variant(variant) => Some(&variant.attrs),
44        _ => None,
45    };
46    if let Some(attrs) = attrs {
47        if let Some(attr) = attr::find_by_name(attrs, name) {
48            ecx.psess().buffer_lint(
49                DUPLICATE_MACRO_ATTRIBUTES,
50                attr.span,
51                ecx.current_expansion.lint_node_id,
52                BuiltinLintDiag::DuplicateMacroAttribute,
53            );
54        }
55    }
56}
57
58/// `Ok` represents successfully retrieving the string literal at the correct
59/// position, e.g., `println("abc")`.
60pub(crate) type ExprToSpannedStringResult<'a> = Result<ExprToSpannedString, UnexpectedExprKind<'a>>;
61
62pub(crate) struct ExprToSpannedString {
63    pub symbol: Symbol,
64    pub style: ast::StrStyle,
65    pub span: Span,
66    /// The raw string literal, with no escaping or processing.
67    ///
68    /// Generally only useful for lints that care about the raw bytes the user wrote.
69    pub uncooked_symbol: (ast::token::LitKind, Symbol),
70}
71
72/// - `Ok` is returned when the conversion to a string literal is unsuccessful,
73/// but another type of expression is obtained instead.
74/// - `Err` is returned when the conversion process fails.
75type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>;
76
77/// Extracts a string literal from the macro expanded version of `expr`,
78/// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
79/// The returned bool indicates whether an applicable suggestion has already been
80/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))`
81/// indicates that an ast error was encountered.
82// FIXME(Nilstrieb) Make this function setup translatable
83#[allow(rustc::untranslatable_diagnostic)]
84pub(crate) fn expr_to_spanned_string<'a>(
85    cx: &'a mut ExtCtxt<'_>,
86    expr: P<ast::Expr>,
87    err_msg: &'static str,
88) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> {
89    if !cx.force_mode
90        && let ast::ExprKind::MacCall(m) = &expr.kind
91        && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
92    {
93        return ExpandResult::Retry(());
94    }
95
96    // Perform eager expansion on the expression.
97    // We want to be able to handle e.g., `concat!("foo", "bar")`.
98    let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
99
100    ExpandResult::Ready(Err(match expr.kind {
101        ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
102            Ok(ast::LitKind::Str(s, style)) => {
103                return ExpandResult::Ready(Ok(ExprToSpannedString {
104                    symbol: s,
105                    style,
106                    span: expr.span,
107                    uncooked_symbol: (token_lit.kind, token_lit.symbol),
108                }));
109            }
110            Ok(ast::LitKind::ByteStr(..)) => {
111                let mut err = cx.dcx().struct_span_err(expr.span, err_msg);
112                let span = expr.span.shrink_to_lo();
113                err.span_suggestion(
114                    span.with_hi(span.lo() + BytePos(1)),
115                    "consider removing the leading `b`",
116                    "",
117                    Applicability::MaybeIncorrect,
118                );
119                Ok((err, true))
120            }
121            Ok(ast::LitKind::Err(guar)) => Err(guar),
122            Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)),
123            _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
124        },
125        ast::ExprKind::Err(guar) => Err(guar),
126        ast::ExprKind::Dummy => {
127            cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`")
128        }
129        _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
130    }))
131}
132
133/// Extracts a string literal from the macro expanded version of `expr`,
134/// emitting `err_msg` if `expr` is not a string literal. This does not stop
135/// compilation on error, merely emits a non-fatal error and returns `Err`.
136pub(crate) fn expr_to_string(
137    cx: &mut ExtCtxt<'_>,
138    expr: P<ast::Expr>,
139    err_msg: &'static str,
140) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> {
141    expr_to_spanned_string(cx, expr, err_msg).map(|res| {
142        res.map_err(|err| match err {
143            Ok((err, _)) => err.emit(),
144            Err(guar) => guar,
145        })
146        .map(|ExprToSpannedString { symbol, style, .. }| (symbol, style))
147    })
148}
149
150/// Non-fatally assert that `tts` is empty. Note that this function
151/// returns even when `tts` is non-empty, macros that *need* to stop
152/// compilation should call `cx.diagnostic().abort_if_errors()`
153/// (this should be done as rarely as possible).
154pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) {
155    if !tts.is_empty() {
156        cx.dcx().emit_err(errors::TakesNoArguments { span, name });
157    }
158}
159
160/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`.
161pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> {
162    let guar = match p.parse_expr() {
163        Ok(expr) => return Ok(expr),
164        Err(err) => err.emit(),
165    };
166    while p.token != token::Eof {
167        p.bump();
168    }
169    Err(guar)
170}
171
172/// Interpreting `tts` as a comma-separated sequence of expressions,
173/// expect exactly one string literal, or emit an error and return `Err`.
174pub(crate) fn get_single_str_from_tts(
175    cx: &mut ExtCtxt<'_>,
176    span: Span,
177    tts: TokenStream,
178    name: &str,
179) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
180    get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s))
181}
182
183pub(crate) fn get_single_str_spanned_from_tts(
184    cx: &mut ExtCtxt<'_>,
185    span: Span,
186    tts: TokenStream,
187    name: &str,
188) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
189    let ExpandResult::Ready(ret) = get_single_expr_from_tts(cx, span, tts, name) else {
190        return ExpandResult::Retry(());
191    };
192    let ret = match ret {
193        Ok(ret) => ret,
194        Err(e) => return ExpandResult::Ready(Err(e)),
195    };
196    expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
197        res.map_err(|err| match err {
198            Ok((err, _)) => err.emit(),
199            Err(guar) => guar,
200        })
201        .map(|ExprToSpannedString { symbol, span, .. }| (symbol, span))
202    })
203}
204
205/// Interpreting `tts` as a comma-separated sequence of expressions,
206/// expect exactly one expression, or emit an error and return `Err`.
207pub(crate) fn get_single_expr_from_tts(
208    cx: &mut ExtCtxt<'_>,
209    span: Span,
210    tts: TokenStream,
211    name: &str,
212) -> ExpandResult<Result<P<ast::Expr>, ErrorGuaranteed>, ()> {
213    let mut p = cx.new_parser_from_tts(tts);
214    if p.token == token::Eof {
215        let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
216        return ExpandResult::Ready(Err(guar));
217    }
218    let ret = match parse_expr(&mut p) {
219        Ok(ret) => ret,
220        Err(guar) => return ExpandResult::Ready(Err(guar)),
221    };
222    let _ = p.eat(exp!(Comma));
223
224    if p.token != token::Eof {
225        cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
226    }
227    ExpandResult::Ready(Ok(ret))
228}
229
230/// Extracts comma-separated expressions from `tts`.
231/// On error, emit it, and return `Err`.
232pub(crate) fn get_exprs_from_tts(
233    cx: &mut ExtCtxt<'_>,
234    tts: TokenStream,
235) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> {
236    let mut p = cx.new_parser_from_tts(tts);
237    let mut es = Vec::new();
238    while p.token != token::Eof {
239        let expr = match parse_expr(&mut p) {
240            Ok(expr) => expr,
241            Err(guar) => return ExpandResult::Ready(Err(guar)),
242        };
243        if !cx.force_mode
244            && let ast::ExprKind::MacCall(m) = &expr.kind
245            && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
246        {
247            return ExpandResult::Retry(());
248        }
249
250        // Perform eager expansion on the expression.
251        // We want to be able to handle e.g., `concat!("foo", "bar")`.
252        let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
253
254        es.push(expr);
255        if p.eat(exp!(Comma)) {
256            continue;
257        }
258        if p.token != token::Eof {
259            let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
260            return ExpandResult::Ready(Err(guar));
261        }
262    }
263    ExpandResult::Ready(Ok(es))
264}