249 lines
9.6 KiB
Rust
249 lines
9.6 KiB
Rust
use rustc_ast::ptr::P;
|
|
use rustc_ast::tokenstream::TokenStream;
|
|
use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token};
|
|
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::BuiltinLintDiag;
|
|
use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES;
|
|
use rustc_parse::{parser, validate_attr};
|
|
use rustc_session::errors::report_lit_error;
|
|
use rustc_span::{BytePos, Span, Symbol};
|
|
|
|
use crate::errors;
|
|
|
|
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(
|
|
&ecx.sess.psess,
|
|
meta_item,
|
|
AttrStyle::Outer,
|
|
name,
|
|
template,
|
|
true,
|
|
);
|
|
}
|
|
|
|
/// 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(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::AssocItem(item, _) => Some(&item.attrs),
|
|
Annotatable::ForeignItem(item) => Some(&item.attrs),
|
|
Annotatable::Expr(expr) => Some(&expr.attrs),
|
|
Annotatable::Arm(arm) => Some(&arm.attrs),
|
|
Annotatable::ExprField(field) => Some(&field.attrs),
|
|
Annotatable::PatField(field) => Some(&field.attrs),
|
|
Annotatable::GenericParam(param) => Some(¶m.attrs),
|
|
Annotatable::Param(param) => Some(¶m.attrs),
|
|
Annotatable::FieldDef(def) => Some(&def.attrs),
|
|
Annotatable::Variant(variant) => Some(&variant.attrs),
|
|
_ => None,
|
|
};
|
|
if let Some(attrs) = attrs {
|
|
if let Some(attr) = attr::find_by_name(attrs, name) {
|
|
ecx.psess().buffer_lint(
|
|
DUPLICATE_MACRO_ATTRIBUTES,
|
|
attr.span,
|
|
ecx.current_expansion.lint_node_id,
|
|
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 ExpandResult::Ready(ret) = get_single_expr_from_tts(cx, span, tts, name) else {
|
|
return ExpandResult::Retry(());
|
|
};
|
|
let ret = match ret {
|
|
Ok(ret) => ret,
|
|
Err(e) => return ExpandResult::Ready(Err(e)),
|
|
};
|
|
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))
|
|
})
|
|
}
|
|
|
|
/// Interpreting `tts` as a comma-separated sequence of expressions,
|
|
/// expect exactly one expression, or emit an error and return `Err`.
|
|
pub(crate) fn get_single_expr_from_tts(
|
|
cx: &mut ExtCtxt<'_>,
|
|
span: Span,
|
|
tts: TokenStream,
|
|
name: &str,
|
|
) -> ExpandResult<Result<P<ast::Expr>, 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 });
|
|
}
|
|
ExpandResult::Ready(Ok(ret))
|
|
}
|
|
|
|
/// 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))
|
|
}
|