rustc_builtin_macros/
util.rs

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