|
1 |
| -use rustc_ast::{attr, AttrStyle, Attribute, MetaItem}; |
2 |
| -use rustc_expand::base::{Annotatable, ExtCtxt}; |
| 1 | +use crate::errors; |
| 2 | +use rustc_ast::tokenstream::TokenStream; |
| 3 | +use rustc_ast::{self as ast, attr, ptr::P, token, AttrStyle, Attribute, MetaItem}; |
| 4 | +use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; |
| 5 | +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt}; |
| 6 | +use rustc_expand::expand::AstFragment; |
3 | 7 | use rustc_feature::AttributeTemplate;
|
4 | 8 | use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES;
|
5 |
| -use rustc_parse::validate_attr; |
6 |
| -use rustc_span::Symbol; |
| 9 | +use rustc_parse::{parser, validate_attr}; |
| 10 | +use rustc_session::errors::report_lit_error; |
| 11 | +use rustc_span::{BytePos, Span, Symbol}; |
7 | 12 |
|
8 | 13 | pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
|
9 | 14 | // All the built-in macro attributes are "words" at the moment.
|
@@ -46,3 +51,178 @@ pub fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name:
|
46 | 51 | }
|
47 | 52 | }
|
48 | 53 | }
|
| 54 | + |
| 55 | +/// `Ok` represents successfully retrieving the string literal at the correct |
| 56 | +/// position, e.g., `println("abc")`. |
| 57 | +type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>; |
| 58 | + |
| 59 | +/// - `Ok` is returned when the conversion to a string literal is unsuccessful, |
| 60 | +/// but another type of expression is obtained instead. |
| 61 | +/// - `Err` is returned when the conversion process fails. |
| 62 | +type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>; |
| 63 | + |
| 64 | +/// Extracts a string literal from the macro expanded version of `expr`, |
| 65 | +/// returning a diagnostic error of `err_msg` if `expr` is not a string literal. |
| 66 | +/// The returned bool indicates whether an applicable suggestion has already been |
| 67 | +/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))` |
| 68 | +/// indicates that an ast error was encountered. |
| 69 | +// FIXME(Nilstrieb) Make this function setup translatable |
| 70 | +#[allow(rustc::untranslatable_diagnostic)] |
| 71 | +pub(crate) fn expr_to_spanned_string<'a>( |
| 72 | + cx: &'a mut ExtCtxt<'_>, |
| 73 | + expr: P<ast::Expr>, |
| 74 | + err_msg: &'static str, |
| 75 | +) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> { |
| 76 | + if !cx.force_mode |
| 77 | + && let ast::ExprKind::MacCall(m) = &expr.kind |
| 78 | + && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() |
| 79 | + { |
| 80 | + return ExpandResult::Retry(()); |
| 81 | + } |
| 82 | + |
| 83 | + // Perform eager expansion on the expression. |
| 84 | + // We want to be able to handle e.g., `concat!("foo", "bar")`. |
| 85 | + let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); |
| 86 | + |
| 87 | + ExpandResult::Ready(Err(match expr.kind { |
| 88 | + ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) { |
| 89 | + Ok(ast::LitKind::Str(s, style)) => { |
| 90 | + return ExpandResult::Ready(Ok((s, style, expr.span))); |
| 91 | + } |
| 92 | + Ok(ast::LitKind::ByteStr(..)) => { |
| 93 | + let mut err = cx.dcx().struct_span_err(expr.span, err_msg); |
| 94 | + let span = expr.span.shrink_to_lo(); |
| 95 | + err.span_suggestion( |
| 96 | + span.with_hi(span.lo() + BytePos(1)), |
| 97 | + "consider removing the leading `b`", |
| 98 | + "", |
| 99 | + Applicability::MaybeIncorrect, |
| 100 | + ); |
| 101 | + Ok((err, true)) |
| 102 | + } |
| 103 | + Ok(ast::LitKind::Err(guar)) => Err(guar), |
| 104 | + Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)), |
| 105 | + _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), |
| 106 | + }, |
| 107 | + ast::ExprKind::Err(guar) => Err(guar), |
| 108 | + ast::ExprKind::Dummy => { |
| 109 | + cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`") |
| 110 | + } |
| 111 | + _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), |
| 112 | + })) |
| 113 | +} |
| 114 | + |
| 115 | +/// Extracts a string literal from the macro expanded version of `expr`, |
| 116 | +/// emitting `err_msg` if `expr` is not a string literal. This does not stop |
| 117 | +/// compilation on error, merely emits a non-fatal error and returns `Err`. |
| 118 | +pub(crate) fn expr_to_string( |
| 119 | + cx: &mut ExtCtxt<'_>, |
| 120 | + expr: P<ast::Expr>, |
| 121 | + err_msg: &'static str, |
| 122 | +) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> { |
| 123 | + expr_to_spanned_string(cx, expr, err_msg).map(|res| { |
| 124 | + res.map_err(|err| match err { |
| 125 | + Ok((err, _)) => err.emit(), |
| 126 | + Err(guar) => guar, |
| 127 | + }) |
| 128 | + .map(|(symbol, style, _)| (symbol, style)) |
| 129 | + }) |
| 130 | +} |
| 131 | + |
| 132 | +/// Non-fatally assert that `tts` is empty. Note that this function |
| 133 | +/// returns even when `tts` is non-empty, macros that *need* to stop |
| 134 | +/// compilation should call `cx.diagnostic().abort_if_errors()` |
| 135 | +/// (this should be done as rarely as possible). |
| 136 | +pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) { |
| 137 | + if !tts.is_empty() { |
| 138 | + cx.dcx().emit_err(errors::TakesNoArguments { span, name }); |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`. |
| 143 | +pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> { |
| 144 | + let guar = match p.parse_expr() { |
| 145 | + Ok(expr) => return Ok(expr), |
| 146 | + Err(err) => err.emit(), |
| 147 | + }; |
| 148 | + while p.token != token::Eof { |
| 149 | + p.bump(); |
| 150 | + } |
| 151 | + Err(guar) |
| 152 | +} |
| 153 | + |
| 154 | +/// Interpreting `tts` as a comma-separated sequence of expressions, |
| 155 | +/// expect exactly one string literal, or emit an error and return `Err`. |
| 156 | +pub(crate) fn get_single_str_from_tts( |
| 157 | + cx: &mut ExtCtxt<'_>, |
| 158 | + span: Span, |
| 159 | + tts: TokenStream, |
| 160 | + name: &str, |
| 161 | +) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> { |
| 162 | + get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s)) |
| 163 | +} |
| 164 | + |
| 165 | +pub(crate) fn get_single_str_spanned_from_tts( |
| 166 | + cx: &mut ExtCtxt<'_>, |
| 167 | + span: Span, |
| 168 | + tts: TokenStream, |
| 169 | + name: &str, |
| 170 | +) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> { |
| 171 | + let mut p = cx.new_parser_from_tts(tts); |
| 172 | + if p.token == token::Eof { |
| 173 | + let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); |
| 174 | + return ExpandResult::Ready(Err(guar)); |
| 175 | + } |
| 176 | + let ret = match parse_expr(&mut p) { |
| 177 | + Ok(ret) => ret, |
| 178 | + Err(guar) => return ExpandResult::Ready(Err(guar)), |
| 179 | + }; |
| 180 | + let _ = p.eat(&token::Comma); |
| 181 | + |
| 182 | + if p.token != token::Eof { |
| 183 | + cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); |
| 184 | + } |
| 185 | + expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| { |
| 186 | + res.map_err(|err| match err { |
| 187 | + Ok((err, _)) => err.emit(), |
| 188 | + Err(guar) => guar, |
| 189 | + }) |
| 190 | + .map(|(symbol, _style, span)| (symbol, span)) |
| 191 | + }) |
| 192 | +} |
| 193 | + |
| 194 | +/// Extracts comma-separated expressions from `tts`. |
| 195 | +/// On error, emit it, and return `Err`. |
| 196 | +pub(crate) fn get_exprs_from_tts( |
| 197 | + cx: &mut ExtCtxt<'_>, |
| 198 | + tts: TokenStream, |
| 199 | +) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> { |
| 200 | + let mut p = cx.new_parser_from_tts(tts); |
| 201 | + let mut es = Vec::new(); |
| 202 | + while p.token != token::Eof { |
| 203 | + let expr = match parse_expr(&mut p) { |
| 204 | + Ok(expr) => expr, |
| 205 | + Err(guar) => return ExpandResult::Ready(Err(guar)), |
| 206 | + }; |
| 207 | + if !cx.force_mode |
| 208 | + && let ast::ExprKind::MacCall(m) = &expr.kind |
| 209 | + && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() |
| 210 | + { |
| 211 | + return ExpandResult::Retry(()); |
| 212 | + } |
| 213 | + |
| 214 | + // Perform eager expansion on the expression. |
| 215 | + // We want to be able to handle e.g., `concat!("foo", "bar")`. |
| 216 | + let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); |
| 217 | + |
| 218 | + es.push(expr); |
| 219 | + if p.eat(&token::Comma) { |
| 220 | + continue; |
| 221 | + } |
| 222 | + if p.token != token::Eof { |
| 223 | + let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span }); |
| 224 | + return ExpandResult::Ready(Err(guar)); |
| 225 | + } |
| 226 | + } |
| 227 | + ExpandResult::Ready(Ok(es)) |
| 228 | +} |
0 commit comments