Skip to main content

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