From 631cd5845e8de329d0e227aaa707d7ea228b8f8f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:20:29 +0200 Subject: Merging upstream version 1.70.0+dfsg1. Signed-off-by: Daniel Baumann --- .../src/alloc_error_handler.rs | 3 +- compiler/rustc_builtin_macros/src/asm.rs | 35 +- compiler/rustc_builtin_macros/src/assert.rs | 28 +- .../rustc_builtin_macros/src/assert/context.rs | 3 +- compiler/rustc_builtin_macros/src/cfg.rs | 21 +- .../rustc_builtin_macros/src/cfg_accessible.rs | 18 +- compiler/rustc_builtin_macros/src/cmdline_attrs.rs | 4 +- compiler/rustc_builtin_macros/src/compile_error.rs | 5 + compiler/rustc_builtin_macros/src/concat.rs | 23 +- compiler/rustc_builtin_macros/src/concat_bytes.rs | 77 +-- compiler/rustc_builtin_macros/src/concat_idents.rs | 8 +- compiler/rustc_builtin_macros/src/derive.rs | 40 +- .../rustc_builtin_macros/src/deriving/default.rs | 134 ++--- .../src/deriving/generic/mod.rs | 7 +- compiler/rustc_builtin_macros/src/env.rs | 30 +- compiler/rustc_builtin_macros/src/errors.rs | 553 +++++++++++++++++++++ compiler/rustc_builtin_macros/src/format.rs | 254 +++++----- .../rustc_builtin_macros/src/global_allocator.rs | 4 +- compiler/rustc_builtin_macros/src/lib.rs | 5 +- .../rustc_builtin_macros/src/proc_macro_harness.rs | 20 +- .../src/standard_library_imports.rs | 19 +- compiler/rustc_builtin_macros/src/test.rs | 185 ++++--- compiler/rustc_builtin_macros/src/test_harness.rs | 30 +- compiler/rustc_builtin_macros/src/util.rs | 4 +- 24 files changed, 1001 insertions(+), 509 deletions(-) create mode 100644 compiler/rustc_builtin_macros/src/errors.rs (limited to 'compiler/rustc_builtin_macros/src') diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs index ac6697232..82bae9157 100644 --- a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs +++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs @@ -1,3 +1,4 @@ +use crate::errors; use crate::util::check_builtin_macro_attribute; use rustc_ast::ptr::P; @@ -31,7 +32,7 @@ pub fn expand( { (item, true, ecx.with_def_site_ctxt(fn_kind.sig.span)) } else { - ecx.sess.parse_sess.span_diagnostic.span_err(item.span(), "alloc_error_handler must be a function"); + ecx.sess.parse_sess.span_diagnostic.emit_err(errors::AllocErrorMustBeFn {span: item.span() }); return vec![orig_item]; }; diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 3fdbc9715..8c1579baa 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -203,17 +203,6 @@ pub fn parse_asm_args<'a>( // Validate the order of named, positional & explicit register operands and // clobber_abi/options. We do this at the end once we have the full span // of the argument available. - if !args.options_spans.is_empty() { - diag.struct_span_err(span, "arguments are not allowed after options") - .span_labels(args.options_spans.clone(), "previous options") - .span_label(span, "argument") - .emit(); - } else if let Some((_, abi_span)) = args.clobber_abis.last() { - diag.struct_span_err(span, "arguments are not allowed after clobber_abi") - .span_label(*abi_span, "clobber_abi") - .span_label(span, "argument") - .emit(); - } if explicit_reg { if name.is_some() { diag.struct_span_err(span, "explicit register arguments cannot have names").emit(); @@ -227,17 +216,6 @@ pub fn parse_asm_args<'a>( .emit(); continue; } - if !args.reg_args.is_empty() { - let mut err = diag.struct_span_err( - span, - "named arguments cannot follow explicit register arguments", - ); - err.span_label(span, "named argument"); - for pos in &args.reg_args { - err.span_label(args.operands[*pos].1, "explicit register argument"); - } - err.emit(); - } args.named_args.insert(name, slot); } else { if !args.named_args.is_empty() || !args.reg_args.is_empty() { @@ -478,15 +456,6 @@ fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, let full_span = span_start.to(p.prev_token.span); - if !args.options_spans.is_empty() { - let mut err = p - .sess - .span_diagnostic - .struct_span_err(full_span, "clobber_abi is not allowed after options"); - err.span_labels(args.options_spans.clone(), "options"); - return Err(err); - } - match &new_abis[..] { // should have errored above during parsing [] => unreachable!(), @@ -699,6 +668,10 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PRes let mut parser = cx.new_parser_from_tts(stream); if parser.token == token::Eof { - let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument"); - err.span_label(sp, "boolean expression required"); - return Err(err); + return Err(cx.create_err(errors::AssertRequiresBoolean { span: sp })); } let cond_expr = parser.parse_expr()?; @@ -129,15 +128,7 @@ fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PRes // // Emit an error about semicolon and suggest removing it. if parser.token == token::Semi { - let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument"); - err.span_suggestion( - parser.token.span, - "try removing semicolon", - "", - Applicability::MaybeIncorrect, - ); - err.emit(); - + cx.emit_err(errors::AssertRequiresExpression { span: sp, token: parser.token.span }); parser.bump(); } @@ -149,15 +140,8 @@ fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PRes // Emit an error and suggest inserting a comma. let custom_message = if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind { - let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal"); - let comma_span = parser.prev_token.span.shrink_to_hi(); - err.span_suggestion_short( - comma_span, - "try adding a comma", - ", ", - Applicability::MaybeIncorrect, - ); - err.emit(); + let comma = parser.prev_token.span.shrink_to_hi(); + cx.emit_err(errors::AssertMissingComma { span: parser.token.span, comma }); parse_custom_message(&mut parser) } else if parser.eat(&token::Comma) { diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index b0b4dda16..c9e3cd486 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -287,10 +287,9 @@ impl<'cx, 'a> Context<'cx, 'a> { // sync with the `rfc-2011-nicer-assert-messages/all-expr-kinds.rs` test. ExprKind::Assign(_, _, _) | ExprKind::AssignOp(_, _, _) - | ExprKind::Async(_, _, _) + | ExprKind::Async(_, _) | ExprKind::Await(_) | ExprKind::Block(_, _) - | ExprKind::Box(_) | ExprKind::Break(_, _) | ExprKind::Closure(_) | ExprKind::ConstBlock(_) diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index 5638c2f61..1397cee7a 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -2,13 +2,13 @@ //! a literal `true` or `false` based on whether the given cfg matches the //! current compilation environment. +use crate::errors; use rustc_ast as ast; use rustc_ast::token; use rustc_ast::tokenstream::TokenStream; use rustc_attr as attr; use rustc_errors::PResult; use rustc_expand::base::{self, *}; -use rustc_macros::Diagnostic; use rustc_span::Span; pub fn expand_cfg( @@ -35,26 +35,11 @@ pub fn expand_cfg( } } -#[derive(Diagnostic)] -#[diag(builtin_macros_requires_cfg_pattern)] -struct RequiresCfgPattern { - #[primary_span] - #[label] - span: Span, -} - -#[derive(Diagnostic)] -#[diag(builtin_macros_expected_one_cfg_pattern)] -struct OneCfgPattern { - #[primary_span] - span: Span, -} - fn parse_cfg<'a>(cx: &mut ExtCtxt<'a>, span: Span, tts: TokenStream) -> PResult<'a, ast::MetaItem> { let mut p = cx.new_parser_from_tts(tts); if p.token == token::Eof { - return Err(cx.create_err(RequiresCfgPattern { span })); + return Err(cx.create_err(errors::RequiresCfgPattern { span })); } let cfg = p.parse_meta_item()?; @@ -62,7 +47,7 @@ fn parse_cfg<'a>(cx: &mut ExtCtxt<'a>, span: Span, tts: TokenStream) -> PResult< let _ = p.eat(&token::Comma); if !p.eat(&token::Eof) { - return Err(cx.create_err(OneCfgPattern { span })); + return Err(cx.create_err(errors::OneCfgPattern { span })); } Ok(cfg) diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs index 4e4cafc71..37ac09ccd 100644 --- a/compiler/rustc_builtin_macros/src/cfg_accessible.rs +++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs @@ -1,5 +1,6 @@ //! Implementation of the `#[cfg_accessible(path)]` attribute macro. +use crate::errors; use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier}; use rustc_feature::AttributeTemplate; @@ -10,15 +11,22 @@ use rustc_span::Span; pub(crate) struct Expander; fn validate_input<'a>(ecx: &mut ExtCtxt<'_>, mi: &'a ast::MetaItem) -> Option<&'a ast::Path> { + use errors::CfgAccessibleInvalid::*; match mi.meta_item_list() { None => {} - Some([]) => ecx.span_err(mi.span, "`cfg_accessible` path is not specified"), - Some([_, .., l]) => ecx.span_err(l.span(), "multiple `cfg_accessible` paths are specified"), + Some([]) => { + ecx.emit_err(UnspecifiedPath(mi.span)); + } + Some([_, .., l]) => { + ecx.emit_err(MultiplePaths(l.span())); + } Some([nmi]) => match nmi.meta_item() { - None => ecx.span_err(nmi.span(), "`cfg_accessible` path cannot be a literal"), + None => { + ecx.emit_err(LiteralPath(nmi.span())); + } Some(mi) => { if !mi.is_word() { - ecx.span_err(mi.span, "`cfg_accessible` path cannot accept arguments"); + ecx.emit_err(HasArguments(mi.span)); } return Some(&mi.path); } @@ -53,7 +61,7 @@ impl MultiItemModifier for Expander { Ok(true) => ExpandResult::Ready(vec![item]), Ok(false) => ExpandResult::Ready(Vec::new()), Err(Indeterminate) if ecx.force_mode => { - ecx.span_err(span, "cannot determine whether the path is accessible or not"); + ecx.emit_err(errors::CfgAccessibleIndeterminate { span }); ExpandResult::Ready(vec![item]) } Err(Indeterminate) => ExpandResult::Retry(item), diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs index db05c00d2..2b6fcc169 100644 --- a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs +++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs @@ -6,7 +6,7 @@ use rustc_ast::{self as ast, AttrItem, AttrStyle}; use rustc_session::parse::ParseSess; use rustc_span::FileName; -pub fn inject(mut krate: ast::Crate, parse_sess: &ParseSess, attrs: &[String]) -> ast::Crate { +pub fn inject(krate: &mut ast::Crate, parse_sess: &ParseSess, attrs: &[String]) { for raw_attr in attrs { let mut parser = rustc_parse::new_parser_from_source_str( parse_sess, @@ -36,6 +36,4 @@ pub fn inject(mut krate: ast::Crate, parse_sess: &ParseSess, attrs: &[String]) - start_span.to(end_span), )); } - - krate } diff --git a/compiler/rustc_builtin_macros/src/compile_error.rs b/compiler/rustc_builtin_macros/src/compile_error.rs index 72397aa25..aeb3bb800 100644 --- a/compiler/rustc_builtin_macros/src/compile_error.rs +++ b/compiler/rustc_builtin_macros/src/compile_error.rs @@ -13,6 +13,11 @@ pub fn expand_compile_error<'cx>( return DummyResult::any(sp); }; + #[expect( + rustc::diagnostic_outside_of_impl, + reason = "diagnostic message is specified by user" + )] + #[expect(rustc::untranslatable_diagnostic, reason = "diagnostic message is specified by user")] cx.span_err(sp, var.as_str()); DummyResult::any(sp) diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs index 7da9bdc38..b92964d03 100644 --- a/compiler/rustc_builtin_macros/src/concat.rs +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -4,6 +4,8 @@ use rustc_expand::base::{self, DummyResult}; use rustc_session::errors::report_lit_error; use rustc_span::symbol::Symbol; +use crate::errors; + pub fn expand_concat( cx: &mut base::ExtCtxt<'_>, sp: rustc_span::Span, @@ -31,7 +33,7 @@ pub fn expand_concat( accumulator.push_str(&b.to_string()); } Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => { - cx.span_err(e.span, "cannot concatenate a byte string literal"); + cx.emit_err(errors::ConcatBytestr { span: e.span }); has_errors = true; } Ok(ast::LitKind::Err) => { @@ -42,8 +44,20 @@ pub fn expand_concat( has_errors = true; } }, + // We also want to allow negative numeric literals. + ast::ExprKind::Unary(ast::UnOp::Neg, ref expr) if let ast::ExprKind::Lit(token_lit) = expr.kind => { + match ast::LitKind::from_token_lit(token_lit) { + Ok(ast::LitKind::Int(i, _)) => accumulator.push_str(&format!("-{i}")), + Ok(ast::LitKind::Float(f, _)) => accumulator.push_str(&format!("-{f}")), + Err(err) => { + report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span); + has_errors = true; + } + _ => missing_literal.push(e.span), + } + } ast::ExprKind::IncludedBytes(..) => { - cx.span_err(e.span, "cannot concatenate a byte string literal") + cx.emit_err(errors::ConcatBytestr { span: e.span }); } ast::ExprKind::Err => { has_errors = true; @@ -53,10 +67,9 @@ pub fn expand_concat( } } } + if !missing_literal.is_empty() { - let mut err = cx.struct_span_err(missing_literal, "expected a literal"); - err.note("only literals (like `\"foo\"`, `42` and `3.14`) can be passed to `concat!()`"); - err.emit(); + cx.emit_err(errors::ConcatMissingLiteral { spans: missing_literal }); return DummyResult::any(sp); } else if has_errors { return DummyResult::any(sp); diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs index 4f1a7d709..ba639c0a9 100644 --- a/compiler/rustc_builtin_macros/src/concat_bytes.rs +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -1,10 +1,11 @@ use rustc_ast as ast; use rustc_ast::{ptr::P, tokenstream::TokenStream}; -use rustc_errors::Applicability; use rustc_expand::base::{self, DummyResult}; use rustc_session::errors::report_lit_error; use rustc_span::Span; +use crate::errors; + /// Emits errors for literal expressions that are invalid inside and outside of an array. fn invalid_type_err( cx: &mut base::ExtCtxt<'_>, @@ -12,62 +13,46 @@ fn invalid_type_err( span: Span, is_nested: bool, ) { + use errors::{ + ConcatBytesInvalid, ConcatBytesInvalidSuggestion, ConcatBytesNonU8, ConcatBytesOob, + }; + let snippet = cx.sess.source_map().span_to_snippet(span).ok(); match ast::LitKind::from_token_lit(token_lit) { Ok(ast::LitKind::Char(_)) => { - let mut err = cx.struct_span_err(span, "cannot concatenate character literals"); - if let Ok(snippet) = cx.sess.source_map().span_to_snippet(span) { - err.span_suggestion( - span, - "try using a byte character", - format!("b{}", snippet), - Applicability::MachineApplicable, - ) - .emit(); - } + let sugg = + snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet }); + cx.sess.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg }); } Ok(ast::LitKind::Str(_, _)) => { - let mut err = cx.struct_span_err(span, "cannot concatenate string literals"); // suggestion would be invalid if we are nested - if !is_nested { - if let Ok(snippet) = cx.sess.source_map().span_to_snippet(span) { - err.span_suggestion( - span, - "try using a byte string", - format!("b{}", snippet), - Applicability::MachineApplicable, - ); - } - } - err.emit(); + let sugg = if !is_nested { + snippet.map(|snippet| ConcatBytesInvalidSuggestion::StrLit { span, snippet }) + } else { + None + }; + cx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg }); } Ok(ast::LitKind::Float(_, _)) => { - cx.span_err(span, "cannot concatenate float literals"); + cx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None }); } Ok(ast::LitKind::Bool(_)) => { - cx.span_err(span, "cannot concatenate boolean literals"); + cx.emit_err(ConcatBytesInvalid { span, lit_kind: "boolean", sugg: None }); } Ok(ast::LitKind::Err) => {} Ok(ast::LitKind::Int(_, _)) if !is_nested => { - let mut err = cx.struct_span_err(span, "cannot concatenate numeric literals"); - if let Ok(snippet) = cx.sess.source_map().span_to_snippet(span) { - err.span_suggestion( - span, - "try wrapping the number in an array", - format!("[{}]", snippet), - Applicability::MachineApplicable, - ); - } - err.emit(); + let sugg = + snippet.map(|snippet| ConcatBytesInvalidSuggestion::IntLit { span: span, snippet }); + cx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg }); } Ok(ast::LitKind::Int( val, ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8), )) => { assert!(val > u8::MAX.into()); // must be an error - cx.span_err(span, "numeric literal is out of bounds"); + cx.emit_err(ConcatBytesOob { span }); } Ok(ast::LitKind::Int(_, _)) => { - cx.span_err(span, "numeric literal is not a `u8`"); + cx.emit_err(ConcatBytesNonU8 { span }); } Ok(ast::LitKind::ByteStr(..) | ast::LitKind::Byte(_)) => unreachable!(), Err(err) => { @@ -85,7 +70,7 @@ fn handle_array_element( match expr.kind { ast::ExprKind::Array(_) | ast::ExprKind::Repeat(_, _) => { if !*has_errors { - cx.span_err(expr.span, "cannot concatenate doubly nested array"); + cx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false }); } *has_errors = true; None @@ -99,10 +84,7 @@ fn handle_array_element( Ok(ast::LitKind::Byte(val)) => Some(val), Ok(ast::LitKind::ByteStr(..)) => { if !*has_errors { - cx.struct_span_err(expr.span, "cannot concatenate doubly nested array") - .note("byte strings are treated as arrays of bytes") - .help("try flattening the array") - .emit(); + cx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: true }); } *has_errors = true; None @@ -117,10 +99,7 @@ fn handle_array_element( }, ast::ExprKind::IncludedBytes(..) => { if !*has_errors { - cx.struct_span_err(expr.span, "cannot concatenate doubly nested array") - .note("byte strings are treated as arrays of bytes") - .help("try flattening the array") - .emit(); + cx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false }); } *has_errors = true; None @@ -167,7 +146,7 @@ pub fn expand_concat_bytes( } } } else { - cx.span_err(count.value.span, "repeat count is not a positive number"); + cx.emit_err(errors::ConcatBytesBadRepeat {span: count.value.span }); } } &ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) { @@ -196,9 +175,7 @@ pub fn expand_concat_bytes( } } if !missing_literals.is_empty() { - let mut err = cx.struct_span_err(missing_literals, "expected a byte literal"); - err.note("only byte literals (like `b\"foo\"`, `b's'`, and `[3, 4, 5]`) can be passed to `concat_bytes!()`"); - err.emit(); + cx.emit_err(errors::ConcatBytesMissingLiteral { spans: missing_literals }); return base::MacEager::expr(DummyResult::raw_expr(sp, true)); } else if has_errors { return base::MacEager::expr(DummyResult::raw_expr(sp, true)); diff --git a/compiler/rustc_builtin_macros/src/concat_idents.rs b/compiler/rustc_builtin_macros/src/concat_idents.rs index 297c604e0..8c737f043 100644 --- a/compiler/rustc_builtin_macros/src/concat_idents.rs +++ b/compiler/rustc_builtin_macros/src/concat_idents.rs @@ -6,13 +6,15 @@ use rustc_expand::base::{self, *}; use rustc_span::symbol::{Ident, Symbol}; use rustc_span::Span; +use crate::errors; + pub fn expand_concat_idents<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, tts: TokenStream, ) -> Box { if tts.is_empty() { - cx.span_err(sp, "concat_idents! takes 1 or more arguments"); + cx.emit_err(errors::ConcatIdentsMissingArgs { span: sp }); return DummyResult::any(sp); } @@ -22,7 +24,7 @@ pub fn expand_concat_idents<'cx>( match e { TokenTree::Token(Token { kind: token::Comma, .. }, _) => {} _ => { - cx.span_err(sp, "concat_idents! expecting comma"); + cx.emit_err(errors::ConcatIdentsMissingComma { span: sp }); return DummyResult::any(sp); } } @@ -34,7 +36,7 @@ pub fn expand_concat_idents<'cx>( } } - cx.span_err(sp, "concat_idents! requires ident args"); + cx.emit_err(errors::ConcatIdentsIdentArgs { span: sp }); return DummyResult::any(sp); } } diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index 2a8dc0284..fe4483104 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -1,8 +1,8 @@ use crate::cfg_eval::cfg_eval; +use crate::errors; use rustc_ast as ast; use rustc_ast::{GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, StmtKind}; -use rustc_errors::{struct_span_err, Applicability}; use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier}; use rustc_feature::AttributeTemplate; use rustc_parse::validate_attr; @@ -116,49 +116,33 @@ fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool { let bad_target = !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..))); if bad_target { - struct_span_err!( - sess, - span, - E0774, - "`derive` may only be applied to `struct`s, `enum`s and `union`s", - ) - .span_label(span, "not applicable here") - .span_label(item.span(), "not a `struct`, `enum` or `union`") - .emit(); + sess.emit_err(errors::BadDeriveTarget { span, item: item.span() }); } bad_target } fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) { - let help_msg = match lit.kind { + let help = match lit.kind { ast::LitKind::Str(_, ast::StrStyle::Cooked) if rustc_lexer::is_ident(lit.symbol.as_str()) => { - format!("try using `#[derive({})]`", lit.symbol) + errors::BadDeriveLitHelp::StrLit { sym: lit.symbol } } - _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(), + _ => errors::BadDeriveLitHelp::Other, }; - struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",) - .span_label(lit.span, "not a trait") - .help(&help_msg) - .emit(); + sess.emit_err(errors::BadDeriveLit { span: lit.span, help }); } fn report_path_args(sess: &Session, meta: &ast::MetaItem) { - let report_error = |title, action| { - let span = meta.span.with_lo(meta.path.span.hi()); - sess.struct_span_err(span, title) - .span_suggestion(span, action, "", Applicability::MachineApplicable) - .emit(); - }; + let span = meta.span.with_lo(meta.path.span.hi()); + match meta.kind { MetaItemKind::Word => {} - MetaItemKind::List(..) => report_error( - "traits in `#[derive(...)]` don't accept arguments", - "remove the arguments", - ), + MetaItemKind::List(..) => { + sess.emit_err(errors::DerivePathArgsList { span }); + } MetaItemKind::NameValue(..) => { - report_error("traits in `#[derive(...)]` don't accept values", "remove the value") + sess.emit_err(errors::DerivePathArgsValue { span }); } } } diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs index 4d753a2ed..33fe98b40 100644 --- a/compiler/rustc_builtin_macros/src/deriving/default.rs +++ b/compiler/rustc_builtin_macros/src/deriving/default.rs @@ -1,8 +1,8 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; +use crate::errors; use rustc_ast as ast; -use rustc_ast::{walk_list, EnumDef, VariantData}; -use rustc_errors::Applicability; +use rustc_ast::{attr, walk_list, EnumDef, VariantData}; use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt}; use rustc_span::symbol::Ident; use rustc_span::symbol::{kw, sym}; @@ -106,7 +106,7 @@ fn extract_default_variant<'a>( let default_variants: SmallVec<[_; 1]> = enum_def .variants .iter() - .filter(|variant| cx.sess.contains_name(&variant.attrs, kw::Default)) + .filter(|variant| attr::contains_name(&variant.attrs, kw::Default)) .collect(); let variant = match default_variants.as_slice() { @@ -116,69 +116,52 @@ fn extract_default_variant<'a>( .variants .iter() .filter(|variant| matches!(variant.data, VariantData::Unit(..))) - .filter(|variant| !cx.sess.contains_name(&variant.attrs, sym::non_exhaustive)); + .filter(|variant| !attr::contains_name(&variant.attrs, sym::non_exhaustive)); - let mut diag = cx.struct_span_err(trait_span, "no default declared"); - diag.help("make a unit variant default by placing `#[default]` above it"); - for variant in possible_defaults { - // Suggest making each unit variant default. - diag.tool_only_span_suggestion( - variant.span, - &format!("make `{}` default", variant.ident), - format!("#[default] {}", variant.ident), - Applicability::MaybeIncorrect, - ); - } - diag.emit(); + let suggs = possible_defaults + .map(|v| errors::NoDefaultVariantSugg { span: v.span, ident: v.ident }) + .collect(); + cx.emit_err(errors::NoDefaultVariant { span: trait_span, suggs }); return Err(()); } [first, rest @ ..] => { - let mut diag = cx.struct_span_err(trait_span, "multiple declared defaults"); - diag.span_label(first.span, "first default"); - diag.span_labels(rest.iter().map(|variant| variant.span), "additional default"); - diag.note("only one variant can be default"); - for variant in &default_variants { - // Suggest making each variant already tagged default. - let suggestion = default_variants - .iter() - .filter_map(|v| { - if v.span == variant.span { - None - } else { - Some((cx.sess.find_by_name(&v.attrs, kw::Default)?.span, String::new())) - } - }) - .collect(); - - diag.tool_only_multipart_suggestion( - &format!("make `{}` default", variant.ident), - suggestion, - Applicability::MaybeIncorrect, - ); - } - diag.emit(); - + let suggs = default_variants + .iter() + .map(|variant| { + let spans = default_variants + .iter() + .filter_map(|v| { + if v.span == variant.span { + None + } else { + Some(attr::find_by_name(&v.attrs, kw::Default)?.span) + } + }) + .collect(); + errors::MultipleDefaultsSugg { spans, ident: variant.ident } + }) + .collect(); + cx.emit_err(errors::MultipleDefaults { + span: trait_span, + first: first.span, + additional: rest.iter().map(|v| v.span).collect(), + suggs, + }); return Err(()); } }; if !matches!(variant.data, VariantData::Unit(..)) { - cx.struct_span_err( - variant.ident.span, - "the `#[default]` attribute may only be used on unit enum variants", - ) - .help("consider a manual implementation of `Default`") - .emit(); - + cx.emit_err(errors::NonUnitDefault { span: variant.ident.span }); return Err(()); } - if let Some(non_exhaustive_attr) = cx.sess.find_by_name(&variant.attrs, sym::non_exhaustive) { - cx.struct_span_err(variant.ident.span, "default variant must be exhaustive") - .span_label(non_exhaustive_attr.span, "declared `#[non_exhaustive]` here") - .help("consider a manual implementation of `Default`") - .emit(); + if let Some(non_exhaustive_attr) = attr::find_by_name(&variant.attrs, sym::non_exhaustive) { + cx.emit_err(errors::NonExhaustiveDefault { + span: variant.ident.span, + non_exhaustive: non_exhaustive_attr.span, + }); return Err(()); } @@ -191,7 +174,7 @@ fn validate_default_attribute( default_variant: &rustc_ast::Variant, ) -> Result<(), ()> { let attrs: SmallVec<[_; 1]> = - cx.sess.filter_by_name(&default_variant.attrs, kw::Default).collect(); + attr::filter_by_name(&default_variant.attrs, kw::Default).collect(); let attr = match attrs.as_slice() { [attr] => attr, @@ -199,35 +182,23 @@ fn validate_default_attribute( "this method must only be called with a variant that has a `#[default]` attribute", ), [first, rest @ ..] => { - let suggestion_text = - if rest.len() == 1 { "try removing this" } else { "try removing these" }; - - cx.struct_span_err(default_variant.ident.span, "multiple `#[default]` attributes") - .note("only one `#[default]` attribute is needed") - .span_label(first.span, "`#[default]` used here") - .span_label(rest[0].span, "`#[default]` used again here") - .span_help(rest.iter().map(|attr| attr.span).collect::>(), suggestion_text) - // This would otherwise display the empty replacement, hence the otherwise - // repetitive `.span_help` call above. - .tool_only_multipart_suggestion( - suggestion_text, - rest.iter().map(|attr| (attr.span, String::new())).collect(), - Applicability::MachineApplicable, - ) - .emit(); + let sugg = errors::MultipleDefaultAttrsSugg { + spans: rest.iter().map(|attr| attr.span).collect(), + }; + cx.emit_err(errors::MultipleDefaultAttrs { + span: default_variant.ident.span, + first: first.span, + first_rest: rest[0].span, + rest: rest.iter().map(|attr| attr.span).collect::>().into(), + only_one: rest.len() == 1, + sugg, + }); return Err(()); } }; if !attr.is_word() { - cx.struct_span_err(attr.span, "`#[default]` attribute does not accept a value") - .span_suggestion_hidden( - attr.span, - "try using `#[default]`", - "#[default]", - Applicability::MaybeIncorrect, - ) - .emit(); + cx.emit_err(errors::DefaultHasArg { span: attr.span }); return Err(()); } @@ -241,12 +212,7 @@ struct DetectNonVariantDefaultAttr<'a, 'b> { impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> { fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) { if attr.has_name(kw::Default) { - self.cx - .struct_span_err( - attr.span, - "the `#[default]` attribute may only be used on unit enum variants", - ) - .emit(); + self.cx.emit_err(errors::NonUnitDefault { span: attr.span }); } rustc_ast::visit::walk_attribute(self, attr); diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 1f819beeb..e5a003315 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -162,7 +162,7 @@ pub use StaticFields::*; pub use SubstructureFields::*; -use crate::deriving; +use crate::{deriving, errors}; use rustc_ast::ptr::P; use rustc_ast::{ self as ast, BindingAnnotation, ByRef, EnumDef, Expr, GenericArg, GenericParamKind, Generics, @@ -415,7 +415,7 @@ fn find_type_parameters( } fn visit_mac_call(&mut self, mac: &ast::MacCall) { - self.cx.span_err(mac.span(), "`derive` cannot be used on items with type macros"); + self.cx.emit_err(errors::DeriveMacroCall { span: mac.span() }); } } @@ -488,7 +488,7 @@ impl<'a> TraitDef<'a> { is_packed, ) } else { - cx.span_err(mitem.span, "this trait cannot be derived for unions"); + cx.emit_err(errors::DeriveUnion { span: mitem.span }); return; } } @@ -1052,6 +1052,7 @@ impl<'a> MethodDef<'a> { /// ::core::hash::Hash::hash(&{ self.y }, state) /// } /// } + /// ``` fn expand_struct_method_body<'b>( &self, cx: &mut ExtCtxt<'_>, diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs index f011cb754..58c972738 100644 --- a/compiler/rustc_builtin_macros/src/env.rs +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -11,6 +11,8 @@ use rustc_span::Span; use std::env; use thin_vec::thin_vec; +use crate::errors; + pub fn expand_option_env<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, @@ -54,7 +56,7 @@ pub fn expand_env<'cx>( ) -> Box { let mut exprs = match get_exprs_from_tts(cx, tts) { Some(exprs) if exprs.is_empty() || exprs.len() > 2 => { - cx.span_err(sp, "env! takes 1 or 2 arguments"); + cx.emit_err(errors::EnvTakesArgs { span: sp }); return DummyResult::any(sp); } None => return DummyResult::any(sp), @@ -78,18 +80,12 @@ pub fn expand_env<'cx>( cx.sess.parse_sess.env_depinfo.borrow_mut().insert((var, value)); let e = match value { None => { - let (msg, help) = match custom_msg { - None => ( - format!("environment variable `{var}` not defined at compile time"), - Some(help_for_missing_env_var(var.as_str())), - ), - Some(s) => (s.to_string(), None), - }; - let mut diag = cx.struct_span_err(sp, &msg); - if let Some(help) = help { - diag.help(help); - } - diag.emit(); + cx.emit_err(errors::EnvNotDefined { + span: sp, + msg: custom_msg, + var, + help: custom_msg.is_none().then(|| help_for_missing_env_var(var.as_str())), + }); return DummyResult::any(sp); } Some(value) => cx.expr_str(sp, value), @@ -97,15 +93,13 @@ pub fn expand_env<'cx>( MacEager::expr(e) } -fn help_for_missing_env_var(var: &str) -> String { +fn help_for_missing_env_var(var: &str) -> errors::EnvNotDefinedHelp { if var.starts_with("CARGO_") || var.starts_with("DEP_") || matches!(var, "OUT_DIR" | "OPT_LEVEL" | "PROFILE" | "HOST" | "TARGET") { - format!( - "Cargo sets build script variables at run time. Use `std::env::var(\"{var}\")` instead" - ) + errors::EnvNotDefinedHelp::CargoVar } else { - format!("Use `std::env::var(\"{var}\")` to read the variable at run time") + errors::EnvNotDefinedHelp::Other } } diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs new file mode 100644 index 000000000..630f9b87b --- /dev/null +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -0,0 +1,553 @@ +use rustc_errors::{ + AddToDiagnostic, EmissionGuarantee, IntoDiagnostic, MultiSpan, SingleLabelManySpans, +}; +use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_span::{symbol::Ident, Span, Symbol}; + +#[derive(Diagnostic)] +#[diag(builtin_macros_requires_cfg_pattern)] +pub(crate) struct RequiresCfgPattern { + #[primary_span] + #[label] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_expected_one_cfg_pattern)] +pub(crate) struct OneCfgPattern { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_alloc_error_must_be_fn)] +pub(crate) struct AllocErrorMustBeFn { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_assert_requires_boolean)] +pub(crate) struct AssertRequiresBoolean { + #[primary_span] + #[label] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_assert_requires_expression)] +pub(crate) struct AssertRequiresExpression { + #[primary_span] + pub(crate) span: Span, + #[suggestion(code = "", applicability = "maybe-incorrect")] + pub(crate) token: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_assert_missing_comma)] +pub(crate) struct AssertMissingComma { + #[primary_span] + pub(crate) span: Span, + #[suggestion(code = ", ", applicability = "maybe-incorrect", style = "short")] + pub(crate) comma: Span, +} + +#[derive(Diagnostic)] +pub(crate) enum CfgAccessibleInvalid { + #[diag(builtin_macros_cfg_accessible_unspecified_path)] + UnspecifiedPath(#[primary_span] Span), + #[diag(builtin_macros_cfg_accessible_multiple_paths)] + MultiplePaths(#[primary_span] Span), + #[diag(builtin_macros_cfg_accessible_literal_path)] + LiteralPath(#[primary_span] Span), + #[diag(builtin_macros_cfg_accessible_has_args)] + HasArguments(#[primary_span] Span), +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_cfg_accessible_indeterminate)] +pub(crate) struct CfgAccessibleIndeterminate { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_missing_literal)] +#[note] +pub(crate) struct ConcatMissingLiteral { + #[primary_span] + pub(crate) spans: Vec, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytestr)] +pub(crate) struct ConcatBytestr { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_invalid)] +pub(crate) struct ConcatBytesInvalid { + #[primary_span] + pub(crate) span: Span, + pub(crate) lit_kind: &'static str, + #[subdiagnostic] + pub(crate) sugg: Option, +} + +#[derive(Subdiagnostic)] +pub(crate) enum ConcatBytesInvalidSuggestion { + #[suggestion( + builtin_macros_byte_char, + code = "b{snippet}", + applicability = "machine-applicable" + )] + CharLit { + #[primary_span] + span: Span, + snippet: String, + }, + #[suggestion( + builtin_macros_byte_str, + code = "b{snippet}", + applicability = "machine-applicable" + )] + StrLit { + #[primary_span] + span: Span, + snippet: String, + }, + #[suggestion( + builtin_macros_number_array, + code = "[{snippet}]", + applicability = "machine-applicable" + )] + IntLit { + #[primary_span] + span: Span, + snippet: String, + }, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_oob)] +pub(crate) struct ConcatBytesOob { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_non_u8)] +pub(crate) struct ConcatBytesNonU8 { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_missing_literal)] +#[note] +pub(crate) struct ConcatBytesMissingLiteral { + #[primary_span] + pub(crate) spans: Vec, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_array)] +pub(crate) struct ConcatBytesArray { + #[primary_span] + pub(crate) span: Span, + #[note] + #[help] + pub(crate) bytestr: bool, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_bad_repeat)] +pub(crate) struct ConcatBytesBadRepeat { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_idents_missing_args)] +pub(crate) struct ConcatIdentsMissingArgs { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_idents_missing_comma)] +pub(crate) struct ConcatIdentsMissingComma { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_idents_ident_args)] +pub(crate) struct ConcatIdentsIdentArgs { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_bad_derive_target, code = "E0774")] +pub(crate) struct BadDeriveTarget { + #[primary_span] + #[label] + pub(crate) span: Span, + #[label(builtin_macros_label2)] + pub(crate) item: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_unexpected_lit, code = "E0777")] +pub(crate) struct BadDeriveLit { + #[primary_span] + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub help: BadDeriveLitHelp, +} + +#[derive(Subdiagnostic)] +pub(crate) enum BadDeriveLitHelp { + #[help(builtin_macros_str_lit)] + StrLit { sym: Symbol }, + #[help(builtin_macros_other)] + Other, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_path_args_list)] +pub(crate) struct DerivePathArgsList { + #[suggestion(code = "", applicability = "machine-applicable")] + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_path_args_value)] +pub(crate) struct DerivePathArgsValue { + #[suggestion(code = "", applicability = "machine-applicable")] + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_no_default_variant)] +#[help] +pub(crate) struct NoDefaultVariant { + #[primary_span] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) suggs: Vec, +} + +#[derive(Subdiagnostic)] +#[suggestion( + builtin_macros_suggestion, + code = "#[default] {ident}", + applicability = "maybe-incorrect", + style = "tool-only" +)] +pub(crate) struct NoDefaultVariantSugg { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_multiple_defaults)] +#[note] +pub(crate) struct MultipleDefaults { + #[primary_span] + pub(crate) span: Span, + #[label] + pub(crate) first: Span, + #[label(builtin_macros_additional)] + pub additional: Vec, + #[subdiagnostic] + pub suggs: Vec, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + builtin_macros_suggestion, + applicability = "maybe-incorrect", + style = "tool-only" +)] +pub(crate) struct MultipleDefaultsSugg { + #[suggestion_part(code = "")] + pub(crate) spans: Vec, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_non_unit_default)] +#[help] +pub(crate) struct NonUnitDefault { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_non_exhaustive_default)] +#[help] +pub(crate) struct NonExhaustiveDefault { + #[primary_span] + pub(crate) span: Span, + #[label] + pub(crate) non_exhaustive: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_multiple_default_attrs)] +#[note] +pub(crate) struct MultipleDefaultAttrs { + #[primary_span] + pub(crate) span: Span, + #[label] + pub(crate) first: Span, + #[label(builtin_macros_label_again)] + pub(crate) first_rest: Span, + #[help] + pub(crate) rest: MultiSpan, + pub(crate) only_one: bool, + #[subdiagnostic] + pub(crate) sugg: MultipleDefaultAttrsSugg, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + builtin_macros_help, + applicability = "machine-applicable", + style = "tool-only" +)] +pub(crate) struct MultipleDefaultAttrsSugg { + #[suggestion_part(code = "")] + pub(crate) spans: Vec, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_default_arg)] +pub(crate) struct DefaultHasArg { + #[primary_span] + #[suggestion(code = "#[default]", style = "hidden", applicability = "maybe-incorrect")] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_macro_call)] +pub(crate) struct DeriveMacroCall { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_cannot_derive_union)] +pub(crate) struct DeriveUnion { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_env_takes_args)] +pub(crate) struct EnvTakesArgs { + #[primary_span] + pub(crate) span: Span, +} + +//#[derive(Diagnostic)] +//#[diag(builtin_macros_env_not_defined)] +pub(crate) struct EnvNotDefined { + pub(crate) span: Span, + pub(crate) msg: Option, + pub(crate) var: Symbol, + pub(crate) help: Option, +} + +// Hand-written implementation to support custom user messages +impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for EnvNotDefined { + #[track_caller] + fn into_diagnostic( + self, + handler: &'a rustc_errors::Handler, + ) -> rustc_errors::DiagnosticBuilder<'a, G> { + let mut diag = if let Some(msg) = self.msg { + handler.struct_diagnostic(msg.as_str()) + } else { + handler.struct_diagnostic(crate::fluent_generated::builtin_macros_env_not_defined) + }; + diag.set_arg("var", self.var); + diag.set_span(self.span); + if let Some(help) = self.help { + diag.subdiagnostic(help); + } + diag + } +} + +#[derive(Subdiagnostic)] +pub(crate) enum EnvNotDefinedHelp { + #[help(builtin_macros_cargo)] + CargoVar, + #[help(builtin_macros_other)] + Other, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_requires_string)] +pub(crate) struct FormatRequiresString { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_duplicate_arg)] +pub(crate) struct FormatDuplicateArg { + #[primary_span] + pub(crate) span: Span, + #[label(builtin_macros_label1)] + pub(crate) prev: Span, + #[label(builtin_macros_label2)] + pub(crate) duplicate: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_positional_after_named)] +pub(crate) struct PositionalAfterNamed { + #[primary_span] + #[label] + pub(crate) span: Span, + #[label(builtin_macros_named_args)] + pub(crate) args: Vec, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_string_invalid)] +pub(crate) struct InvalidFormatString { + #[primary_span] + #[label] + pub(crate) span: Span, + pub(crate) desc: String, + pub(crate) label1: String, + #[subdiagnostic] + pub(crate) note_: Option, + #[subdiagnostic] + pub(crate) label_: Option, + #[subdiagnostic] + pub(crate) sugg_: Option, +} + +#[derive(Subdiagnostic)] +#[note(builtin_macros_note)] +pub(crate) struct InvalidFormatStringNote { + pub(crate) note: String, +} + +#[derive(Subdiagnostic)] +#[label(builtin_macros_second_label)] +pub(crate) struct InvalidFormatStringLabel { + #[primary_span] + pub(crate) span: Span, + pub(crate) label: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + builtin_macros_sugg, + style = "verbose", + applicability = "machine-applicable" +)] +pub(crate) struct InvalidFormatStringSuggestion { + #[suggestion_part(code = "{len}")] + pub(crate) captured: Span, + pub(crate) len: String, + #[suggestion_part(code = ", {arg}")] + pub(crate) span: Span, + pub(crate) arg: String, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_no_arg_named)] +#[note] +#[note(builtin_macros_note2)] +pub(crate) struct FormatNoArgNamed { + #[primary_span] + pub(crate) span: Span, + pub(crate) name: Symbol, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_unknown_trait)] +#[note] +pub(crate) struct FormatUnknownTrait<'a> { + #[primary_span] + pub(crate) span: Span, + pub(crate) ty: &'a str, + #[subdiagnostic] + pub(crate) suggs: Vec, +} + +#[derive(Subdiagnostic)] +#[suggestion( + builtin_macros_suggestion, + code = "{fmt}", + style = "tool-only", + applicability = "maybe-incorrect" +)] +pub struct FormatUnknownTraitSugg { + #[primary_span] + pub span: Span, + pub fmt: &'static str, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_unused_arg)] +pub(crate) struct FormatUnusedArg { + #[primary_span] + #[label(builtin_macros_format_unused_arg)] + pub(crate) span: Span, + pub(crate) named: bool, +} + +// Allow the singular form to be a subdiagnostic of the multiple-unused +// form of diagnostic. +impl AddToDiagnostic for FormatUnusedArg { + fn add_to_diagnostic_with(self, diag: &mut rustc_errors::Diagnostic, f: F) + where + F: Fn( + &mut rustc_errors::Diagnostic, + rustc_errors::SubdiagnosticMessage, + ) -> rustc_errors::SubdiagnosticMessage, + { + diag.set_arg("named", self.named); + let msg = f(diag, crate::fluent_generated::builtin_macros_format_unused_arg.into()); + diag.span_label(self.span, msg); + } +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_unused_args)] +pub(crate) struct FormatUnusedArgs { + #[primary_span] + pub(crate) unused: Vec, + #[label] + pub(crate) fmt: Span, + #[subdiagnostic] + pub(crate) unused_labels: Vec, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_pos_mismatch)] +pub(crate) struct FormatPositionalMismatch { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) n: usize, + pub(crate) desc: String, + #[subdiagnostic] + pub(crate) highlight: SingleLabelManySpans, +} diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index e93a23394..f0fc61d7c 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -7,7 +7,7 @@ use rustc_ast::{ FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, }; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{pluralize, Applicability, MultiSpan, PResult}; +use rustc_errors::{Applicability, MultiSpan, PResult, SingleLabelManySpans}; use rustc_expand::base::{self, *}; use rustc_parse_format as parse; use rustc_span::symbol::{Ident, Symbol}; @@ -36,6 +36,23 @@ enum PositionUsedAs { } use PositionUsedAs::*; +use crate::errors; + +struct MacroInput { + fmtstr: P, + args: FormatArguments, + /// Whether the first argument was a string literal or a result from eager macro expansion. + /// If it's not a string literal, we disallow implicit argument capturing. + /// + /// This does not correspond to whether we can treat spans to the literal normally, as the whole + /// invocation might be the result of another macro expansion, in which case this flag may still be true. + /// + /// See [RFC 2795] for more information. + /// + /// [RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html#macro-hygiene + is_direct_literal: bool, +} + /// Parses the arguments from the given list of tokens, returning the diagnostic /// if there's a parse error so we can continue parsing other format! /// expressions. @@ -45,39 +62,31 @@ use PositionUsedAs::*; /// ```text /// Ok((fmtstr, parsed arguments)) /// ``` -fn parse_args<'a>( - ecx: &mut ExtCtxt<'a>, - sp: Span, - tts: TokenStream, -) -> PResult<'a, (P, FormatArguments)> { +fn parse_args<'a>(ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> { let mut args = FormatArguments::new(); let mut p = ecx.new_parser_from_tts(tts); if p.token == token::Eof { - return Err(ecx.struct_span_err(sp, "requires at least a format string argument")); + return Err(ecx.create_err(errors::FormatRequiresString { span: sp })); } let first_token = &p.token; - let fmtstr = match first_token.kind { - token::TokenKind::Literal(token::Lit { - kind: token::LitKind::Str | token::LitKind::StrRaw(_), - .. - }) => { - // If the first token is a string literal, then a format expression - // is constructed from it. - // - // This allows us to properly handle cases when the first comma - // after the format string is mistakenly replaced with any operator, - // which cause the expression parser to eat too much tokens. - p.parse_literal_maybe_minus()? - } - _ => { - // Otherwise, we fall back to the expression parser. - p.parse_expr()? - } + + let fmtstr = if let token::Literal(lit) = first_token.kind && matches!(lit.kind, token::Str | token::StrRaw(_)) { + // This allows us to properly handle cases when the first comma + // after the format string is mistakenly replaced with any operator, + // which cause the expression parser to eat too much tokens. + p.parse_literal_maybe_minus()? + } else { + // Otherwise, we fall back to the expression parser. + p.parse_expr()? }; + // Only allow implicit captures to be used when the argument is a direct literal + // instead of a macro expanding to one. + let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_)); + let mut first = true; while p.token != token::Eof { @@ -114,13 +123,12 @@ fn parse_args<'a>( p.expect(&token::Eq)?; let expr = p.parse_expr()?; if let Some((_, prev)) = args.by_name(ident.name) { - ecx.struct_span_err( - ident.span, - &format!("duplicate argument named `{}`", ident), - ) - .span_label(prev.kind.ident().unwrap().span, "previously here") - .span_label(ident.span, "duplicate argument") - .emit(); + ecx.emit_err(errors::FormatDuplicateArg { + span: ident.span, + prev: prev.kind.ident().unwrap().span, + duplicate: ident.span, + ident, + }); continue; } args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); @@ -128,36 +136,39 @@ fn parse_args<'a>( _ => { let expr = p.parse_expr()?; if !args.named_args().is_empty() { - let mut err = ecx.struct_span_err( - expr.span, - "positional arguments cannot follow named arguments", - ); - err.span_label( - expr.span, - "positional arguments must be before named arguments", - ); - for arg in args.named_args() { - if let Some(name) = arg.kind.ident() { - err.span_label(name.span.to(arg.expr.span), "named argument"); - } - } - err.emit(); + ecx.emit_err(errors::PositionalAfterNamed { + span: expr.span, + args: args + .named_args() + .iter() + .filter_map(|a| { + if let Some(ident) = a.kind.ident() { + Some((a, ident)) + } else { + None + } + }) + .map(|(arg, n)| n.span.to(arg.expr.span)) + .collect(), + }); } args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); } } } - Ok((fmtstr, args)) + Ok(MacroInput { fmtstr, args, is_direct_literal }) } -pub fn make_format_args( +fn make_format_args( ecx: &mut ExtCtxt<'_>, - efmt: P, - mut args: FormatArguments, + input: MacroInput, append_newline: bool, ) -> Result { let msg = "format argument must be a string literal"; - let unexpanded_fmt_span = efmt.span; + let unexpanded_fmt_span = input.fmtstr.span; + + let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input; + let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) { Ok(mut fmt) if append_newline => { fmt.0 = Symbol::intern(&format!("{}\n", fmt.0)); @@ -208,11 +219,11 @@ pub fn make_format_args( } } - let is_literal = parser.is_literal; + let is_source_literal = parser.is_source_literal; if !parser.errors.is_empty() { let err = parser.errors.remove(0); - let sp = if is_literal { + let sp = if is_source_literal { fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)) } else { // The format string could be another macro invocation, e.g.: @@ -225,13 +236,19 @@ pub fn make_format_args( // argument span here. fmt_span }; - let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", err.description)); - e.span_label(sp, err.label + " in format string"); + let mut e = errors::InvalidFormatString { + span: sp, + note_: None, + label_: None, + sugg_: None, + desc: err.description, + label1: err.label, + }; if let Some(note) = err.note { - e.note(¬e); + e.note_ = Some(errors::InvalidFormatStringNote { note }); } - if let Some((label, span)) = err.secondary_label && is_literal { - e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label); + if let Some((label, span)) = err.secondary_label && is_source_literal { + e.label_ = Some(errors::InvalidFormatStringLabel { span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label } ); } if err.should_be_replaced_with_positional_argument { let captured_arg_span = @@ -241,22 +258,20 @@ pub fn make_format_args( Some(arg) => arg.expr.span, None => fmt_span, }; - e.multipart_suggestion_verbose( - "consider using a positional formatting argument instead", - vec![ - (captured_arg_span, args.unnamed_args().len().to_string()), - (span.shrink_to_hi(), format!(", {}", arg)), - ], - Applicability::MachineApplicable, - ); + e.sugg_ = Some(errors::InvalidFormatStringSuggestion { + captured: captured_arg_span, + len: args.unnamed_args().len().to_string(), + span: span.shrink_to_hi(), + arg, + }); } } - e.emit(); + ecx.emit_err(e); return Err(()); } let to_span = |inner_span: rustc_parse_format::InnerSpan| { - is_literal.then(|| { + is_source_literal.then(|| { fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }) }) }; @@ -304,15 +319,12 @@ pub fn make_format_args( // Name not found in `args`, so we add it as an implicitly captured argument. let span = span.unwrap_or(fmt_span); let ident = Ident::new(name, span); - let expr = if is_literal { + let expr = if is_direct_literal { ecx.expr_ident(span, ident) } else { // For the moment capturing variables from format strings expanded from macros is // disabled (see RFC #2795) - ecx.struct_span_err(span, &format!("there is no argument named `{name}`")) - .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?")) - .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro") - .emit(); + ecx.emit_err(errors::FormatNoArgNamed { span, name }); DummyResult::raw_expr(span, true) }; Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) @@ -466,12 +478,8 @@ pub fn make_format_args( .enumerate() .filter(|&(_, used)| !used) .map(|(i, _)| { - let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind { - "named argument never used" - } else { - "argument never used" - }; - (args.explicit_args()[i].expr.span, msg) + let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_)); + (args.explicit_args()[i].expr.span, named) }) .collect::>(); @@ -522,22 +530,8 @@ fn invalid_placeholder_type_error( fmt_span: Span, ) { let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end))); - let mut err = - ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty)); - err.note( - "the only appropriate formatting traits are:\n\ - - ``, which uses the `Display` trait\n\ - - `?`, which uses the `Debug` trait\n\ - - `e`, which uses the `LowerExp` trait\n\ - - `E`, which uses the `UpperExp` trait\n\ - - `o`, which uses the `Octal` trait\n\ - - `p`, which uses the `Pointer` trait\n\ - - `b`, which uses the `Binary` trait\n\ - - `x`, which uses the `LowerHex` trait\n\ - - `X`, which uses the `UpperHex` trait", - ); - if let Some(sp) = sp { - for (fmt, name) in &[ + let suggs = if let Some(sp) = sp { + [ ("", "Display"), ("?", "Debug"), ("e", "LowerExp"), @@ -547,40 +541,38 @@ fn invalid_placeholder_type_error( ("b", "Binary"), ("x", "LowerHex"), ("X", "UpperHex"), - ] { - err.tool_only_span_suggestion( - sp, - &format!("use the `{}` trait", name), - *fmt, - Applicability::MaybeIncorrect, - ); - } - } - err.emit(); + ] + .into_iter() + .map(|(fmt, trait_name)| errors::FormatUnknownTraitSugg { span: sp, fmt, trait_name }) + .collect() + } else { + vec![] + }; + ecx.emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs }); } fn report_missing_placeholders( ecx: &mut ExtCtxt<'_>, - unused: Vec<(Span, &str)>, + unused: Vec<(Span, bool)>, detect_foreign_fmt: bool, str_style: Option, fmt_str: &str, fmt_span: Span, ) { - let mut diag = if let &[(span, msg)] = &unused[..] { - let mut diag = ecx.struct_span_err(span, msg); - diag.span_label(span, msg); - diag + let mut diag = if let &[(span, named)] = &unused[..] { + //let mut diag = ecx.struct_span_err(span, msg); + //diag.span_label(span, msg); + //diag + ecx.create_err(errors::FormatUnusedArg { span, named }) } else { - let mut diag = ecx.struct_span_err( - unused.iter().map(|&(sp, _)| sp).collect::>(), - "multiple unused formatting arguments", - ); - diag.span_label(fmt_span, "multiple missing formatting specifiers"); - for &(span, msg) in &unused { - diag.span_label(span, msg); - } - diag + let unused_labels = + unused.iter().map(|&(span, named)| errors::FormatUnusedArg { span, named }).collect(); + let unused_spans = unused.iter().map(|&(span, _)| span).collect(); + ecx.create_err(errors::FormatUnusedArgs { + fmt: fmt_span, + unused: unused_spans, + unused_labels, + }) }; // Used to ensure we only report translations for *one* kind of foreign format. @@ -759,18 +751,16 @@ fn report_invalid_references( } else { MultiSpan::from_spans(spans) }; - e = ecx.struct_span_err( + e = ecx.create_err(errors::FormatPositionalMismatch { span, - &format!( - "{} positional argument{} in format string, but {}", - num_placeholders, - pluralize!(num_placeholders), - num_args_desc, - ), - ); - for arg in args.explicit_args() { - e.span_label(arg.expr.span, ""); - } + n: num_placeholders, + desc: num_args_desc, + highlight: SingleLabelManySpans { + spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(), + label: "", + kind: rustc_errors::LabelKind::Label, + }, + }); // Point out `{:.*}` placeholders: those take an extra argument. let mut has_precision_star = false; for piece in template { @@ -814,7 +804,7 @@ fn report_invalid_references( // for `println!("{7:7$}", 1);` indexes.sort(); indexes.dedup(); - let span: MultiSpan = if !parser.is_literal || parser.arg_places.is_empty() { + let span: MultiSpan = if !parser.is_source_literal || parser.arg_places.is_empty() { MultiSpan::from_span(fmt_span) } else { MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect()) @@ -855,8 +845,8 @@ fn expand_format_args_impl<'cx>( ) -> Box { sp = ecx.with_def_site_ctxt(sp); match parse_args(ecx, sp, tts) { - Ok((efmt, args)) => { - if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) { + Ok(input) => { + if let Ok(format_args) = make_format_args(ecx, input, nl) { MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args)))) } else { MacEager::expr(DummyResult::raw_expr(sp, true)) diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs index 41b51bae7..866cc5adb 100644 --- a/compiler/rustc_builtin_macros/src/global_allocator.rs +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -25,12 +25,12 @@ pub fn expand( // FIXME - if we get deref patterns, use them to reduce duplication here let (item, is_stmt, ty_span) = if let Annotatable::Item(item) = &item - && let ItemKind::Static(ty, ..) = &item.kind + && let ItemKind::Static(box ast::StaticItem { ty, ..}) = &item.kind { (item, false, ecx.with_def_site_ctxt(ty.span)) } else if let Annotatable::Stmt(stmt) = &item && let StmtKind::Item(item) = &stmt.kind - && let ItemKind::Static(ty, ..) = &item.kind + && let ItemKind::Static(box ast::StaticItem { ty, ..}) = &item.kind { (item, true, ecx.with_def_site_ctxt(ty.span)) } else { diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 8afb6e560..37fbd03a6 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -7,9 +7,9 @@ #![feature(box_patterns)] #![feature(decl_macro)] #![feature(if_let_guard)] -#![feature(is_some_and)] #![feature(is_sorted)] #![feature(let_chains)] +#![feature(lint_reasons)] #![feature(proc_macro_internals)] #![feature(proc_macro_quote)] #![recursion_limit = "256"] @@ -40,6 +40,7 @@ mod derive; mod deriving; mod edition_panic; mod env; +mod errors; mod format; mod format_foreign; mod global_allocator; @@ -56,7 +57,7 @@ pub mod proc_macro_harness; pub mod standard_library_imports; pub mod test_harness; -fluent_messages! { "../locales/en-US.ftl" } +fluent_messages! { "../messages.ftl" } pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { let mut register = |name, kind| resolver.register_builtin_macro(name, kind); diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index bc513607d..378d5f39f 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -1,6 +1,6 @@ use rustc_ast::ptr::P; use rustc_ast::visit::{self, Visitor}; -use rustc_ast::{self as ast, NodeId}; +use rustc_ast::{self as ast, attr, NodeId}; use rustc_ast_pretty::pprust; use rustc_expand::base::{parse_macro_name_and_helper_attrs, ExtCtxt, ResolverExpand}; use rustc_expand::expand::{AstFragment, ExpansionConfig}; @@ -34,7 +34,6 @@ enum ProcMacro { } struct CollectProcMacros<'a> { - sess: &'a Session, macros: Vec, in_root: bool, handler: &'a rustc_errors::Handler, @@ -44,19 +43,18 @@ struct CollectProcMacros<'a> { } pub fn inject( + krate: &mut ast::Crate, sess: &Session, resolver: &mut dyn ResolverExpand, - mut krate: ast::Crate, is_proc_macro_crate: bool, has_proc_macro_decls: bool, is_test_crate: bool, handler: &rustc_errors::Handler, -) -> ast::Crate { +) { let ecfg = ExpansionConfig::default("proc_macro".to_string()); let mut cx = ExtCtxt::new(sess, ecfg, resolver, None); let mut collect = CollectProcMacros { - sess, macros: Vec::new(), in_root: true, handler, @@ -66,22 +64,20 @@ pub fn inject( }; if has_proc_macro_decls || is_proc_macro_crate { - visit::walk_crate(&mut collect, &krate); + visit::walk_crate(&mut collect, krate); } let macros = collect.macros; if !is_proc_macro_crate { - return krate; + return; } if is_test_crate { - return krate; + return; } let decls = mk_decls(&mut cx, ¯os); krate.items.push(decls); - - krate } impl<'a> CollectProcMacros<'a> { @@ -160,7 +156,7 @@ impl<'a> CollectProcMacros<'a> { impl<'a> Visitor<'a> for CollectProcMacros<'a> { fn visit_item(&mut self, item: &'a ast::Item) { if let ast::ItemKind::MacroDef(..) = item.kind { - if self.is_proc_macro_crate && self.sess.contains_name(&item.attrs, sym::macro_export) { + if self.is_proc_macro_crate && attr::contains_name(&item.attrs, sym::macro_export) { let msg = "cannot export macro_rules! macros from a `proc-macro` crate type currently"; self.handler.span_err(self.source_map.guess_head_span(item.span), msg); @@ -176,7 +172,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> { let mut found_attr: Option<&'a ast::Attribute> = None; for attr in &item.attrs { - if self.sess.is_proc_macro_attr(&attr) { + if attr.is_proc_macro_attr() { if let Some(prev_attr) = found_attr { let prev_item = prev_attr.get_normal_item(); let item = attr.get_normal_item(); diff --git a/compiler/rustc_builtin_macros/src/standard_library_imports.rs b/compiler/rustc_builtin_macros/src/standard_library_imports.rs index e67c0dba6..6493c6f13 100644 --- a/compiler/rustc_builtin_macros/src/standard_library_imports.rs +++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs @@ -1,4 +1,4 @@ -use rustc_ast as ast; +use rustc_ast::{self as ast, attr}; use rustc_expand::base::{ExtCtxt, ResolverExpand}; use rustc_expand::expand::ExpansionConfig; use rustc_session::Session; @@ -9,17 +9,19 @@ use rustc_span::DUMMY_SP; use thin_vec::thin_vec; pub fn inject( - mut krate: ast::Crate, + krate: &mut ast::Crate, + pre_configured_attrs: &[ast::Attribute], resolver: &mut dyn ResolverExpand, sess: &Session, -) -> ast::Crate { +) -> usize { + let orig_num_items = krate.items.len(); let edition = sess.parse_sess.edition; // the first name in this list is the crate name of the crate with the prelude - let names: &[Symbol] = if sess.contains_name(&krate.attrs, sym::no_core) { - return krate; - } else if sess.contains_name(&krate.attrs, sym::no_std) { - if sess.contains_name(&krate.attrs, sym::compiler_builtins) { + let names: &[Symbol] = if attr::contains_name(pre_configured_attrs, sym::no_core) { + return 0; + } else if attr::contains_name(pre_configured_attrs, sym::no_std) { + if attr::contains_name(pre_configured_attrs, sym::compiler_builtins) { &[sym::core] } else { &[sym::core, sym::compiler_builtins] @@ -88,6 +90,5 @@ pub fn inject( ); krate.items.insert(0, use_item); - - krate + krate.items.len() - orig_num_items } diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index e02c7e6c0..79d8be248 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -1,14 +1,13 @@ /// The expansion from a test function to the appropriate test struct for libtest /// Ideally, this code would be in libtest but for efficiency and error messages it lives here. use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; -use rustc_ast as ast; use rustc_ast::ptr::P; +use rustc_ast::{self as ast, attr}; use rustc_ast_pretty::pprust; use rustc_errors::Applicability; use rustc_expand::base::*; -use rustc_session::Session; use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_span::Span; +use rustc_span::{FileNameDisplayPreference, Span}; use std::iter; use thin_vec::{thin_vec, ThinVec}; @@ -33,7 +32,23 @@ pub fn expand_test_case( } let sp = ecx.with_def_site_ctxt(attr_sp); - let mut item = anno_item.expect_item(); + let (mut item, is_stmt) = match anno_item { + Annotatable::Item(item) => (item, false), + Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => if let ast::StmtKind::Item(i) = stmt.into_inner().kind { + (i, true) + } else { + unreachable!() + }, + _ => { + ecx.struct_span_err( + anno_item.span(), + "`#[test_case]` attribute is only allowed on items", + ) + .emit(); + + return vec![]; + } + }; item = item.map(|mut item| { let test_path_symbol = Symbol::intern(&item_path( // skip the name of the root module @@ -50,7 +65,13 @@ pub fn expand_test_case( item }); - return vec![Annotatable::Item(item)]; + let ret = if is_stmt { + Annotatable::Stmt(P(ecx.stmt_item(item.span, item))) + } else { + Annotatable::Item(item) + }; + + vec![ret] } pub fn expand_test( @@ -97,34 +118,22 @@ pub fn expand_test_or_bench( } } other => { - cx.struct_span_err( - other.span(), - "`#[test]` attribute is only allowed on non associated functions", - ) - .emit(); + not_testable_error(cx, attr_sp, None); return vec![other]; } }; - // Note: non-associated fn items are already handled by `expand_test_or_bench` let ast::ItemKind::Fn(fn_) = &item.kind else { - let diag = &cx.sess.parse_sess.span_diagnostic; - let msg = "the `#[test]` attribute may only be used on a non-associated function"; - let mut err = match item.kind { - // These were a warning before #92959 and need to continue being that to avoid breaking - // stable user code (#94508). - ast::ItemKind::MacCall(_) => diag.struct_span_warn(attr_sp, msg), - // `.forget_guarantee()` needed to get these two arms to match types. Because of how - // locally close the `.emit()` call is I'm comfortable with it, but if it can be - // reworked in the future to not need it, it'd be nice. - _ => diag.struct_span_err(attr_sp, msg).forget_guarantee(), + not_testable_error(cx, attr_sp, Some(&item)); + return if is_stmt { + vec![Annotatable::Stmt(P(ast::Stmt { + id: ast::DUMMY_NODE_ID, + span: item.span, + kind: ast::StmtKind::Item(item), + }))] + } else { + vec![Annotatable::Item(item)] }; - err.span_label(attr_sp, "the `#[test]` macro causes a function to be run on a test and has no effect on non-functions") - .span_label(item.span, format!("expected a non-associated function, found {} {}", item.kind.article(), item.kind.descr())) - .span_suggestion(attr_sp, "replace with conditional compilation to make the item only exist when tests are being run", "#[cfg(test)]", Applicability::MaybeIncorrect) - .emit(); - - return vec![Annotatable::Item(item)]; }; // has_*_signature will report any errors in the type so compilation @@ -231,25 +240,29 @@ pub fn expand_test_or_bench( &item.ident, )); - let mut test_const = cx.item( - sp, - Ident::new(item.ident.name, sp), - thin_vec![ - // #[cfg(test)] - cx.attr_nested_word(sym::cfg, sym::test, attr_sp), - // #[rustc_test_marker = "test_case_sort_key"] - cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp), - ], - // const $ident: test::TestDescAndFn = - ast::ItemKind::Const( - ast::Defaultness::Final, - cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), - // test::TestDescAndFn { - Some( - cx.expr_struct( - sp, - test_path("TestDescAndFn"), - thin_vec![ + let location_info = get_location_info(cx, &item); + + let mut test_const = + cx.item( + sp, + Ident::new(item.ident.name, sp), + thin_vec![ + // #[cfg(test)] + cx.attr_nested_word(sym::cfg, sym::test, attr_sp), + // #[rustc_test_marker = "test_case_sort_key"] + cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp), + ], + // const $ident: test::TestDescAndFn = + ast::ItemKind::Const( + ast::ConstItem { + defaultness: ast::Defaultness::Final, + ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), + // test::TestDescAndFn { + expr: Some( + cx.expr_struct( + sp, + test_path("TestDescAndFn"), + thin_vec![ // desc: test::TestDesc { field( "desc", @@ -267,19 +280,26 @@ pub fn expand_test_or_bench( ), ), // ignore: true | false - field( - "ignore", - cx.expr_bool(sp, should_ignore(&cx.sess, &item)), - ), + field("ignore", cx.expr_bool(sp, should_ignore(&item)),), // ignore_message: Some("...") | None field( "ignore_message", - if let Some(msg) = should_ignore_message(cx, &item) { + if let Some(msg) = should_ignore_message(&item) { cx.expr_some(sp, cx.expr_str(sp, msg)) } else { cx.expr_none(sp) }, ), + // source_file: + field("source_file", cx.expr_str(sp, location_info.0)), + // start_line: start line of the test fn identifier. + field("start_line", cx.expr_usize(sp, location_info.1)), + // start_col: start column of the test fn identifier. + field("start_col", cx.expr_usize(sp, location_info.2)), + // end_line: end line of the test fn identifier. + field("end_line", cx.expr_usize(sp, location_info.3)), + // end_col: end column of the test fn identifier. + field("end_col", cx.expr_usize(sp, location_info.4)), // compile_fail: true | false field("compile_fail", cx.expr_bool(sp, false)), // no_run: true | false @@ -329,10 +349,12 @@ pub fn expand_test_or_bench( // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...) field("testfn", test_fn), // } ], - ), // } + ), // } + ), + } + .into(), ), - ), - ); + ); test_const = test_const.map(|mut tc| { tc.vis.kind = ast::VisibilityKind::Public; tc @@ -364,6 +386,49 @@ pub fn expand_test_or_bench( } } +fn not_testable_error(cx: &ExtCtxt<'_>, attr_sp: Span, item: Option<&ast::Item>) { + let diag = &cx.sess.parse_sess.span_diagnostic; + let msg = "the `#[test]` attribute may only be used on a non-associated function"; + let mut err = match item.map(|i| &i.kind) { + // These were a warning before #92959 and need to continue being that to avoid breaking + // stable user code (#94508). + Some(ast::ItemKind::MacCall(_)) => diag.struct_span_warn(attr_sp, msg), + // `.forget_guarantee()` needed to get these two arms to match types. Because of how + // locally close the `.emit()` call is I'm comfortable with it, but if it can be + // reworked in the future to not need it, it'd be nice. + _ => diag.struct_span_err(attr_sp, msg).forget_guarantee(), + }; + if let Some(item) = item { + err.span_label( + item.span, + format!( + "expected a non-associated function, found {} {}", + item.kind.article(), + item.kind.descr() + ), + ); + } + err.span_label(attr_sp, "the `#[test]` macro causes a function to be run as a test and has no effect on non-functions") + .span_suggestion(attr_sp, + "replace with conditional compilation to make the item only exist when tests are being run", + "#[cfg(test)]", + Applicability::MaybeIncorrect) + .emit(); +} + +fn get_location_info(cx: &ExtCtxt<'_>, item: &ast::Item) -> (Symbol, usize, usize, usize, usize) { + let span = item.ident.span; + let (source_file, lo_line, lo_col, hi_line, hi_col) = + cx.sess.source_map().span_to_location_info(span); + + let file_name = match source_file { + Some(sf) => sf.name.display(FileNameDisplayPreference::Remapped).to_string(), + None => "no-location".to_string(), + }; + + (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col) +} + fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String { mod_path .iter() @@ -378,12 +443,12 @@ enum ShouldPanic { Yes(Option), } -fn should_ignore(sess: &Session, i: &ast::Item) -> bool { - sess.contains_name(&i.attrs, sym::ignore) +fn should_ignore(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, sym::ignore) } -fn should_ignore_message(cx: &ExtCtxt<'_>, i: &ast::Item) -> Option { - match cx.sess.find_by_name(&i.attrs, sym::ignore) { +fn should_ignore_message(i: &ast::Item) -> Option { + match attr::find_by_name(&i.attrs, sym::ignore) { Some(attr) => { match attr.meta_item_list() { // Handle #[ignore(bar = "foo")] @@ -397,7 +462,7 @@ fn should_ignore_message(cx: &ExtCtxt<'_>, i: &ast::Item) -> Option { } fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic { - match cx.sess.find_by_name(&i.attrs, sym::should_panic) { + match attr::find_by_name(&i.attrs, sym::should_panic) { Some(attr) => { let sd = &cx.sess.parse_sess.span_diagnostic; @@ -463,7 +528,7 @@ fn test_type(cx: &ExtCtxt<'_>) -> TestType { } fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool { - let has_should_panic_attr = cx.sess.contains_name(&i.attrs, sym::should_panic); + let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic); let sd = &cx.sess.parse_sess.span_diagnostic; match &i.kind { ast::ItemKind::Fn(box ast::Fn { sig, generics, .. }) => { diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index d8e3db9e8..80f497333 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -37,7 +37,7 @@ struct TestCtxt<'a> { /// Traverse the crate, collecting all the test functions, eliding any /// existing main functions, and synthesizing a main test harness -pub fn inject(sess: &Session, resolver: &mut dyn ResolverExpand, krate: &mut ast::Crate) { +pub fn inject(krate: &mut ast::Crate, sess: &Session, resolver: &mut dyn ResolverExpand) { let span_diagnostic = sess.diagnostic(); let panic_strategy = sess.panic_strategy(); let platform_panic_strategy = sess.target.panic_strategy; @@ -47,13 +47,13 @@ pub fn inject(sess: &Session, resolver: &mut dyn ResolverExpand, krate: &mut ast // unconditional, so that the attribute is still marked as used in // non-test builds. let reexport_test_harness_main = - sess.first_attr_value_str_by_name(&krate.attrs, sym::reexport_test_harness_main); + attr::first_attr_value_str_by_name(&krate.attrs, sym::reexport_test_harness_main); // Do this here so that the test_runner crate attribute gets marked as used // even in non-test builds - let test_runner = get_test_runner(sess, span_diagnostic, &krate); + let test_runner = get_test_runner(span_diagnostic, &krate); - if sess.opts.test { + if sess.is_test_crate() { let panic_strategy = match (panic_strategy, sess.opts.unstable_opts.panic_abort_tests) { (PanicStrategy::Abort, true) => PanicStrategy::Abort, (PanicStrategy::Abort, false) => { @@ -123,7 +123,7 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> { fn flat_map_item(&mut self, i: P) -> SmallVec<[P; 1]> { let mut item = i.into_inner(); - if let Some(name) = get_test_name(&self.cx.ext_cx.sess, &item) { + if let Some(name) = get_test_name(&item) { debug!("this is a test item"); let test = Test { span: item.span, ident: item.ident, name }; @@ -145,12 +145,12 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> { // Beware, this is duplicated in librustc_passes/entry.rs (with // `rustc_hir::Item`), so make sure to keep them in sync. -fn entry_point_type(sess: &Session, item: &ast::Item, depth: usize) -> EntryPointType { +fn entry_point_type(item: &ast::Item, depth: usize) -> EntryPointType { match item.kind { ast::ItemKind::Fn(..) => { - if sess.contains_name(&item.attrs, sym::start) { + if attr::contains_name(&item.attrs, sym::start) { EntryPointType::Start - } else if sess.contains_name(&item.attrs, sym::rustc_main) { + } else if attr::contains_name(&item.attrs, sym::rustc_main) { EntryPointType::RustcMainAttr } else if item.ident.name == sym::main { if depth == 0 { @@ -184,7 +184,7 @@ impl<'a> MutVisitor for EntryPointCleaner<'a> { // Remove any #[rustc_main] or #[start] from the AST so it doesn't // clash with the one we're going to add, but mark it as // #[allow(dead_code)] to avoid printing warnings. - let item = match entry_point_type(self.sess, &item, self.depth) { + let item = match entry_point_type(&item, self.depth) { EntryPointType::MainNamed | EntryPointType::RustcMainAttr | EntryPointType::Start => { item.map(|ast::Item { id, ident, attrs, kind, vis, span, tokens }| { let allow_dead_code = attr::mk_attr_nested_word( @@ -373,16 +373,12 @@ fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P { ) } -fn get_test_name(sess: &Session, i: &ast::Item) -> Option { - sess.first_attr_value_str_by_name(&i.attrs, sym::rustc_test_marker) +fn get_test_name(i: &ast::Item) -> Option { + attr::first_attr_value_str_by_name(&i.attrs, sym::rustc_test_marker) } -fn get_test_runner( - sess: &Session, - sd: &rustc_errors::Handler, - krate: &ast::Crate, -) -> Option { - let test_attr = sess.find_by_name(&krate.attrs, sym::test_runner)?; +fn get_test_runner(sd: &rustc_errors::Handler, krate: &ast::Crate) -> Option { + let test_attr = attr::find_by_name(&krate.attrs, sym::test_runner)?; let meta_list = test_attr.meta_item_list()?; let span = test_attr.span; match &*meta_list { diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index 83812631c..9463a1418 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -1,4 +1,4 @@ -use rustc_ast::{AttrStyle, Attribute, MetaItem}; +use rustc_ast::{attr, AttrStyle, Attribute, MetaItem}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_feature::AttributeTemplate; use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES; @@ -36,7 +36,7 @@ pub fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: _ => None, }; if let Some(attrs) = attrs { - if let Some(attr) = ecx.sess.find_by_name(attrs, name) { + if let Some(attr) = attr::find_by_name(attrs, name) { ecx.parse_sess().buffer_lint( DUPLICATE_MACRO_ATTRIBUTES, attr.span, -- cgit v1.2.3