summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_builtin_macros
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_builtin_macros
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_builtin_macros')
-rw-r--r--compiler/rustc_builtin_macros/Cargo.toml26
-rw-r--r--compiler/rustc_builtin_macros/src/asm.rs875
-rw-r--r--compiler/rustc_builtin_macros/src/assert.rs178
-rw-r--r--compiler/rustc_builtin_macros/src/assert/context.rs453
-rw-r--r--compiler/rustc_builtin_macros/src/cfg.rs69
-rw-r--r--compiler/rustc_builtin_macros/src/cfg_accessible.rs61
-rw-r--r--compiler/rustc_builtin_macros/src/cfg_eval.rs269
-rw-r--r--compiler/rustc_builtin_macros/src/cmdline_attrs.rs35
-rw-r--r--compiler/rustc_builtin_macros/src/compile_error.rs19
-rw-r--r--compiler/rustc_builtin_macros/src/concat.rs64
-rw-r--r--compiler/rustc_builtin_macros/src/concat_bytes.rs189
-rw-r--r--compiler/rustc_builtin_macros/src/concat_idents.rs70
-rw-r--r--compiler/rustc_builtin_macros/src/derive.rs158
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/bounds.rs28
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/clone.rs212
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs90
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs79
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs110
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs88
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/debug.rs181
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/decodable.rs224
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/default.rs267
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/encodable.rs295
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/mod.rs1655
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/ty.rs203
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/hash.rs80
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/mod.rs208
-rw-r--r--compiler/rustc_builtin_macros/src/edition_panic.rs86
-rw-r--r--compiler/rustc_builtin_macros/src/env.rs91
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs1573
-rw-r--r--compiler/rustc_builtin_macros/src/format_foreign.rs829
-rw-r--r--compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs145
-rw-r--r--compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs56
-rw-r--r--compiler/rustc_builtin_macros/src/global_allocator.rs194
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs119
-rw-r--r--compiler/rustc_builtin_macros/src/log_syntax.rs14
-rw-r--r--compiler/rustc_builtin_macros/src/proc_macro_harness.rs393
-rw-r--r--compiler/rustc_builtin_macros/src/source_util.rs225
-rw-r--r--compiler/rustc_builtin_macros/src/standard_library_imports.rs92
-rw-r--r--compiler/rustc_builtin_macros/src/test.rs529
-rw-r--r--compiler/rustc_builtin_macros/src/test_harness.rs390
-rw-r--r--compiler/rustc_builtin_macros/src/trace_macros.rs29
-rw-r--r--compiler/rustc_builtin_macros/src/util.rs43
43 files changed, 10994 insertions, 0 deletions
diff --git a/compiler/rustc_builtin_macros/Cargo.toml b/compiler/rustc_builtin_macros/Cargo.toml
new file mode 100644
index 000000000..8d8e9d9b5
--- /dev/null
+++ b/compiler/rustc_builtin_macros/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "rustc_builtin_macros"
+version = "0.0.0"
+edition = "2021"
+
+[lib]
+doctest = false
+
+[dependencies]
+rustc_parse_format = { path = "../rustc_parse_format" }
+tracing = "0.1"
+rustc_ast_pretty = { path = "../rustc_ast_pretty" }
+rustc_attr = { path = "../rustc_attr" }
+rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_errors = { path = "../rustc_errors" }
+rustc_feature = { path = "../rustc_feature" }
+rustc_lexer = { path = "../rustc_lexer" }
+rustc_lint_defs = { path = "../rustc_lint_defs" }
+rustc_macros = { path = "../rustc_macros" }
+rustc_parse = { path = "../rustc_parse" }
+rustc_target = { path = "../rustc_target" }
+rustc_session = { path = "../rustc_session" }
+smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
+rustc_ast = { path = "../rustc_ast" }
+rustc_expand = { path = "../rustc_expand" }
+rustc_span = { path = "../rustc_span" }
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs
new file mode 100644
index 000000000..1a0ea8f41
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/asm.rs
@@ -0,0 +1,875 @@
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::token::{self, Delimiter};
+use rustc_ast::tokenstream::TokenStream;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::{Applicability, PResult};
+use rustc_expand::base::{self, *};
+use rustc_parse::parser::Parser;
+use rustc_parse_format as parse;
+use rustc_session::lint;
+use rustc_session::parse::ParseSess;
+use rustc_span::symbol::Ident;
+use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_span::{InnerSpan, Span};
+use rustc_target::asm::InlineAsmArch;
+use smallvec::smallvec;
+
+pub struct AsmArgs {
+ pub templates: Vec<P<ast::Expr>>,
+ pub operands: Vec<(ast::InlineAsmOperand, Span)>,
+ named_args: FxHashMap<Symbol, usize>,
+ reg_args: FxHashSet<usize>,
+ pub clobber_abis: Vec<(Symbol, Span)>,
+ options: ast::InlineAsmOptions,
+ pub options_spans: Vec<Span>,
+}
+
+fn parse_args<'a>(
+ ecx: &mut ExtCtxt<'a>,
+ sp: Span,
+ tts: TokenStream,
+ is_global_asm: bool,
+) -> PResult<'a, AsmArgs> {
+ let mut p = ecx.new_parser_from_tts(tts);
+ let sess = &ecx.sess.parse_sess;
+ parse_asm_args(&mut p, sess, sp, is_global_asm)
+}
+
+// Primarily public for rustfmt consumption.
+// Internal consumers should continue to leverage `expand_asm`/`expand__global_asm`
+pub fn parse_asm_args<'a>(
+ p: &mut Parser<'a>,
+ sess: &'a ParseSess,
+ sp: Span,
+ is_global_asm: bool,
+) -> PResult<'a, AsmArgs> {
+ let diag = &sess.span_diagnostic;
+
+ if p.token == token::Eof {
+ return Err(diag.struct_span_err(sp, "requires at least a template string argument"));
+ }
+
+ let first_template = p.parse_expr()?;
+ let mut args = AsmArgs {
+ templates: vec![first_template],
+ operands: vec![],
+ named_args: FxHashMap::default(),
+ reg_args: FxHashSet::default(),
+ clobber_abis: Vec::new(),
+ options: ast::InlineAsmOptions::empty(),
+ options_spans: vec![],
+ };
+
+ let mut allow_templates = true;
+ while p.token != token::Eof {
+ if !p.eat(&token::Comma) {
+ if allow_templates {
+ // After a template string, we always expect *only* a comma...
+ let mut err = diag.struct_span_err(p.token.span, "expected token: `,`");
+ err.span_label(p.token.span, "expected `,`");
+ p.maybe_annotate_with_ascription(&mut err, false);
+ return Err(err);
+ } else {
+ // ...after that delegate to `expect` to also include the other expected tokens.
+ return Err(p.expect(&token::Comma).err().unwrap());
+ }
+ }
+ if p.token == token::Eof {
+ break;
+ } // accept trailing commas
+
+ // Parse clobber_abi
+ if p.eat_keyword(sym::clobber_abi) {
+ parse_clobber_abi(p, &mut args)?;
+ allow_templates = false;
+ continue;
+ }
+
+ // Parse options
+ if p.eat_keyword(sym::options) {
+ parse_options(p, &mut args, is_global_asm)?;
+ allow_templates = false;
+ continue;
+ }
+
+ let span_start = p.token.span;
+
+ // Parse operand names
+ let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) {
+ let (ident, _) = p.token.ident().unwrap();
+ p.bump();
+ p.expect(&token::Eq)?;
+ allow_templates = false;
+ Some(ident.name)
+ } else {
+ None
+ };
+
+ let mut explicit_reg = false;
+ let op = if !is_global_asm && p.eat_keyword(kw::In) {
+ let reg = parse_reg(p, &mut explicit_reg)?;
+ if p.eat_keyword(kw::Underscore) {
+ let err = diag.struct_span_err(p.token.span, "_ cannot be used for input operands");
+ return Err(err);
+ }
+ let expr = p.parse_expr()?;
+ ast::InlineAsmOperand::In { reg, expr }
+ } else if !is_global_asm && p.eat_keyword(sym::out) {
+ let reg = parse_reg(p, &mut explicit_reg)?;
+ let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+ ast::InlineAsmOperand::Out { reg, expr, late: false }
+ } else if !is_global_asm && p.eat_keyword(sym::lateout) {
+ let reg = parse_reg(p, &mut explicit_reg)?;
+ let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+ ast::InlineAsmOperand::Out { reg, expr, late: true }
+ } else if !is_global_asm && p.eat_keyword(sym::inout) {
+ let reg = parse_reg(p, &mut explicit_reg)?;
+ if p.eat_keyword(kw::Underscore) {
+ let err = diag.struct_span_err(p.token.span, "_ cannot be used for input operands");
+ return Err(err);
+ }
+ let expr = p.parse_expr()?;
+ if p.eat(&token::FatArrow) {
+ let out_expr =
+ if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+ ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: false }
+ } else {
+ ast::InlineAsmOperand::InOut { reg, expr, late: false }
+ }
+ } else if !is_global_asm && p.eat_keyword(sym::inlateout) {
+ let reg = parse_reg(p, &mut explicit_reg)?;
+ if p.eat_keyword(kw::Underscore) {
+ let err = diag.struct_span_err(p.token.span, "_ cannot be used for input operands");
+ return Err(err);
+ }
+ let expr = p.parse_expr()?;
+ if p.eat(&token::FatArrow) {
+ let out_expr =
+ if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+ ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: true }
+ } else {
+ ast::InlineAsmOperand::InOut { reg, expr, late: true }
+ }
+ } else if p.eat_keyword(kw::Const) {
+ let anon_const = p.parse_anon_const_expr()?;
+ ast::InlineAsmOperand::Const { anon_const }
+ } else if p.eat_keyword(sym::sym) {
+ let expr = p.parse_expr()?;
+ let ast::ExprKind::Path(qself, path) = &expr.kind else {
+ let err = diag
+ .struct_span_err(expr.span, "expected a path for argument to `sym`");
+ return Err(err);
+ };
+ let sym = ast::InlineAsmSym {
+ id: ast::DUMMY_NODE_ID,
+ qself: qself.clone(),
+ path: path.clone(),
+ };
+ ast::InlineAsmOperand::Sym { sym }
+ } else if allow_templates {
+ let template = p.parse_expr()?;
+ // If it can't possibly expand to a string, provide diagnostics here to include other
+ // things it could have been.
+ match template.kind {
+ ast::ExprKind::Lit(ast::Lit { kind: ast::LitKind::Str(..), .. }) => {}
+ ast::ExprKind::MacCall(..) => {}
+ _ => {
+ let errstr = if is_global_asm {
+ "expected operand, options, or additional template string"
+ } else {
+ "expected operand, clobber_abi, options, or additional template string"
+ };
+ let mut err = diag.struct_span_err(template.span, errstr);
+ err.span_label(template.span, errstr);
+ return Err(err);
+ }
+ }
+ args.templates.push(template);
+ continue;
+ } else {
+ return p.unexpected();
+ };
+
+ allow_templates = false;
+ let span = span_start.to(p.prev_token.span);
+ let slot = args.operands.len();
+ args.operands.push((op, span));
+
+ // 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();
+ }
+ args.reg_args.insert(slot);
+ } else if let Some(name) = name {
+ if let Some(&prev) = args.named_args.get(&name) {
+ diag.struct_span_err(span, &format!("duplicate argument named `{}`", name))
+ .span_label(args.operands[prev].1, "previously here")
+ .span_label(span, "duplicate argument")
+ .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() {
+ let mut err = diag.struct_span_err(
+ span,
+ "positional arguments cannot follow named arguments \
+ or explicit register arguments",
+ );
+ err.span_label(span, "positional argument");
+ for pos in args.named_args.values() {
+ err.span_label(args.operands[*pos].1, "named argument");
+ }
+ for pos in &args.reg_args {
+ err.span_label(args.operands[*pos].1, "explicit register argument");
+ }
+ err.emit();
+ }
+ }
+ }
+
+ if args.options.contains(ast::InlineAsmOptions::NOMEM)
+ && args.options.contains(ast::InlineAsmOptions::READONLY)
+ {
+ let spans = args.options_spans.clone();
+ diag.struct_span_err(spans, "the `nomem` and `readonly` options are mutually exclusive")
+ .emit();
+ }
+ if args.options.contains(ast::InlineAsmOptions::PURE)
+ && args.options.contains(ast::InlineAsmOptions::NORETURN)
+ {
+ let spans = args.options_spans.clone();
+ diag.struct_span_err(spans, "the `pure` and `noreturn` options are mutually exclusive")
+ .emit();
+ }
+ if args.options.contains(ast::InlineAsmOptions::PURE)
+ && !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY)
+ {
+ let spans = args.options_spans.clone();
+ diag.struct_span_err(
+ spans,
+ "the `pure` option must be combined with either `nomem` or `readonly`",
+ )
+ .emit();
+ }
+
+ let mut have_real_output = false;
+ let mut outputs_sp = vec![];
+ let mut regclass_outputs = vec![];
+ for (op, op_sp) in &args.operands {
+ match op {
+ ast::InlineAsmOperand::Out { reg, expr, .. }
+ | ast::InlineAsmOperand::SplitInOut { reg, out_expr: expr, .. } => {
+ outputs_sp.push(*op_sp);
+ have_real_output |= expr.is_some();
+ if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
+ regclass_outputs.push(*op_sp);
+ }
+ }
+ ast::InlineAsmOperand::InOut { reg, .. } => {
+ outputs_sp.push(*op_sp);
+ have_real_output = true;
+ if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
+ regclass_outputs.push(*op_sp);
+ }
+ }
+ _ => {}
+ }
+ }
+ if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output {
+ diag.struct_span_err(
+ args.options_spans.clone(),
+ "asm with the `pure` option must have at least one output",
+ )
+ .emit();
+ }
+ if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() {
+ let err = diag
+ .struct_span_err(outputs_sp, "asm outputs are not allowed with the `noreturn` option");
+
+ // Bail out now since this is likely to confuse MIR
+ return Err(err);
+ }
+
+ if args.clobber_abis.len() > 0 {
+ if is_global_asm {
+ let err = diag.struct_span_err(
+ args.clobber_abis.iter().map(|(_, span)| *span).collect::<Vec<Span>>(),
+ "`clobber_abi` cannot be used with `global_asm!`",
+ );
+
+ // Bail out now since this is likely to confuse later stages
+ return Err(err);
+ }
+ if !regclass_outputs.is_empty() {
+ diag.struct_span_err(
+ regclass_outputs.clone(),
+ "asm with `clobber_abi` must specify explicit registers for outputs",
+ )
+ .span_labels(
+ args.clobber_abis.iter().map(|(_, span)| *span).collect::<Vec<Span>>(),
+ "clobber_abi",
+ )
+ .span_labels(regclass_outputs, "generic outputs")
+ .emit();
+ }
+ }
+
+ Ok(args)
+}
+
+/// Report a duplicate option error.
+///
+/// This function must be called immediately after the option token is parsed.
+/// Otherwise, the suggestion will be incorrect.
+fn err_duplicate_option<'a>(p: &mut Parser<'a>, symbol: Symbol, span: Span) {
+ let mut err = p
+ .sess
+ .span_diagnostic
+ .struct_span_err(span, &format!("the `{}` option was already provided", symbol));
+ err.span_label(span, "this option was already provided");
+
+ // Tool-only output
+ let mut full_span = span;
+ if p.token.kind == token::Comma {
+ full_span = full_span.to(p.token.span);
+ }
+ err.tool_only_span_suggestion(
+ full_span,
+ "remove this option",
+ "",
+ Applicability::MachineApplicable,
+ );
+
+ err.emit();
+}
+
+/// Try to set the provided option in the provided `AsmArgs`.
+/// If it is already set, report a duplicate option error.
+///
+/// This function must be called immediately after the option token is parsed.
+/// Otherwise, the error will not point to the correct spot.
+fn try_set_option<'a>(
+ p: &mut Parser<'a>,
+ args: &mut AsmArgs,
+ symbol: Symbol,
+ option: ast::InlineAsmOptions,
+) {
+ if !args.options.contains(option) {
+ args.options |= option;
+ } else {
+ err_duplicate_option(p, symbol, p.prev_token.span);
+ }
+}
+
+fn parse_options<'a>(
+ p: &mut Parser<'a>,
+ args: &mut AsmArgs,
+ is_global_asm: bool,
+) -> PResult<'a, ()> {
+ let span_start = p.prev_token.span;
+
+ p.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+
+ while !p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+ if !is_global_asm && p.eat_keyword(sym::pure) {
+ try_set_option(p, args, sym::pure, ast::InlineAsmOptions::PURE);
+ } else if !is_global_asm && p.eat_keyword(sym::nomem) {
+ try_set_option(p, args, sym::nomem, ast::InlineAsmOptions::NOMEM);
+ } else if !is_global_asm && p.eat_keyword(sym::readonly) {
+ try_set_option(p, args, sym::readonly, ast::InlineAsmOptions::READONLY);
+ } else if !is_global_asm && p.eat_keyword(sym::preserves_flags) {
+ try_set_option(p, args, sym::preserves_flags, ast::InlineAsmOptions::PRESERVES_FLAGS);
+ } else if !is_global_asm && p.eat_keyword(sym::noreturn) {
+ try_set_option(p, args, sym::noreturn, ast::InlineAsmOptions::NORETURN);
+ } else if !is_global_asm && p.eat_keyword(sym::nostack) {
+ try_set_option(p, args, sym::nostack, ast::InlineAsmOptions::NOSTACK);
+ } else if !is_global_asm && p.eat_keyword(sym::may_unwind) {
+ try_set_option(p, args, kw::Raw, ast::InlineAsmOptions::MAY_UNWIND);
+ } else if p.eat_keyword(sym::att_syntax) {
+ try_set_option(p, args, sym::att_syntax, ast::InlineAsmOptions::ATT_SYNTAX);
+ } else if p.eat_keyword(kw::Raw) {
+ try_set_option(p, args, kw::Raw, ast::InlineAsmOptions::RAW);
+ } else {
+ return p.unexpected();
+ }
+
+ // Allow trailing commas
+ if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+ break;
+ }
+ p.expect(&token::Comma)?;
+ }
+
+ let new_span = span_start.to(p.prev_token.span);
+ args.options_spans.push(new_span);
+
+ Ok(())
+}
+
+fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, ()> {
+ let span_start = p.prev_token.span;
+
+ p.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+
+ if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+ let err = p.sess.span_diagnostic.struct_span_err(
+ p.token.span,
+ "at least one abi must be provided as an argument to `clobber_abi`",
+ );
+ return Err(err);
+ }
+
+ let mut new_abis = Vec::new();
+ loop {
+ match p.parse_str_lit() {
+ Ok(str_lit) => {
+ new_abis.push((str_lit.symbol_unescaped, str_lit.span));
+ }
+ Err(opt_lit) => {
+ // If the non-string literal is a closing paren then it's the end of the list and is fine
+ if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+ break;
+ }
+ let span = opt_lit.map_or(p.token.span, |lit| lit.span);
+ let mut err =
+ p.sess.span_diagnostic.struct_span_err(span, "expected string literal");
+ err.span_label(span, "not a string literal");
+ return Err(err);
+ }
+ };
+
+ // Allow trailing commas
+ if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+ break;
+ }
+ p.expect(&token::Comma)?;
+ }
+
+ 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!(),
+ [(abi, _span)] => args.clobber_abis.push((*abi, full_span)),
+ [abis @ ..] => {
+ for (abi, span) in abis {
+ args.clobber_abis.push((*abi, *span));
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn parse_reg<'a>(
+ p: &mut Parser<'a>,
+ explicit_reg: &mut bool,
+) -> PResult<'a, ast::InlineAsmRegOrRegClass> {
+ p.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+ let result = match p.token.uninterpolate().kind {
+ token::Ident(name, false) => ast::InlineAsmRegOrRegClass::RegClass(name),
+ token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => {
+ *explicit_reg = true;
+ ast::InlineAsmRegOrRegClass::Reg(symbol)
+ }
+ _ => {
+ return Err(
+ p.struct_span_err(p.token.span, "expected register class or explicit register")
+ );
+ }
+ };
+ p.bump();
+ p.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+ Ok(result)
+}
+
+fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::InlineAsm> {
+ let mut template = vec![];
+ // Register operands are implicitly used since they are not allowed to be
+ // referenced in the template string.
+ let mut used = vec![false; args.operands.len()];
+ for pos in &args.reg_args {
+ used[*pos] = true;
+ }
+ let named_pos: FxHashMap<usize, Symbol> =
+ args.named_args.iter().map(|(&sym, &idx)| (idx, sym)).collect();
+ let mut line_spans = Vec::with_capacity(args.templates.len());
+ let mut curarg = 0;
+
+ let mut template_strs = Vec::with_capacity(args.templates.len());
+
+ for (i, template_expr) in args.templates.into_iter().enumerate() {
+ if i != 0 {
+ template.push(ast::InlineAsmTemplatePiece::String("\n".to_string()));
+ }
+
+ let msg = "asm template must be a string literal";
+ let template_sp = template_expr.span;
+ let (template_str, template_style, template_span) =
+ match expr_to_spanned_string(ecx, template_expr, msg) {
+ Ok(template_part) => template_part,
+ Err(err) => {
+ if let Some((mut err, _)) = err {
+ err.emit();
+ }
+ return None;
+ }
+ };
+
+ let str_style = match template_style {
+ ast::StrStyle::Cooked => None,
+ ast::StrStyle::Raw(raw) => Some(raw as usize),
+ };
+
+ let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok();
+ template_strs.push((
+ template_str,
+ template_snippet.as_ref().map(|s| Symbol::intern(s)),
+ template_sp,
+ ));
+ let template_str = template_str.as_str();
+
+ if let Some(InlineAsmArch::X86 | InlineAsmArch::X86_64) = ecx.sess.asm_arch {
+ let find_span = |needle: &str| -> Span {
+ if let Some(snippet) = &template_snippet {
+ if let Some(pos) = snippet.find(needle) {
+ let end = pos
+ + snippet[pos..]
+ .find(|c| matches!(c, '\n' | ';' | '\\' | '"'))
+ .unwrap_or(snippet[pos..].len() - 1);
+ let inner = InnerSpan::new(pos, end);
+ return template_sp.from_inner(inner);
+ }
+ }
+ template_sp
+ };
+
+ if template_str.contains(".intel_syntax") {
+ ecx.parse_sess().buffer_lint(
+ lint::builtin::BAD_ASM_STYLE,
+ find_span(".intel_syntax"),
+ ecx.current_expansion.lint_node_id,
+ "avoid using `.intel_syntax`, Intel syntax is the default",
+ );
+ }
+ if template_str.contains(".att_syntax") {
+ ecx.parse_sess().buffer_lint(
+ lint::builtin::BAD_ASM_STYLE,
+ find_span(".att_syntax"),
+ ecx.current_expansion.lint_node_id,
+ "avoid using `.att_syntax`, prefer using `options(att_syntax)` instead",
+ );
+ }
+ }
+
+ // Don't treat raw asm as a format string.
+ if args.options.contains(ast::InlineAsmOptions::RAW) {
+ template.push(ast::InlineAsmTemplatePiece::String(template_str.to_string()));
+ let template_num_lines = 1 + template_str.matches('\n').count();
+ line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines));
+ continue;
+ }
+
+ let mut parser = parse::Parser::new(
+ template_str,
+ str_style,
+ template_snippet,
+ false,
+ parse::ParseMode::InlineAsm,
+ );
+ parser.curarg = curarg;
+
+ let mut unverified_pieces = Vec::new();
+ while let Some(piece) = parser.next() {
+ if !parser.errors.is_empty() {
+ break;
+ } else {
+ unverified_pieces.push(piece);
+ }
+ }
+
+ if !parser.errors.is_empty() {
+ let err = parser.errors.remove(0);
+ let err_sp = template_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
+ let msg = &format!("invalid asm template string: {}", err.description);
+ let mut e = ecx.struct_span_err(err_sp, msg);
+ e.span_label(err_sp, err.label + " in asm template string");
+ if let Some(note) = err.note {
+ e.note(&note);
+ }
+ if let Some((label, span)) = err.secondary_label {
+ let err_sp = template_span.from_inner(InnerSpan::new(span.start, span.end));
+ e.span_label(err_sp, label);
+ }
+ e.emit();
+ return None;
+ }
+
+ curarg = parser.curarg;
+
+ let mut arg_spans = parser
+ .arg_places
+ .iter()
+ .map(|span| template_span.from_inner(InnerSpan::new(span.start, span.end)));
+ for piece in unverified_pieces {
+ match piece {
+ parse::Piece::String(s) => {
+ template.push(ast::InlineAsmTemplatePiece::String(s.to_string()))
+ }
+ parse::Piece::NextArgument(arg) => {
+ let span = arg_spans.next().unwrap_or(template_sp);
+
+ let operand_idx = match arg.position {
+ parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => {
+ if idx >= args.operands.len()
+ || named_pos.contains_key(&idx)
+ || args.reg_args.contains(&idx)
+ {
+ let msg = format!("invalid reference to argument at index {}", idx);
+ let mut err = ecx.struct_span_err(span, &msg);
+ err.span_label(span, "from here");
+
+ let positional_args = args.operands.len()
+ - args.named_args.len()
+ - args.reg_args.len();
+ let positional = if positional_args != args.operands.len() {
+ "positional "
+ } else {
+ ""
+ };
+ let msg = match positional_args {
+ 0 => format!("no {}arguments were given", positional),
+ 1 => format!("there is 1 {}argument", positional),
+ x => format!("there are {} {}arguments", x, positional),
+ };
+ err.note(&msg);
+
+ if named_pos.contains_key(&idx) {
+ err.span_label(args.operands[idx].1, "named argument");
+ err.span_note(
+ args.operands[idx].1,
+ "named arguments cannot be referenced by position",
+ );
+ } else if args.reg_args.contains(&idx) {
+ err.span_label(
+ args.operands[idx].1,
+ "explicit register argument",
+ );
+ err.span_note(
+ args.operands[idx].1,
+ "explicit register arguments cannot be used in the asm template",
+ );
+ }
+ err.emit();
+ None
+ } else {
+ Some(idx)
+ }
+ }
+ parse::ArgumentNamed(name) => {
+ match args.named_args.get(&Symbol::intern(name)) {
+ Some(&idx) => Some(idx),
+ None => {
+ let msg = format!("there is no argument named `{}`", name);
+ let span = arg.position_span;
+ ecx.struct_span_err(
+ template_span
+ .from_inner(InnerSpan::new(span.start, span.end)),
+ &msg,
+ )
+ .emit();
+ None
+ }
+ }
+ }
+ };
+
+ let mut chars = arg.format.ty.chars();
+ let mut modifier = chars.next();
+ if chars.next().is_some() {
+ let span = arg
+ .format
+ .ty_span
+ .map(|sp| template_sp.from_inner(InnerSpan::new(sp.start, sp.end)))
+ .unwrap_or(template_sp);
+ ecx.struct_span_err(
+ span,
+ "asm template modifier must be a single character",
+ )
+ .emit();
+ modifier = None;
+ }
+
+ if let Some(operand_idx) = operand_idx {
+ used[operand_idx] = true;
+ template.push(ast::InlineAsmTemplatePiece::Placeholder {
+ operand_idx,
+ modifier,
+ span,
+ });
+ }
+ }
+ }
+ }
+
+ if parser.line_spans.is_empty() {
+ let template_num_lines = 1 + template_str.matches('\n').count();
+ line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines));
+ } else {
+ line_spans.extend(
+ parser
+ .line_spans
+ .iter()
+ .map(|span| template_span.from_inner(InnerSpan::new(span.start, span.end))),
+ );
+ };
+ }
+
+ let mut unused_operands = vec![];
+ let mut help_str = String::new();
+ for (idx, used) in used.into_iter().enumerate() {
+ if !used {
+ let msg = if let Some(sym) = named_pos.get(&idx) {
+ help_str.push_str(&format!(" {{{}}}", sym));
+ "named argument never used"
+ } else {
+ help_str.push_str(&format!(" {{{}}}", idx));
+ "argument never used"
+ };
+ unused_operands.push((args.operands[idx].1, msg));
+ }
+ }
+ match unused_operands.len() {
+ 0 => {}
+ 1 => {
+ let (sp, msg) = unused_operands.into_iter().next().unwrap();
+ let mut err = ecx.struct_span_err(sp, msg);
+ err.span_label(sp, msg);
+ err.help(&format!(
+ "if this argument is intentionally unused, \
+ consider using it in an asm comment: `\"/*{} */\"`",
+ help_str
+ ));
+ err.emit();
+ }
+ _ => {
+ let mut err = ecx.struct_span_err(
+ unused_operands.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
+ "multiple unused asm arguments",
+ );
+ for (sp, msg) in unused_operands {
+ err.span_label(sp, msg);
+ }
+ err.help(&format!(
+ "if these arguments are intentionally unused, \
+ consider using them in an asm comment: `\"/*{} */\"`",
+ help_str
+ ));
+ err.emit();
+ }
+ }
+
+ Some(ast::InlineAsm {
+ template,
+ template_strs: template_strs.into_boxed_slice(),
+ operands: args.operands,
+ clobber_abis: args.clobber_abis,
+ options: args.options,
+ line_spans,
+ })
+}
+
+pub(super) fn expand_asm<'cx>(
+ ecx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ match parse_args(ecx, sp, tts, false) {
+ Ok(args) => {
+ let expr = if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
+ P(ast::Expr {
+ id: ast::DUMMY_NODE_ID,
+ kind: ast::ExprKind::InlineAsm(P(inline_asm)),
+ span: sp,
+ attrs: ast::AttrVec::new(),
+ tokens: None,
+ })
+ } else {
+ DummyResult::raw_expr(sp, true)
+ };
+ MacEager::expr(expr)
+ }
+ Err(mut err) => {
+ err.emit();
+ DummyResult::any(sp)
+ }
+ }
+}
+
+pub(super) fn expand_global_asm<'cx>(
+ ecx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ match parse_args(ecx, sp, tts, true) {
+ Ok(args) => {
+ if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
+ MacEager::items(smallvec![P(ast::Item {
+ ident: Ident::empty(),
+ attrs: Vec::new(),
+ id: ast::DUMMY_NODE_ID,
+ kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)),
+ vis: ast::Visibility {
+ span: sp.shrink_to_lo(),
+ kind: ast::VisibilityKind::Inherited,
+ tokens: None,
+ },
+ span: ecx.with_def_site_ctxt(sp),
+ tokens: None,
+ })])
+ } else {
+ DummyResult::any(sp)
+ }
+ }
+ Err(mut err) => {
+ err.emit();
+ DummyResult::any(sp)
+ }
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs
new file mode 100644
index 000000000..925c36edb
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/assert.rs
@@ -0,0 +1,178 @@
+mod context;
+
+use crate::edition_panic::use_panic_2021;
+use rustc_ast::ptr::P;
+use rustc_ast::token;
+use rustc_ast::tokenstream::{DelimSpan, TokenStream};
+use rustc_ast::{Expr, ExprKind, MacArgs, MacCall, MacDelimiter, Path, PathSegment, UnOp};
+use rustc_ast_pretty::pprust;
+use rustc_errors::{Applicability, PResult};
+use rustc_expand::base::{DummyResult, ExtCtxt, MacEager, MacResult};
+use rustc_parse::parser::Parser;
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+
+pub fn expand_assert<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ span: Span,
+ tts: TokenStream,
+) -> Box<dyn MacResult + 'cx> {
+ let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
+ Ok(assert) => assert,
+ Err(mut err) => {
+ err.emit();
+ return DummyResult::any(span);
+ }
+ };
+
+ // `core::panic` and `std::panic` are different macros, so we use call-site
+ // context to pick up whichever is currently in scope.
+ let call_site_span = cx.with_call_site_ctxt(span);
+
+ let panic_path = || {
+ if use_panic_2021(span) {
+ // On edition 2021, we always call `$crate::panic::panic_2021!()`.
+ Path {
+ span: call_site_span,
+ segments: cx
+ .std_path(&[sym::panic, sym::panic_2021])
+ .into_iter()
+ .map(|ident| PathSegment::from_ident(ident))
+ .collect(),
+ tokens: None,
+ }
+ } else {
+ // Before edition 2021, we call `panic!()` unqualified,
+ // such that it calls either `std::panic!()` or `core::panic!()`.
+ Path::from_ident(Ident::new(sym::panic, call_site_span))
+ }
+ };
+
+ // Simply uses the user provided message instead of generating custom outputs
+ let expr = if let Some(tokens) = custom_message {
+ let then = cx.expr(
+ call_site_span,
+ ExprKind::MacCall(MacCall {
+ path: panic_path(),
+ args: P(MacArgs::Delimited(
+ DelimSpan::from_single(call_site_span),
+ MacDelimiter::Parenthesis,
+ tokens,
+ )),
+ prior_type_ascription: None,
+ }),
+ );
+ expr_if_not(cx, call_site_span, cond_expr, then, None)
+ }
+ // If `generic_assert` is enabled, generates rich captured outputs
+ //
+ // FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
+ else if let Some(features) = cx.ecfg.features && features.generic_assert {
+ context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
+ }
+ // If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..."
+ // string
+ else {
+ // Pass our own message directly to $crate::panicking::panic(),
+ // because it might contain `{` and `}` that should always be
+ // passed literally.
+ let then = cx.expr_call_global(
+ call_site_span,
+ cx.std_path(&[sym::panicking, sym::panic]),
+ vec![cx.expr_str(
+ DUMMY_SP,
+ Symbol::intern(&format!(
+ "assertion failed: {}",
+ pprust::expr_to_string(&cond_expr).escape_debug()
+ )),
+ )],
+ );
+ expr_if_not(cx, call_site_span, cond_expr, then, None)
+ };
+
+ MacEager::expr(expr)
+}
+
+struct Assert {
+ cond_expr: P<Expr>,
+ custom_message: Option<TokenStream>,
+}
+
+// if !{ ... } { ... } else { ... }
+fn expr_if_not(
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ cond: P<Expr>,
+ then: P<Expr>,
+ els: Option<P<Expr>>,
+) -> P<Expr> {
+ cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
+}
+
+fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
+ 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);
+ }
+
+ let cond_expr = parser.parse_expr()?;
+
+ // Some crates use the `assert!` macro in the following form (note extra semicolon):
+ //
+ // assert!(
+ // my_function();
+ // );
+ //
+ // 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();
+
+ parser.bump();
+ }
+
+ // Some crates use the `assert!` macro in the following form (note missing comma before
+ // message):
+ //
+ // assert!(true "error message");
+ //
+ // 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();
+
+ parse_custom_message(&mut parser)
+ } else if parser.eat(&token::Comma) {
+ parse_custom_message(&mut parser)
+ } else {
+ None
+ };
+
+ if parser.token != token::Eof {
+ return parser.unexpected();
+ }
+
+ Ok(Assert { cond_expr, custom_message })
+}
+
+fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
+ let ts = parser.parse_tokens();
+ if !ts.is_empty() { Some(ts) } else { None }
+}
diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs
new file mode 100644
index 000000000..dcea883a5
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/assert/context.rs
@@ -0,0 +1,453 @@
+use rustc_ast::{
+ attr,
+ ptr::P,
+ token,
+ tokenstream::{DelimSpan, TokenStream, TokenTree},
+ BinOpKind, BorrowKind, Expr, ExprKind, ItemKind, MacArgs, MacCall, MacDelimiter, Mutability,
+ Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind, DUMMY_NODE_ID,
+};
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_expand::base::ExtCtxt;
+use rustc_span::{
+ symbol::{sym, Ident, Symbol},
+ Span,
+};
+
+pub(super) struct Context<'cx, 'a> {
+ // An optimization.
+ //
+ // Elements that aren't consumed (PartialEq, PartialOrd, ...) can be copied **after** the
+ // `assert!` expression fails rather than copied on-the-fly.
+ best_case_captures: Vec<Stmt>,
+ // Top-level `let captureN = Capture::new()` statements
+ capture_decls: Vec<Capture>,
+ cx: &'cx ExtCtxt<'a>,
+ // Formatting string used for debugging
+ fmt_string: String,
+ // If the current expression being visited consumes itself. Used to construct
+ // `best_case_captures`.
+ is_consumed: bool,
+ // Top-level `let __local_bindN = &expr` statements
+ local_bind_decls: Vec<Stmt>,
+ // Used to avoid capturing duplicated paths
+ //
+ // ```rust
+ // let a = 1i32;
+ // assert!(add(a, a) == 3);
+ // ```
+ paths: FxHashSet<Ident>,
+ span: Span,
+}
+
+impl<'cx, 'a> Context<'cx, 'a> {
+ pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self {
+ Self {
+ best_case_captures: <_>::default(),
+ capture_decls: <_>::default(),
+ cx,
+ fmt_string: <_>::default(),
+ is_consumed: true,
+ local_bind_decls: <_>::default(),
+ paths: <_>::default(),
+ span,
+ }
+ }
+
+ /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to:
+ ///
+ /// ```rust
+ /// let elem = 1;
+ /// {
+ /// #[allow(unused_imports)]
+ /// use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
+ /// let mut __capture0 = ::core::asserting::Capture::new();
+ /// let __local_bind0 = &elem;
+ /// if !(
+ /// *{
+ /// (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
+ /// __local_bind0
+ /// } == 1
+ /// ) {
+ /// panic!("Assertion failed: elem == 1\nWith captures:\n elem = {}", __capture0)
+ /// }
+ /// }
+ /// ```
+ pub(super) fn build(mut self, mut cond_expr: P<Expr>, panic_path: Path) -> P<Expr> {
+ let expr_str = pprust::expr_to_string(&cond_expr);
+ self.manage_cond_expr(&mut cond_expr);
+ let initial_imports = self.build_initial_imports();
+ let panic = self.build_panic(&expr_str, panic_path);
+ let cond_expr_with_unlikely = self.build_unlikely(cond_expr);
+
+ let Self { best_case_captures, capture_decls, cx, local_bind_decls, span, .. } = self;
+
+ let mut assert_then_stmts = Vec::with_capacity(2);
+ assert_then_stmts.extend(best_case_captures);
+ assert_then_stmts.push(self.cx.stmt_expr(panic));
+ let assert_then = self.cx.block(span, assert_then_stmts);
+
+ let mut stmts = Vec::with_capacity(4);
+ stmts.push(initial_imports);
+ stmts.extend(capture_decls.into_iter().map(|c| c.decl));
+ stmts.extend(local_bind_decls);
+ stmts.push(
+ cx.stmt_expr(cx.expr(span, ExprKind::If(cond_expr_with_unlikely, assert_then, None))),
+ );
+ cx.expr_block(cx.block(span, stmts))
+ }
+
+ /// Initial **trait** imports
+ ///
+ /// use ::core::asserting::{ ... };
+ fn build_initial_imports(&self) -> Stmt {
+ let nested_tree = |this: &Self, sym| {
+ (
+ UseTree {
+ prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]),
+ kind: UseTreeKind::Simple(None, DUMMY_NODE_ID, DUMMY_NODE_ID),
+ span: this.span,
+ },
+ DUMMY_NODE_ID,
+ )
+ };
+ self.cx.stmt_item(
+ self.span,
+ self.cx.item(
+ self.span,
+ Ident::empty(),
+ vec![self.cx.attribute(attr::mk_list_item(
+ Ident::new(sym::allow, self.span),
+ vec![attr::mk_nested_word_item(Ident::new(sym::unused_imports, self.span))],
+ ))],
+ ItemKind::Use(UseTree {
+ prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])),
+ kind: UseTreeKind::Nested(vec![
+ nested_tree(self, sym::TryCaptureGeneric),
+ nested_tree(self, sym::TryCapturePrintable),
+ ]),
+ span: self.span,
+ }),
+ ),
+ )
+ }
+
+ /// Takes the conditional expression of `assert!` and then wraps it inside `unlikely`
+ fn build_unlikely(&self, cond_expr: P<Expr>) -> P<Expr> {
+ let unlikely_path = self.cx.std_path(&[sym::intrinsics, sym::unlikely]);
+ self.cx.expr_call(
+ self.span,
+ self.cx.expr_path(self.cx.path(self.span, unlikely_path)),
+ vec![self.cx.expr(self.span, ExprKind::Unary(UnOp::Not, cond_expr))],
+ )
+ }
+
+ /// The necessary custom `panic!(...)` expression.
+ ///
+ /// panic!(
+ /// "Assertion failed: ... \n With expansion: ...",
+ /// __capture0,
+ /// ...
+ /// );
+ fn build_panic(&self, expr_str: &str, panic_path: Path) -> P<Expr> {
+ let escaped_expr_str = escape_to_fmt(expr_str);
+ let initial = [
+ TokenTree::token_alone(
+ token::Literal(token::Lit {
+ kind: token::LitKind::Str,
+ symbol: Symbol::intern(&if self.fmt_string.is_empty() {
+ format!("Assertion failed: {escaped_expr_str}")
+ } else {
+ format!(
+ "Assertion failed: {escaped_expr_str}\nWith captures:\n{}",
+ &self.fmt_string
+ )
+ }),
+ suffix: None,
+ }),
+ self.span,
+ ),
+ TokenTree::token_alone(token::Comma, self.span),
+ ];
+ let captures = self.capture_decls.iter().flat_map(|cap| {
+ [
+ TokenTree::token_alone(token::Ident(cap.ident.name, false), cap.ident.span),
+ TokenTree::token_alone(token::Comma, self.span),
+ ]
+ });
+ self.cx.expr(
+ self.span,
+ ExprKind::MacCall(MacCall {
+ path: panic_path,
+ args: P(MacArgs::Delimited(
+ DelimSpan::from_single(self.span),
+ MacDelimiter::Parenthesis,
+ initial.into_iter().chain(captures).collect::<TokenStream>(),
+ )),
+ prior_type_ascription: None,
+ }),
+ )
+ }
+
+ /// Recursive function called until `cond_expr` and `fmt_str` are fully modified.
+ ///
+ /// See [Self::manage_initial_capture] and [Self::manage_try_capture]
+ fn manage_cond_expr(&mut self, expr: &mut P<Expr>) {
+ match (*expr).kind {
+ ExprKind::AddrOf(_, mutability, ref mut local_expr) => {
+ self.with_is_consumed_management(
+ matches!(mutability, Mutability::Mut),
+ |this| this.manage_cond_expr(local_expr)
+ );
+ }
+ ExprKind::Array(ref mut local_exprs) => {
+ for local_expr in local_exprs {
+ self.manage_cond_expr(local_expr);
+ }
+ }
+ ExprKind::Binary(ref op, ref mut lhs, ref mut rhs) => {
+ self.with_is_consumed_management(
+ matches!(
+ op.node,
+ BinOpKind::Add
+ | BinOpKind::And
+ | BinOpKind::BitAnd
+ | BinOpKind::BitOr
+ | BinOpKind::BitXor
+ | BinOpKind::Div
+ | BinOpKind::Mul
+ | BinOpKind::Or
+ | BinOpKind::Rem
+ | BinOpKind::Shl
+ | BinOpKind::Shr
+ | BinOpKind::Sub
+ ),
+ |this| {
+ this.manage_cond_expr(lhs);
+ this.manage_cond_expr(rhs);
+ }
+ );
+ }
+ ExprKind::Call(_, ref mut local_exprs) => {
+ for local_expr in local_exprs {
+ self.manage_cond_expr(local_expr);
+ }
+ }
+ ExprKind::Cast(ref mut local_expr, _) => {
+ self.manage_cond_expr(local_expr);
+ }
+ ExprKind::Index(ref mut prefix, ref mut suffix) => {
+ self.manage_cond_expr(prefix);
+ self.manage_cond_expr(suffix);
+ }
+ ExprKind::MethodCall(_, ref mut local_exprs, _) => {
+ for local_expr in local_exprs.iter_mut().skip(1) {
+ self.manage_cond_expr(local_expr);
+ }
+ }
+ ExprKind::Path(_, Path { ref segments, .. }) if let &[ref path_segment] = &segments[..] => {
+ let path_ident = path_segment.ident;
+ self.manage_initial_capture(expr, path_ident);
+ }
+ ExprKind::Paren(ref mut local_expr) => {
+ self.manage_cond_expr(local_expr);
+ }
+ ExprKind::Range(ref mut prefix, ref mut suffix, _) => {
+ if let Some(ref mut elem) = prefix {
+ self.manage_cond_expr(elem);
+ }
+ if let Some(ref mut elem) = suffix {
+ self.manage_cond_expr(elem);
+ }
+ }
+ ExprKind::Repeat(ref mut local_expr, ref mut elem) => {
+ self.manage_cond_expr(local_expr);
+ self.manage_cond_expr(&mut elem.value);
+ }
+ ExprKind::Struct(ref mut elem) => {
+ for field in &mut elem.fields {
+ self.manage_cond_expr(&mut field.expr);
+ }
+ if let StructRest::Base(ref mut local_expr) = elem.rest {
+ self.manage_cond_expr(local_expr);
+ }
+ }
+ ExprKind::Tup(ref mut local_exprs) => {
+ for local_expr in local_exprs {
+ self.manage_cond_expr(local_expr);
+ }
+ }
+ ExprKind::Unary(un_op, ref mut local_expr) => {
+ self.with_is_consumed_management(
+ matches!(un_op, UnOp::Neg | UnOp::Not),
+ |this| this.manage_cond_expr(local_expr)
+ );
+ }
+ // Expressions that are not worth or can not be captured.
+ //
+ // Full list instead of `_` to catch possible future inclusions and to
+ // sync with the `rfc-2011-nicer-assert-messages/all-expr-kinds.rs` test.
+ ExprKind::Assign(_, _, _)
+ | ExprKind::AssignOp(_, _, _)
+ | ExprKind::Async(_, _, _)
+ | ExprKind::Await(_)
+ | ExprKind::Block(_, _)
+ | ExprKind::Box(_)
+ | ExprKind::Break(_, _)
+ | ExprKind::Closure(_, _, _, _, _, _, _)
+ | ExprKind::ConstBlock(_)
+ | ExprKind::Continue(_)
+ | ExprKind::Err
+ | ExprKind::Field(_, _)
+ | ExprKind::ForLoop(_, _, _, _)
+ | ExprKind::If(_, _, _)
+ | ExprKind::InlineAsm(_)
+ | ExprKind::Let(_, _, _)
+ | ExprKind::Lit(_)
+ | ExprKind::Loop(_, _)
+ | ExprKind::MacCall(_)
+ | ExprKind::Match(_, _)
+ | ExprKind::Path(_, _)
+ | ExprKind::Ret(_)
+ | ExprKind::Try(_)
+ | ExprKind::TryBlock(_)
+ | ExprKind::Type(_, _)
+ | ExprKind::Underscore
+ | ExprKind::While(_, _, _)
+ | ExprKind::Yeet(_)
+ | ExprKind::Yield(_) => {}
+ }
+ }
+
+ /// Pushes the top-level declarations and modifies `expr` to try capturing variables.
+ ///
+ /// `fmt_str`, the formatting string used for debugging, is constructed to show possible
+ /// captured variables.
+ fn manage_initial_capture(&mut self, expr: &mut P<Expr>, path_ident: Ident) {
+ if self.paths.contains(&path_ident) {
+ return;
+ } else {
+ self.fmt_string.push_str(" ");
+ self.fmt_string.push_str(path_ident.as_str());
+ self.fmt_string.push_str(" = {:?}\n");
+ let _ = self.paths.insert(path_ident);
+ }
+ let curr_capture_idx = self.capture_decls.len();
+ let capture_string = format!("__capture{curr_capture_idx}");
+ let ident = Ident::new(Symbol::intern(&capture_string), self.span);
+ let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]);
+ let init = self.cx.expr_call(
+ self.span,
+ self.cx.expr_path(self.cx.path(self.span, init_std_path)),
+ vec![],
+ );
+ let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident };
+ self.capture_decls.push(capture);
+ self.manage_try_capture(ident, curr_capture_idx, expr);
+ }
+
+ /// Tries to copy `__local_bindN` into `__captureN`.
+ ///
+ /// *{
+ /// (&Wrapper(__local_bindN)).try_capture(&mut __captureN);
+ /// __local_bindN
+ /// }
+ fn manage_try_capture(&mut self, capture: Ident, curr_capture_idx: usize, expr: &mut P<Expr>) {
+ let local_bind_string = format!("__local_bind{curr_capture_idx}");
+ let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span);
+ self.local_bind_decls.push(self.cx.stmt_let(
+ self.span,
+ false,
+ local_bind,
+ self.cx.expr_addr_of(self.span, expr.clone()),
+ ));
+ let wrapper = self.cx.expr_call(
+ self.span,
+ self.cx.expr_path(
+ self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])),
+ ),
+ vec![self.cx.expr_path(Path::from_ident(local_bind))],
+ );
+ let try_capture_call = self
+ .cx
+ .stmt_expr(expr_method_call(
+ self.cx,
+ PathSegment {
+ args: None,
+ id: DUMMY_NODE_ID,
+ ident: Ident::new(sym::try_capture, self.span),
+ },
+ vec![
+ expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)),
+ expr_addr_of_mut(
+ self.cx,
+ self.span,
+ self.cx.expr_path(Path::from_ident(capture)),
+ ),
+ ],
+ self.span,
+ ))
+ .add_trailing_semicolon();
+ let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind));
+ let rslt = if self.is_consumed {
+ let ret = self.cx.stmt_expr(local_bind_path);
+ self.cx.expr_block(self.cx.block(self.span, vec![try_capture_call, ret]))
+ } else {
+ self.best_case_captures.push(try_capture_call);
+ local_bind_path
+ };
+ *expr = self.cx.expr_deref(self.span, rslt);
+ }
+
+ // Calls `f` with the internal `is_consumed` set to `curr_is_consumed` and then
+ // sets the internal `is_consumed` back to its original value.
+ fn with_is_consumed_management(&mut self, curr_is_consumed: bool, f: impl FnOnce(&mut Self)) {
+ let prev_is_consumed = self.is_consumed;
+ self.is_consumed = curr_is_consumed;
+ f(self);
+ self.is_consumed = prev_is_consumed;
+ }
+}
+
+/// Information about a captured element.
+#[derive(Debug)]
+struct Capture {
+ // Generated indexed `Capture` statement.
+ //
+ // `let __capture{} = Capture::new();`
+ decl: Stmt,
+ // The name of the generated indexed `Capture` variable.
+ //
+ // `__capture{}`
+ ident: Ident,
+}
+
+/// Escapes to use as a formatting string.
+fn escape_to_fmt(s: &str) -> String {
+ let mut rslt = String::with_capacity(s.len());
+ for c in s.chars() {
+ rslt.extend(c.escape_debug());
+ match c {
+ '{' | '}' => rslt.push(c),
+ _ => {}
+ }
+ }
+ rslt
+}
+
+fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> {
+ cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e))
+}
+
+fn expr_method_call(
+ cx: &ExtCtxt<'_>,
+ path: PathSegment,
+ args: Vec<P<Expr>>,
+ span: Span,
+) -> P<Expr> {
+ cx.expr(span, ExprKind::MethodCall(path, args, span))
+}
+
+fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> {
+ cx.expr(sp, ExprKind::Paren(e))
+}
diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs
new file mode 100644
index 000000000..aa355150b
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cfg.rs
@@ -0,0 +1,69 @@
+//! The compiler code necessary to support the cfg! extension, which expands to
+//! a literal `true` or `false` based on whether the given cfg matches the
+//! current compilation environment.
+
+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::SessionDiagnostic;
+use rustc_span::Span;
+
+pub fn expand_cfg(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+
+ match parse_cfg(cx, sp, tts) {
+ Ok(cfg) => {
+ let matches_cfg = attr::cfg_matches(
+ &cfg,
+ &cx.sess.parse_sess,
+ cx.current_expansion.lint_node_id,
+ cx.ecfg.features,
+ );
+ MacEager::expr(cx.expr_bool(sp, matches_cfg))
+ }
+ Err(mut err) => {
+ err.emit();
+ DummyResult::any(sp)
+ }
+ }
+}
+
+#[derive(SessionDiagnostic)]
+#[error(builtin_macros::requires_cfg_pattern)]
+struct RequiresCfgPattern {
+ #[primary_span]
+ #[label]
+ span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(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 }));
+ }
+
+ let cfg = p.parse_meta_item()?;
+
+ let _ = p.eat(&token::Comma);
+
+ if !p.eat(&token::Eof) {
+ return Err(cx.create_err(OneCfgPattern { span }));
+ }
+
+ Ok(cfg)
+}
diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs
new file mode 100644
index 000000000..cb5359dd1
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs
@@ -0,0 +1,61 @@
+//! Implementation of the `#[cfg_accessible(path)]` attribute macro.
+
+use rustc_ast as ast;
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
+use rustc_feature::AttributeTemplate;
+use rustc_parse::validate_attr;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+pub(crate) struct Expander;
+
+fn validate_input<'a>(ecx: &mut ExtCtxt<'_>, mi: &'a ast::MetaItem) -> Option<&'a ast::Path> {
+ 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([nmi]) => match nmi.meta_item() {
+ None => ecx.span_err(nmi.span(), "`cfg_accessible` path cannot be a literal"),
+ Some(mi) => {
+ if !mi.is_word() {
+ ecx.span_err(mi.span, "`cfg_accessible` path cannot accept arguments");
+ }
+ return Some(&mi.path);
+ }
+ },
+ }
+ None
+}
+
+impl MultiItemModifier for Expander {
+ fn expand(
+ &self,
+ ecx: &mut ExtCtxt<'_>,
+ span: Span,
+ meta_item: &ast::MetaItem,
+ item: Annotatable,
+ ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
+ let template = AttributeTemplate { list: Some("path"), ..Default::default() };
+ let attr = &ecx.attribute(meta_item.clone());
+ validate_attr::check_builtin_attribute(
+ &ecx.sess.parse_sess,
+ attr,
+ sym::cfg_accessible,
+ template,
+ );
+
+ let Some(path) = validate_input(ecx, meta_item) else {
+ return ExpandResult::Ready(Vec::new());
+ };
+
+ match ecx.resolver.cfg_accessible(ecx.current_expansion.id, path) {
+ 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");
+ ExpandResult::Ready(vec![item])
+ }
+ Err(Indeterminate) => ExpandResult::Retry(item),
+ }
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs
new file mode 100644
index 000000000..89b2c3292
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs
@@ -0,0 +1,269 @@
+use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
+
+use rustc_ast as ast;
+use rustc_ast::mut_visit::MutVisitor;
+use rustc_ast::ptr::P;
+use rustc_ast::visit::Visitor;
+use rustc_ast::NodeId;
+use rustc_ast::{mut_visit, visit};
+use rustc_ast::{Attribute, HasAttrs, HasTokens};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_expand::config::StripUnconfigured;
+use rustc_expand::configure;
+use rustc_feature::Features;
+use rustc_parse::parser::{ForceCollect, Parser};
+use rustc_session::Session;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use smallvec::SmallVec;
+
+pub(crate) fn expand(
+ ecx: &mut ExtCtxt<'_>,
+ _span: Span,
+ meta_item: &ast::MetaItem,
+ annotatable: Annotatable,
+) -> Vec<Annotatable> {
+ check_builtin_macro_attribute(ecx, meta_item, sym::cfg_eval);
+ warn_on_duplicate_attribute(&ecx, &annotatable, sym::cfg_eval);
+ vec![cfg_eval(ecx.sess, ecx.ecfg.features, annotatable, ecx.current_expansion.lint_node_id)]
+}
+
+pub(crate) fn cfg_eval(
+ sess: &Session,
+ features: Option<&Features>,
+ annotatable: Annotatable,
+ lint_node_id: NodeId,
+) -> Annotatable {
+ CfgEval { cfg: &mut StripUnconfigured { sess, features, config_tokens: true, lint_node_id } }
+ .configure_annotatable(annotatable)
+ // Since the item itself has already been configured by the `InvocationCollector`,
+ // we know that fold result vector will contain exactly one element.
+ .unwrap()
+}
+
+struct CfgEval<'a, 'b> {
+ cfg: &'a mut StripUnconfigured<'b>,
+}
+
+fn flat_map_annotatable(
+ vis: &mut impl MutVisitor,
+ annotatable: Annotatable,
+) -> Option<Annotatable> {
+ match annotatable {
+ Annotatable::Item(item) => vis.flat_map_item(item).pop().map(Annotatable::Item),
+ Annotatable::TraitItem(item) => {
+ vis.flat_map_trait_item(item).pop().map(Annotatable::TraitItem)
+ }
+ Annotatable::ImplItem(item) => {
+ vis.flat_map_impl_item(item).pop().map(Annotatable::ImplItem)
+ }
+ Annotatable::ForeignItem(item) => {
+ vis.flat_map_foreign_item(item).pop().map(Annotatable::ForeignItem)
+ }
+ Annotatable::Stmt(stmt) => {
+ vis.flat_map_stmt(stmt.into_inner()).pop().map(P).map(Annotatable::Stmt)
+ }
+ Annotatable::Expr(mut expr) => {
+ vis.visit_expr(&mut expr);
+ Some(Annotatable::Expr(expr))
+ }
+ Annotatable::Arm(arm) => vis.flat_map_arm(arm).pop().map(Annotatable::Arm),
+ Annotatable::ExprField(field) => {
+ vis.flat_map_expr_field(field).pop().map(Annotatable::ExprField)
+ }
+ Annotatable::PatField(fp) => vis.flat_map_pat_field(fp).pop().map(Annotatable::PatField),
+ Annotatable::GenericParam(param) => {
+ vis.flat_map_generic_param(param).pop().map(Annotatable::GenericParam)
+ }
+ Annotatable::Param(param) => vis.flat_map_param(param).pop().map(Annotatable::Param),
+ Annotatable::FieldDef(sf) => vis.flat_map_field_def(sf).pop().map(Annotatable::FieldDef),
+ Annotatable::Variant(v) => vis.flat_map_variant(v).pop().map(Annotatable::Variant),
+ Annotatable::Crate(mut krate) => {
+ vis.visit_crate(&mut krate);
+ Some(Annotatable::Crate(krate))
+ }
+ }
+}
+
+struct CfgFinder {
+ has_cfg_or_cfg_attr: bool,
+}
+
+impl CfgFinder {
+ fn has_cfg_or_cfg_attr(annotatable: &Annotatable) -> bool {
+ let mut finder = CfgFinder { has_cfg_or_cfg_attr: false };
+ match annotatable {
+ Annotatable::Item(item) => finder.visit_item(&item),
+ Annotatable::TraitItem(item) => finder.visit_assoc_item(&item, visit::AssocCtxt::Trait),
+ Annotatable::ImplItem(item) => finder.visit_assoc_item(&item, visit::AssocCtxt::Impl),
+ Annotatable::ForeignItem(item) => finder.visit_foreign_item(&item),
+ Annotatable::Stmt(stmt) => finder.visit_stmt(&stmt),
+ Annotatable::Expr(expr) => finder.visit_expr(&expr),
+ Annotatable::Arm(arm) => finder.visit_arm(&arm),
+ Annotatable::ExprField(field) => finder.visit_expr_field(&field),
+ Annotatable::PatField(field) => finder.visit_pat_field(&field),
+ Annotatable::GenericParam(param) => finder.visit_generic_param(&param),
+ Annotatable::Param(param) => finder.visit_param(&param),
+ Annotatable::FieldDef(field) => finder.visit_field_def(&field),
+ Annotatable::Variant(variant) => finder.visit_variant(&variant),
+ Annotatable::Crate(krate) => finder.visit_crate(krate),
+ };
+ finder.has_cfg_or_cfg_attr
+ }
+}
+
+impl<'ast> visit::Visitor<'ast> for CfgFinder {
+ fn visit_attribute(&mut self, attr: &'ast Attribute) {
+ // We want short-circuiting behavior, so don't use the '|=' operator.
+ self.has_cfg_or_cfg_attr = self.has_cfg_or_cfg_attr
+ || attr
+ .ident()
+ .map_or(false, |ident| ident.name == sym::cfg || ident.name == sym::cfg_attr);
+ }
+}
+
+impl CfgEval<'_, '_> {
+ fn configure<T: HasAttrs + HasTokens>(&mut self, node: T) -> Option<T> {
+ self.cfg.configure(node)
+ }
+
+ fn configure_annotatable(&mut self, mut annotatable: Annotatable) -> Option<Annotatable> {
+ // Tokenizing and re-parsing the `Annotatable` can have a significant
+ // performance impact, so try to avoid it if possible
+ if !CfgFinder::has_cfg_or_cfg_attr(&annotatable) {
+ return Some(annotatable);
+ }
+
+ // The majority of parsed attribute targets will never need to have early cfg-expansion
+ // run (e.g. they are not part of a `#[derive]` or `#[cfg_eval]` macro input).
+ // Therefore, we normally do not capture the necessary information about `#[cfg]`
+ // and `#[cfg_attr]` attributes during parsing.
+ //
+ // Therefore, when we actually *do* run early cfg-expansion, we need to tokenize
+ // and re-parse the attribute target, this time capturing information about
+ // the location of `#[cfg]` and `#[cfg_attr]` in the token stream. The tokenization
+ // process is lossless, so this process is invisible to proc-macros.
+
+ let parse_annotatable_with: fn(&mut Parser<'_>) -> _ = match annotatable {
+ Annotatable::Item(_) => {
+ |parser| Annotatable::Item(parser.parse_item(ForceCollect::Yes).unwrap().unwrap())
+ }
+ Annotatable::TraitItem(_) => |parser| {
+ Annotatable::TraitItem(
+ parser.parse_trait_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
+ )
+ },
+ Annotatable::ImplItem(_) => |parser| {
+ Annotatable::ImplItem(
+ parser.parse_impl_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
+ )
+ },
+ Annotatable::ForeignItem(_) => |parser| {
+ Annotatable::ForeignItem(
+ parser.parse_foreign_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
+ )
+ },
+ Annotatable::Stmt(_) => |parser| {
+ Annotatable::Stmt(P(parser.parse_stmt(ForceCollect::Yes).unwrap().unwrap()))
+ },
+ Annotatable::Expr(_) => {
+ |parser| Annotatable::Expr(parser.parse_expr_force_collect().unwrap())
+ }
+ _ => unreachable!(),
+ };
+
+ // 'Flatten' all nonterminals (i.e. `TokenKind::Interpolated`)
+ // to `None`-delimited groups containing the corresponding tokens. This
+ // is normally delayed until the proc-macro server actually needs to
+ // provide a `TokenKind::Interpolated` to a proc-macro. We do this earlier,
+ // so that we can handle cases like:
+ //
+ // ```rust
+ // #[cfg_eval] #[cfg] $item
+ //```
+ //
+ // where `$item` is `#[cfg_attr] struct Foo {}`. We want to make
+ // sure to evaluate *all* `#[cfg]` and `#[cfg_attr]` attributes - the simplest
+ // way to do this is to do a single parse of a stream without any nonterminals.
+ let orig_tokens = annotatable.to_tokens().flattened();
+
+ // Re-parse the tokens, setting the `capture_cfg` flag to save extra information
+ // to the captured `AttrAnnotatedTokenStream` (specifically, we capture
+ // `AttrAnnotatedTokenTree::AttributesData` for all occurrences of `#[cfg]` and `#[cfg_attr]`)
+ let mut parser =
+ rustc_parse::stream_to_parser(&self.cfg.sess.parse_sess, orig_tokens, None);
+ parser.capture_cfg = true;
+ annotatable = parse_annotatable_with(&mut parser);
+
+ // Now that we have our re-parsed `AttrAnnotatedTokenStream`, recursively configuring
+ // our attribute target will correctly the tokens as well.
+ flat_map_annotatable(self, annotatable)
+ }
+}
+
+impl MutVisitor for CfgEval<'_, '_> {
+ fn visit_expr(&mut self, expr: &mut P<ast::Expr>) {
+ self.cfg.configure_expr(expr);
+ mut_visit::noop_visit_expr(expr, self);
+ }
+
+ fn filter_map_expr(&mut self, expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
+ let mut expr = configure!(self, expr);
+ mut_visit::noop_visit_expr(&mut expr, self);
+ Some(expr)
+ }
+
+ fn flat_map_generic_param(
+ &mut self,
+ param: ast::GenericParam,
+ ) -> SmallVec<[ast::GenericParam; 1]> {
+ mut_visit::noop_flat_map_generic_param(configure!(self, param), self)
+ }
+
+ fn flat_map_stmt(&mut self, stmt: ast::Stmt) -> SmallVec<[ast::Stmt; 1]> {
+ mut_visit::noop_flat_map_stmt(configure!(self, stmt), self)
+ }
+
+ fn flat_map_item(&mut self, item: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
+ mut_visit::noop_flat_map_item(configure!(self, item), self)
+ }
+
+ fn flat_map_impl_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
+ mut_visit::noop_flat_map_assoc_item(configure!(self, item), self)
+ }
+
+ fn flat_map_trait_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
+ mut_visit::noop_flat_map_assoc_item(configure!(self, item), self)
+ }
+
+ fn flat_map_foreign_item(
+ &mut self,
+ foreign_item: P<ast::ForeignItem>,
+ ) -> SmallVec<[P<ast::ForeignItem>; 1]> {
+ mut_visit::noop_flat_map_foreign_item(configure!(self, foreign_item), self)
+ }
+
+ fn flat_map_arm(&mut self, arm: ast::Arm) -> SmallVec<[ast::Arm; 1]> {
+ mut_visit::noop_flat_map_arm(configure!(self, arm), self)
+ }
+
+ fn flat_map_expr_field(&mut self, field: ast::ExprField) -> SmallVec<[ast::ExprField; 1]> {
+ mut_visit::noop_flat_map_expr_field(configure!(self, field), self)
+ }
+
+ fn flat_map_pat_field(&mut self, fp: ast::PatField) -> SmallVec<[ast::PatField; 1]> {
+ mut_visit::noop_flat_map_pat_field(configure!(self, fp), self)
+ }
+
+ fn flat_map_param(&mut self, p: ast::Param) -> SmallVec<[ast::Param; 1]> {
+ mut_visit::noop_flat_map_param(configure!(self, p), self)
+ }
+
+ fn flat_map_field_def(&mut self, sf: ast::FieldDef) -> SmallVec<[ast::FieldDef; 1]> {
+ mut_visit::noop_flat_map_field_def(configure!(self, sf), self)
+ }
+
+ fn flat_map_variant(&mut self, variant: ast::Variant) -> SmallVec<[ast::Variant; 1]> {
+ mut_visit::noop_flat_map_variant(configure!(self, variant), self)
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs
new file mode 100644
index 000000000..747e48ece
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs
@@ -0,0 +1,35 @@
+//! Attributes injected into the crate root from command line using `-Z crate-attr`.
+
+use rustc_ast::attr::mk_attr;
+use rustc_ast::token;
+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 {
+ for raw_attr in attrs {
+ let mut parser = rustc_parse::new_parser_from_source_str(
+ parse_sess,
+ FileName::cli_crate_attr_source_code(&raw_attr),
+ raw_attr.clone(),
+ );
+
+ let start_span = parser.token.span;
+ let AttrItem { path, args, tokens: _ } = match parser.parse_attr_item(false) {
+ Ok(ai) => ai,
+ Err(mut err) => {
+ err.emit();
+ continue;
+ }
+ };
+ let end_span = parser.token.span;
+ if parser.token != token::Eof {
+ parse_sess.span_diagnostic.span_err(start_span.to(end_span), "invalid crate attribute");
+ continue;
+ }
+
+ krate.attrs.push(mk_attr(AttrStyle::Inner, path, args, 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
new file mode 100644
index 000000000..72397aa25
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/compile_error.rs
@@ -0,0 +1,19 @@
+// The compiler code necessary to support the compile_error! extension.
+
+use rustc_ast::tokenstream::TokenStream;
+use rustc_expand::base::{self, *};
+use rustc_span::Span;
+
+pub fn expand_compile_error<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ let Some(var) = get_single_str_from_tts(cx, sp, tts, "compile_error!") else {
+ return DummyResult::any(sp);
+ };
+
+ 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
new file mode 100644
index 000000000..a23dd1d12
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/concat.rs
@@ -0,0 +1,64 @@
+use rustc_ast as ast;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_expand::base::{self, DummyResult};
+use rustc_span::symbol::Symbol;
+
+use std::string::String;
+
+pub fn expand_concat(
+ cx: &mut base::ExtCtxt<'_>,
+ sp: rustc_span::Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let Some(es) = base::get_exprs_from_tts(cx, sp, tts) else {
+ return DummyResult::any(sp);
+ };
+ let mut accumulator = String::new();
+ let mut missing_literal = vec![];
+ let mut has_errors = false;
+ for e in es {
+ match e.kind {
+ ast::ExprKind::Lit(ref lit) => match lit.kind {
+ ast::LitKind::Str(ref s, _) | ast::LitKind::Float(ref s, _) => {
+ accumulator.push_str(s.as_str());
+ }
+ ast::LitKind::Char(c) => {
+ accumulator.push(c);
+ }
+ ast::LitKind::Int(
+ i,
+ ast::LitIntType::Unsigned(_)
+ | ast::LitIntType::Signed(_)
+ | ast::LitIntType::Unsuffixed,
+ ) => {
+ accumulator.push_str(&i.to_string());
+ }
+ ast::LitKind::Bool(b) => {
+ accumulator.push_str(&b.to_string());
+ }
+ ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..) => {
+ cx.span_err(e.span, "cannot concatenate a byte string literal");
+ }
+ ast::LitKind::Err(_) => {
+ has_errors = true;
+ }
+ },
+ ast::ExprKind::Err => {
+ has_errors = true;
+ }
+ _ => {
+ missing_literal.push(e.span);
+ }
+ }
+ }
+ 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();
+ return DummyResult::any(sp);
+ } else if has_errors {
+ return DummyResult::any(sp);
+ }
+ let sp = cx.with_def_site_ctxt(sp);
+ base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&accumulator)))
+}
diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs
new file mode 100644
index 000000000..a1afec410
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs
@@ -0,0 +1,189 @@
+use rustc_ast as ast;
+use rustc_ast::{ptr::P, tokenstream::TokenStream};
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::Applicability;
+use rustc_expand::base::{self, DummyResult};
+
+/// Emits errors for literal expressions that are invalid inside and outside of an array.
+fn invalid_type_err(cx: &mut base::ExtCtxt<'_>, expr: &P<rustc_ast::Expr>, is_nested: bool) {
+ let ast::ExprKind::Lit(lit) = &expr.kind else {
+ unreachable!();
+ };
+ match lit.kind {
+ ast::LitKind::Char(_) => {
+ let mut err = cx.struct_span_err(expr.span, "cannot concatenate character literals");
+ if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
+ err.span_suggestion(
+ expr.span,
+ "try using a byte character",
+ format!("b{}", snippet),
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ }
+ }
+ ast::LitKind::Str(_, _) => {
+ let mut err = cx.struct_span_err(expr.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(expr.span) {
+ err.span_suggestion(
+ expr.span,
+ "try using a byte string",
+ format!("b{}", snippet),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ err.emit();
+ }
+ ast::LitKind::Float(_, _) => {
+ cx.span_err(expr.span, "cannot concatenate float literals");
+ }
+ ast::LitKind::Bool(_) => {
+ cx.span_err(expr.span, "cannot concatenate boolean literals");
+ }
+ ast::LitKind::Err(_) => {}
+ ast::LitKind::Int(_, _) if !is_nested => {
+ let mut err = cx.struct_span_err(expr.span, "cannot concatenate numeric literals");
+ if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
+ err.span_suggestion(
+ expr.span,
+ "try wrapping the number in an array",
+ format!("[{}]", snippet),
+ Applicability::MachineApplicable,
+ );
+ }
+ err.emit();
+ }
+ 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(expr.span, "numeric literal is out of bounds");
+ }
+ ast::LitKind::Int(_, _) => {
+ cx.span_err(expr.span, "numeric literal is not a `u8`");
+ }
+ _ => unreachable!(),
+ }
+}
+
+fn handle_array_element(
+ cx: &mut base::ExtCtxt<'_>,
+ has_errors: &mut bool,
+ missing_literals: &mut Vec<rustc_span::Span>,
+ expr: &P<rustc_ast::Expr>,
+) -> Option<u8> {
+ match expr.kind {
+ ast::ExprKind::Array(_) | ast::ExprKind::Repeat(_, _) => {
+ if !*has_errors {
+ cx.span_err(expr.span, "cannot concatenate doubly nested array");
+ }
+ *has_errors = true;
+ None
+ }
+ ast::ExprKind::Lit(ref lit) => match lit.kind {
+ ast::LitKind::Int(
+ val,
+ ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8),
+ ) if val <= u8::MAX.into() => Some(val as u8),
+
+ ast::LitKind::Byte(val) => Some(val),
+ 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();
+ }
+ *has_errors = true;
+ None
+ }
+ _ => {
+ if !*has_errors {
+ invalid_type_err(cx, expr, true);
+ }
+ *has_errors = true;
+ None
+ }
+ },
+ _ => {
+ missing_literals.push(expr.span);
+ None
+ }
+ }
+}
+
+pub fn expand_concat_bytes(
+ cx: &mut base::ExtCtxt<'_>,
+ sp: rustc_span::Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let Some(es) = base::get_exprs_from_tts(cx, sp, tts) else {
+ return DummyResult::any(sp);
+ };
+ let mut accumulator = Vec::new();
+ let mut missing_literals = vec![];
+ let mut has_errors = false;
+ for e in es {
+ match e.kind {
+ ast::ExprKind::Array(ref exprs) => {
+ for expr in exprs {
+ if let Some(elem) =
+ handle_array_element(cx, &mut has_errors, &mut missing_literals, expr)
+ {
+ accumulator.push(elem);
+ }
+ }
+ }
+ ast::ExprKind::Repeat(ref expr, ref count) => {
+ if let ast::ExprKind::Lit(ast::Lit {
+ kind: ast::LitKind::Int(count_val, _), ..
+ }) = count.value.kind
+ {
+ if let Some(elem) =
+ handle_array_element(cx, &mut has_errors, &mut missing_literals, expr)
+ {
+ for _ in 0..count_val {
+ accumulator.push(elem);
+ }
+ }
+ } else {
+ cx.span_err(count.value.span, "repeat count is not a positive number");
+ }
+ }
+ ast::ExprKind::Lit(ref lit) => match lit.kind {
+ ast::LitKind::Byte(val) => {
+ accumulator.push(val);
+ }
+ ast::LitKind::ByteStr(ref bytes) => {
+ accumulator.extend_from_slice(&bytes);
+ }
+ _ => {
+ if !has_errors {
+ invalid_type_err(cx, &e, false);
+ }
+ has_errors = true;
+ }
+ },
+ ast::ExprKind::Err => {
+ has_errors = true;
+ }
+ _ => {
+ missing_literals.push(e.span);
+ }
+ }
+ }
+ if !missing_literals.is_empty() {
+ let mut err = cx.struct_span_err(missing_literals.clone(), "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();
+ return base::MacEager::expr(DummyResult::raw_expr(sp, true));
+ } else if has_errors {
+ return base::MacEager::expr(DummyResult::raw_expr(sp, true));
+ }
+ let sp = cx.with_def_site_ctxt(sp);
+ base::MacEager::expr(cx.expr_lit(sp, ast::LitKind::ByteStr(Lrc::from(accumulator))))
+}
diff --git a/compiler/rustc_builtin_macros/src/concat_idents.rs b/compiler/rustc_builtin_macros/src/concat_idents.rs
new file mode 100644
index 000000000..297c604e0
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/concat_idents.rs
@@ -0,0 +1,70 @@
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::token::{self, Token};
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_expand::base::{self, *};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+pub fn expand_concat_idents<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ if tts.is_empty() {
+ cx.span_err(sp, "concat_idents! takes 1 or more arguments");
+ return DummyResult::any(sp);
+ }
+
+ let mut res_str = String::new();
+ for (i, e) in tts.into_trees().enumerate() {
+ if i & 1 == 1 {
+ match e {
+ TokenTree::Token(Token { kind: token::Comma, .. }, _) => {}
+ _ => {
+ cx.span_err(sp, "concat_idents! expecting comma");
+ return DummyResult::any(sp);
+ }
+ }
+ } else {
+ if let TokenTree::Token(token, _) = e {
+ if let Some((ident, _)) = token.ident() {
+ res_str.push_str(ident.name.as_str());
+ continue;
+ }
+ }
+
+ cx.span_err(sp, "concat_idents! requires ident args");
+ return DummyResult::any(sp);
+ }
+ }
+
+ let ident = Ident::new(Symbol::intern(&res_str), cx.with_call_site_ctxt(sp));
+
+ struct ConcatIdentsResult {
+ ident: Ident,
+ }
+
+ impl base::MacResult for ConcatIdentsResult {
+ fn make_expr(self: Box<Self>) -> Option<P<ast::Expr>> {
+ Some(P(ast::Expr {
+ id: ast::DUMMY_NODE_ID,
+ kind: ast::ExprKind::Path(None, ast::Path::from_ident(self.ident)),
+ span: self.ident.span,
+ attrs: ast::AttrVec::new(),
+ tokens: None,
+ }))
+ }
+
+ fn make_ty(self: Box<Self>) -> Option<P<ast::Ty>> {
+ Some(P(ast::Ty {
+ id: ast::DUMMY_NODE_ID,
+ kind: ast::TyKind::Path(None, ast::Path::from_ident(self.ident)),
+ span: self.ident.span,
+ tokens: None,
+ }))
+ }
+ }
+
+ Box::new(ConcatIdentsResult { ident })
+}
diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs
new file mode 100644
index 000000000..d3de10ca4
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/derive.rs
@@ -0,0 +1,158 @@
+use crate::cfg_eval::cfg_eval;
+
+use rustc_ast as ast;
+use rustc_ast::{attr, token, 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;
+use rustc_session::Session;
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+
+pub(crate) struct Expander;
+
+impl MultiItemModifier for Expander {
+ fn expand(
+ &self,
+ ecx: &mut ExtCtxt<'_>,
+ span: Span,
+ meta_item: &ast::MetaItem,
+ item: Annotatable,
+ ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
+ let sess = ecx.sess;
+ if report_bad_target(sess, &item, span) {
+ // We don't want to pass inappropriate targets to derive macros to avoid
+ // follow up errors, all other errors below are recoverable.
+ return ExpandResult::Ready(vec![item]);
+ }
+
+ let (sess, features) = (ecx.sess, ecx.ecfg.features);
+ let result =
+ ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| {
+ let template =
+ AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
+ let attr = attr::mk_attr_outer(meta_item.clone());
+ validate_attr::check_builtin_attribute(
+ &sess.parse_sess,
+ &attr,
+ sym::derive,
+ template,
+ );
+
+ let mut resolutions: Vec<_> = attr
+ .meta_item_list()
+ .unwrap_or_default()
+ .into_iter()
+ .filter_map(|nested_meta| match nested_meta {
+ NestedMetaItem::MetaItem(meta) => Some(meta),
+ NestedMetaItem::Literal(lit) => {
+ // Reject `#[derive("Debug")]`.
+ report_unexpected_literal(sess, &lit);
+ None
+ }
+ })
+ .map(|meta| {
+ // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
+ report_path_args(sess, &meta);
+ meta.path
+ })
+ .map(|path| (path, dummy_annotatable(), None))
+ .collect();
+
+ // Do not configure or clone items unless necessary.
+ match &mut resolutions[..] {
+ [] => {}
+ [(_, first_item, _), others @ ..] => {
+ *first_item = cfg_eval(
+ sess,
+ features,
+ item.clone(),
+ ecx.current_expansion.lint_node_id,
+ );
+ for (_, item, _) in others {
+ *item = first_item.clone();
+ }
+ }
+ }
+
+ resolutions
+ });
+
+ match result {
+ Ok(()) => ExpandResult::Ready(vec![item]),
+ Err(Indeterminate) => ExpandResult::Retry(item),
+ }
+ }
+}
+
+// The cheapest `Annotatable` to construct.
+fn dummy_annotatable() -> Annotatable {
+ Annotatable::GenericParam(ast::GenericParam {
+ id: ast::DUMMY_NODE_ID,
+ ident: Ident::empty(),
+ attrs: Default::default(),
+ bounds: Default::default(),
+ is_placeholder: false,
+ kind: GenericParamKind::Lifetime,
+ colon_span: None,
+ })
+}
+
+fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool {
+ let item_kind = match item {
+ Annotatable::Item(item) => Some(&item.kind),
+ Annotatable::Stmt(stmt) => match &stmt.kind {
+ StmtKind::Item(item) => Some(&item.kind),
+ _ => None,
+ },
+ _ => None,
+ };
+
+ 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();
+ }
+ bad_target
+}
+
+fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) {
+ let help_msg = match lit.token.kind {
+ token::Str if rustc_lexer::is_ident(lit.token.symbol.as_str()) => {
+ format!("try using `#[derive({})]`", lit.token.symbol)
+ }
+ _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(),
+ };
+ 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();
+}
+
+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();
+ };
+ match meta.kind {
+ MetaItemKind::Word => {}
+ MetaItemKind::List(..) => report_error(
+ "traits in `#[derive(...)]` don't accept arguments",
+ "remove the arguments",
+ ),
+ MetaItemKind::NameValue(..) => {
+ report_error("traits in `#[derive(...)]` don't accept values", "remove the value")
+ }
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/bounds.rs b/compiler/rustc_builtin_macros/src/deriving/bounds.rs
new file mode 100644
index 000000000..5ef68c6ae
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/bounds.rs
@@ -0,0 +1,28 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::MetaItem;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::Span;
+
+pub fn expand_deriving_copy(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: path_std!(marker::Copy),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: true,
+ methods: Vec::new(),
+ associated_types: Vec::new(),
+ };
+
+ trait_def.expand(cx, mitem, item, push);
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/clone.rs b/compiler/rustc_builtin_macros/src/deriving/clone.rs
new file mode 100644
index 000000000..7755ff779
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs
@@ -0,0 +1,212 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::{self as ast, Generics, ItemKind, MetaItem, VariantData};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{kw, sym, Ident};
+use rustc_span::Span;
+
+pub fn expand_deriving_clone(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ // The simple form is `fn clone(&self) -> Self { *self }`, possibly with
+ // some additional `AssertParamIsClone` assertions.
+ //
+ // We can use the simple form if either of the following are true.
+ // - The type derives Copy and there are no generic parameters. (If we
+ // used the simple form with generics, we'd have to bound the generics
+ // with Clone + Copy, and then there'd be no Clone impl at all if the
+ // user fills in something that is Clone but not Copy. After
+ // specialization we can remove this no-generics limitation.)
+ // - The item is a union. (Unions with generic parameters still can derive
+ // Clone because they require Copy for deriving, Clone alone is not
+ // enough. Whether Clone is implemented for fields is irrelevant so we
+ // don't assert it.)
+ let bounds;
+ let substructure;
+ let is_simple;
+ match *item {
+ Annotatable::Item(ref annitem) => match annitem.kind {
+ ItemKind::Struct(_, Generics { ref params, .. })
+ | ItemKind::Enum(_, Generics { ref params, .. }) => {
+ let container_id = cx.current_expansion.id.expn_data().parent.expect_local();
+ let has_derive_copy = cx.resolver.has_derive_copy(container_id);
+ if has_derive_copy
+ && !params
+ .iter()
+ .any(|param| matches!(param.kind, ast::GenericParamKind::Type { .. }))
+ {
+ bounds = vec![];
+ is_simple = true;
+ substructure = combine_substructure(Box::new(|c, s, sub| {
+ cs_clone_simple("Clone", c, s, sub, false)
+ }));
+ } else {
+ bounds = vec![];
+ is_simple = false;
+ substructure =
+ combine_substructure(Box::new(|c, s, sub| cs_clone("Clone", c, s, sub)));
+ }
+ }
+ ItemKind::Union(..) => {
+ bounds = vec![Path(path_std!(marker::Copy))];
+ is_simple = true;
+ substructure = combine_substructure(Box::new(|c, s, sub| {
+ cs_clone_simple("Clone", c, s, sub, true)
+ }));
+ }
+ _ => cx.span_bug(span, "`#[derive(Clone)]` on wrong item kind"),
+ },
+
+ _ => cx.span_bug(span, "`#[derive(Clone)]` on trait item or impl item"),
+ }
+
+ let inline = cx.meta_word(span, sym::inline);
+ let attrs = vec![cx.attribute(inline)];
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: path_std!(clone::Clone),
+ additional_bounds: bounds,
+ generics: Bounds::empty(),
+ supports_unions: true,
+ methods: vec![MethodDef {
+ name: sym::clone,
+ generics: Bounds::empty(),
+ explicit_self: true,
+ nonself_args: Vec::new(),
+ ret_ty: Self_,
+ attributes: attrs,
+ unify_fieldless_variants: false,
+ combine_substructure: substructure,
+ }],
+ associated_types: Vec::new(),
+ };
+
+ trait_def.expand_ext(cx, mitem, item, push, is_simple)
+}
+
+fn cs_clone_simple(
+ name: &str,
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substr: &Substructure<'_>,
+ is_union: bool,
+) -> BlockOrExpr {
+ let mut stmts = Vec::new();
+ let mut seen_type_names = FxHashSet::default();
+ let mut process_variant = |variant: &VariantData| {
+ for field in variant.fields() {
+ // This basic redundancy checking only prevents duplication of
+ // assertions like `AssertParamIsClone<Foo>` where the type is a
+ // simple name. That's enough to get a lot of cases, though.
+ if let Some(name) = field.ty.kind.is_simple_path() && !seen_type_names.insert(name) {
+ // Already produced an assertion for this type.
+ } else {
+ // let _: AssertParamIsClone<FieldTy>;
+ super::assert_ty_bounds(
+ cx,
+ &mut stmts,
+ field.ty.clone(),
+ field.span,
+ &[sym::clone, sym::AssertParamIsClone],
+ );
+ }
+ }
+ };
+
+ if is_union {
+ // Just a single assertion for unions, that the union impls `Copy`.
+ // let _: AssertParamIsCopy<Self>;
+ let self_ty = cx.ty_path(cx.path_ident(trait_span, Ident::with_dummy_span(kw::SelfUpper)));
+ super::assert_ty_bounds(
+ cx,
+ &mut stmts,
+ self_ty,
+ trait_span,
+ &[sym::clone, sym::AssertParamIsCopy],
+ );
+ } else {
+ match *substr.fields {
+ StaticStruct(vdata, ..) => {
+ process_variant(vdata);
+ }
+ StaticEnum(enum_def, ..) => {
+ for variant in &enum_def.variants {
+ process_variant(&variant.data);
+ }
+ }
+ _ => cx.span_bug(
+ trait_span,
+ &format!("unexpected substructure in simple `derive({})`", name),
+ ),
+ }
+ }
+ BlockOrExpr::new_mixed(stmts, Some(cx.expr_deref(trait_span, cx.expr_self(trait_span))))
+}
+
+fn cs_clone(
+ name: &str,
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substr: &Substructure<'_>,
+) -> BlockOrExpr {
+ let ctor_path;
+ let all_fields;
+ let fn_path = cx.std_path(&[sym::clone, sym::Clone, sym::clone]);
+ let subcall = |cx: &mut ExtCtxt<'_>, field: &FieldInfo| {
+ let args = vec![field.self_expr.clone()];
+ cx.expr_call_global(field.span, fn_path.clone(), args)
+ };
+
+ let vdata;
+ match *substr.fields {
+ Struct(vdata_, ref af) => {
+ ctor_path = cx.path(trait_span, vec![substr.type_ident]);
+ all_fields = af;
+ vdata = vdata_;
+ }
+ EnumMatching(.., variant, ref af) => {
+ ctor_path = cx.path(trait_span, vec![substr.type_ident, variant.ident]);
+ all_fields = af;
+ vdata = &variant.data;
+ }
+ EnumTag(..) => cx.span_bug(trait_span, &format!("enum tags in `derive({})`", name,)),
+ StaticEnum(..) | StaticStruct(..) => {
+ cx.span_bug(trait_span, &format!("associated function in `derive({})`", name))
+ }
+ }
+
+ let expr = match *vdata {
+ VariantData::Struct(..) => {
+ let fields = all_fields
+ .iter()
+ .map(|field| {
+ let Some(ident) = field.name else {
+ cx.span_bug(
+ trait_span,
+ &format!("unnamed field in normal struct in `derive({})`", name,),
+ );
+ };
+ let call = subcall(cx, field);
+ cx.field_imm(field.span, ident, call)
+ })
+ .collect::<Vec<_>>();
+
+ cx.expr_struct(trait_span, ctor_path, fields)
+ }
+ VariantData::Tuple(..) => {
+ let subcalls = all_fields.iter().map(|f| subcall(cx, f)).collect();
+ let path = cx.expr_path(ctor_path);
+ cx.expr_call(trait_span, path, subcalls)
+ }
+ VariantData::Unit(..) => cx.expr_path(ctor_path),
+ };
+ BlockOrExpr::new_expr(expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
new file mode 100644
index 000000000..4e798bf6a
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
@@ -0,0 +1,90 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::{self as ast, MetaItem};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+
+pub fn expand_deriving_eq(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let span = cx.with_def_site_ctxt(span);
+ let inline = cx.meta_word(span, sym::inline);
+ let hidden = rustc_ast::attr::mk_nested_word_item(Ident::new(sym::hidden, span));
+ let doc = rustc_ast::attr::mk_list_item(Ident::new(sym::doc, span), vec![hidden]);
+ let no_coverage = cx.meta_word(span, sym::no_coverage);
+ let attrs = vec![cx.attribute(inline), cx.attribute(doc), cx.attribute(no_coverage)];
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: path_std!(cmp::Eq),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: true,
+ methods: vec![MethodDef {
+ name: sym::assert_receiver_is_total_eq,
+ generics: Bounds::empty(),
+ explicit_self: true,
+ nonself_args: vec![],
+ ret_ty: Unit,
+ attributes: attrs,
+ unify_fieldless_variants: true,
+ combine_substructure: combine_substructure(Box::new(|a, b, c| {
+ cs_total_eq_assert(a, b, c)
+ })),
+ }],
+ associated_types: Vec::new(),
+ };
+
+ super::inject_impl_of_structural_trait(cx, span, item, path_std!(marker::StructuralEq), push);
+
+ trait_def.expand_ext(cx, mitem, item, push, true)
+}
+
+fn cs_total_eq_assert(
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substr: &Substructure<'_>,
+) -> BlockOrExpr {
+ let mut stmts = Vec::new();
+ let mut seen_type_names = FxHashSet::default();
+ let mut process_variant = |variant: &ast::VariantData| {
+ for field in variant.fields() {
+ // This basic redundancy checking only prevents duplication of
+ // assertions like `AssertParamIsEq<Foo>` where the type is a
+ // simple name. That's enough to get a lot of cases, though.
+ if let Some(name) = field.ty.kind.is_simple_path() && !seen_type_names.insert(name) {
+ // Already produced an assertion for this type.
+ } else {
+ // let _: AssertParamIsEq<FieldTy>;
+ super::assert_ty_bounds(
+ cx,
+ &mut stmts,
+ field.ty.clone(),
+ field.span,
+ &[sym::cmp, sym::AssertParamIsEq],
+ );
+ }
+ }
+ };
+
+ match *substr.fields {
+ StaticStruct(vdata, ..) => {
+ process_variant(vdata);
+ }
+ StaticEnum(enum_def, ..) => {
+ for variant in &enum_def.variants {
+ process_variant(&variant.data);
+ }
+ }
+ _ => cx.span_bug(trait_span, "unexpected substructure in `derive(Eq)`"),
+ }
+ BlockOrExpr::new_stmts(stmts)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
new file mode 100644
index 000000000..1612be862
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
@@ -0,0 +1,79 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::MetaItem;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+
+pub fn expand_deriving_ord(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let inline = cx.meta_word(span, sym::inline);
+ let attrs = vec![cx.attribute(inline)];
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: path_std!(cmp::Ord),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods: vec![MethodDef {
+ name: sym::cmp,
+ generics: Bounds::empty(),
+ explicit_self: true,
+ nonself_args: vec![(self_ref(), sym::other)],
+ ret_ty: Path(path_std!(cmp::Ordering)),
+ attributes: attrs,
+ unify_fieldless_variants: true,
+ combine_substructure: combine_substructure(Box::new(|a, b, c| cs_cmp(a, b, c))),
+ }],
+ associated_types: Vec::new(),
+ };
+
+ trait_def.expand(cx, mitem, item, push)
+}
+
+pub fn cs_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+ let test_id = Ident::new(sym::cmp, span);
+ let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
+ let cmp_path = cx.std_path(&[sym::cmp, sym::Ord, sym::cmp]);
+
+ // Builds:
+ //
+ // match ::core::cmp::Ord::cmp(&self.x, &other.x) {
+ // ::std::cmp::Ordering::Equal =>
+ // ::core::cmp::Ord::cmp(&self.y, &other.y),
+ // cmp => cmp,
+ // }
+ let expr = cs_fold(
+ // foldr nests the if-elses correctly, leaving the first field
+ // as the outermost one, and the last as the innermost.
+ false,
+ cx,
+ span,
+ substr,
+ |cx, fold| match fold {
+ CsFold::Single(field) => {
+ let [other_expr] = &field.other_selflike_exprs[..] else {
+ cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`");
+ };
+ let args = vec![field.self_expr.clone(), other_expr.clone()];
+ cx.expr_call_global(field.span, cmp_path.clone(), args)
+ }
+ CsFold::Combine(span, expr1, expr2) => {
+ let eq_arm = cx.arm(span, cx.pat_path(span, equal_path.clone()), expr1);
+ let neq_arm =
+ cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id));
+ cx.expr_match(span, expr2, vec![eq_arm, neq_arm])
+ }
+ CsFold::Fieldless => cx.expr_path(equal_path.clone()),
+ },
+ );
+ BlockOrExpr::new_expr(expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
new file mode 100644
index 000000000..0141b3377
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
@@ -0,0 +1,110 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::{path_local, path_std};
+
+use rustc_ast::ptr::P;
+use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+pub fn expand_deriving_partial_eq(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ fn cs_op(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ substr: &Substructure<'_>,
+ op: BinOpKind,
+ combiner: BinOpKind,
+ base: bool,
+ ) -> BlockOrExpr {
+ let expr = cs_fold(
+ true, // use foldl
+ cx,
+ span,
+ substr,
+ |cx, fold| match fold {
+ CsFold::Single(field) => {
+ let [other_expr] = &field.other_selflike_exprs[..] else {
+ cx.span_bug(field.span, "not exactly 2 arguments in `derive(PartialEq)`");
+ };
+
+ // We received `&T` arguments. Convert them to `T` by
+ // stripping `&` or adding `*`. This isn't necessary for
+ // type checking, but it results in much better error
+ // messages if something goes wrong.
+ let convert = |expr: &P<Expr>| {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) =
+ &expr.kind
+ {
+ inner.clone()
+ } else {
+ cx.expr_deref(field.span, expr.clone())
+ }
+ };
+ cx.expr_binary(field.span, op, convert(&field.self_expr), convert(other_expr))
+ }
+ CsFold::Combine(span, expr1, expr2) => cx.expr_binary(span, combiner, expr1, expr2),
+ CsFold::Fieldless => cx.expr_bool(span, base),
+ },
+ );
+ BlockOrExpr::new_expr(expr)
+ }
+
+ fn cs_eq(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+ cs_op(cx, span, substr, BinOpKind::Eq, BinOpKind::And, true)
+ }
+ fn cs_ne(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+ cs_op(cx, span, substr, BinOpKind::Ne, BinOpKind::Or, false)
+ }
+
+ macro_rules! md {
+ ($name:expr, $f:ident) => {{
+ let inline = cx.meta_word(span, sym::inline);
+ let attrs = vec![cx.attribute(inline)];
+ MethodDef {
+ name: $name,
+ generics: Bounds::empty(),
+ explicit_self: true,
+ nonself_args: vec![(self_ref(), sym::other)],
+ ret_ty: Path(path_local!(bool)),
+ attributes: attrs,
+ unify_fieldless_variants: true,
+ combine_substructure: combine_substructure(Box::new(|a, b, c| $f(a, b, c))),
+ }
+ }};
+ }
+
+ super::inject_impl_of_structural_trait(
+ cx,
+ span,
+ item,
+ path_std!(marker::StructuralPartialEq),
+ push,
+ );
+
+ // avoid defining `ne` if we can
+ // c-like enums, enums without any fields and structs without fields
+ // can safely define only `eq`.
+ let mut methods = vec![md!(sym::eq, cs_eq)];
+ if !is_type_without_fields(item) {
+ methods.push(md!(sym::ne, cs_ne));
+ }
+
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: path_std!(cmp::PartialEq),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods,
+ associated_types: Vec::new(),
+ };
+ trait_def.expand(cx, mitem, item, push)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
new file mode 100644
index 000000000..2ebb01cc8
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
@@ -0,0 +1,88 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::{path_std, pathvec_std};
+
+use rustc_ast::MetaItem;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+
+pub fn expand_deriving_partial_ord(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let ordering_ty = Path(path_std!(cmp::Ordering));
+ let ret_ty =
+ Path(Path::new_(pathvec_std!(option::Option), vec![Box::new(ordering_ty)], PathKind::Std));
+
+ let inline = cx.meta_word(span, sym::inline);
+ let attrs = vec![cx.attribute(inline)];
+
+ let partial_cmp_def = MethodDef {
+ name: sym::partial_cmp,
+ generics: Bounds::empty(),
+ explicit_self: true,
+ nonself_args: vec![(self_ref(), sym::other)],
+ ret_ty,
+ attributes: attrs,
+ unify_fieldless_variants: true,
+ combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
+ cs_partial_cmp(cx, span, substr)
+ })),
+ };
+
+ let trait_def = TraitDef {
+ span,
+ attributes: vec![],
+ path: path_std!(cmp::PartialOrd),
+ additional_bounds: vec![],
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods: vec![partial_cmp_def],
+ associated_types: Vec::new(),
+ };
+ trait_def.expand(cx, mitem, item, push)
+}
+
+pub fn cs_partial_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+ let test_id = Ident::new(sym::cmp, span);
+ let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
+ let partial_cmp_path = cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]);
+
+ // Builds:
+ //
+ // match ::core::cmp::PartialOrd::partial_cmp(&self.x, &other.x) {
+ // ::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
+ // ::core::cmp::PartialOrd::partial_cmp(&self.y, &other.y),
+ // cmp => cmp,
+ // }
+ let expr = cs_fold(
+ // foldr nests the if-elses correctly, leaving the first field
+ // as the outermost one, and the last as the innermost.
+ false,
+ cx,
+ span,
+ substr,
+ |cx, fold| match fold {
+ CsFold::Single(field) => {
+ let [other_expr] = &field.other_selflike_exprs[..] else {
+ cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`");
+ };
+ let args = vec![field.self_expr.clone(), other_expr.clone()];
+ cx.expr_call_global(field.span, partial_cmp_path.clone(), args)
+ }
+ CsFold::Combine(span, expr1, expr2) => {
+ let eq_arm =
+ cx.arm(span, cx.pat_some(span, cx.pat_path(span, equal_path.clone())), expr1);
+ let neq_arm =
+ cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id));
+ cx.expr_match(span, expr2, vec![eq_arm, neq_arm])
+ }
+ CsFold::Fieldless => cx.expr_some(span, cx.expr_path(equal_path.clone())),
+ },
+ );
+ BlockOrExpr::new_expr(expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs
new file mode 100644
index 000000000..ceef893e8
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs
@@ -0,0 +1,181 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::{self as ast, MetaItem};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+
+pub fn expand_deriving_debug(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ // &mut ::std::fmt::Formatter
+ let fmtr = Ref(Box::new(Path(path_std!(fmt::Formatter))), ast::Mutability::Mut);
+
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: path_std!(fmt::Debug),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods: vec![MethodDef {
+ name: sym::fmt,
+ generics: Bounds::empty(),
+ explicit_self: true,
+ nonself_args: vec![(fmtr, sym::f)],
+ ret_ty: Path(path_std!(fmt::Result)),
+ attributes: Vec::new(),
+ unify_fieldless_variants: false,
+ combine_substructure: combine_substructure(Box::new(|a, b, c| {
+ show_substructure(a, b, c)
+ })),
+ }],
+ associated_types: Vec::new(),
+ };
+ trait_def.expand(cx, mitem, item, push)
+}
+
+fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+ let (ident, vdata, fields) = match substr.fields {
+ Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
+ EnumMatching(_, _, v, fields) => (v.ident, &v.data, fields),
+ EnumTag(..) | StaticStruct(..) | StaticEnum(..) => {
+ cx.span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
+ }
+ };
+
+ // We want to make sure we have the ctxt set so that we can use unstable methods
+ let span = cx.with_def_site_ctxt(span);
+ let name = cx.expr_lit(span, ast::LitKind::Str(ident.name, ast::StrStyle::Cooked));
+ let fmt = substr.nonselflike_args[0].clone();
+
+ // Struct and tuples are similar enough that we use the same code for both,
+ // with some extra pieces for structs due to the field names.
+ let (is_struct, args_per_field) = match vdata {
+ ast::VariantData::Unit(..) => {
+ // Special fast path for unit variants.
+ assert!(fields.is_empty());
+ (false, 0)
+ }
+ ast::VariantData::Tuple(..) => (false, 1),
+ ast::VariantData::Struct(..) => (true, 2),
+ };
+
+ // The number of fields that can be handled without an array.
+ const CUTOFF: usize = 5;
+
+ if fields.is_empty() {
+ // Special case for no fields.
+ let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
+ let expr = cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]);
+ BlockOrExpr::new_expr(expr)
+ } else if fields.len() <= CUTOFF {
+ // Few enough fields that we can use a specific-length method.
+ let debug = if is_struct {
+ format!("debug_struct_field{}_finish", fields.len())
+ } else {
+ format!("debug_tuple_field{}_finish", fields.len())
+ };
+ let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
+
+ let mut args = Vec::with_capacity(2 + fields.len() * args_per_field);
+ args.extend([fmt, name]);
+ for i in 0..fields.len() {
+ let field = &fields[i];
+ if is_struct {
+ let name = cx.expr_lit(
+ field.span,
+ ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
+ );
+ args.push(name);
+ }
+ // Use an extra indirection to make sure this works for unsized types.
+ let field = cx.expr_addr_of(field.span, field.self_expr.clone());
+ args.push(field);
+ }
+ let expr = cx.expr_call_global(span, fn_path_debug, args);
+ BlockOrExpr::new_expr(expr)
+ } else {
+ // Enough fields that we must use the any-length method.
+ let mut name_exprs = Vec::with_capacity(fields.len());
+ let mut value_exprs = Vec::with_capacity(fields.len());
+
+ for field in fields {
+ if is_struct {
+ name_exprs.push(cx.expr_lit(
+ field.span,
+ ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
+ ));
+ }
+
+ // Use an extra indirection to make sure this works for unsized types.
+ let field = cx.expr_addr_of(field.span, field.self_expr.clone());
+ value_exprs.push(field);
+ }
+
+ // `let names: &'static _ = &["field1", "field2"];`
+ let names_let = if is_struct {
+ let lt_static = Some(cx.lifetime_static(span));
+ let ty_static_ref =
+ cx.ty_rptr(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
+ Some(cx.stmt_let_ty(
+ span,
+ false,
+ Ident::new(sym::names, span),
+ Some(ty_static_ref),
+ cx.expr_array_ref(span, name_exprs),
+ ))
+ } else {
+ None
+ };
+
+ // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
+ let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
+ let ty_dyn_debug = cx.ty(
+ span,
+ ast::TyKind::TraitObject(vec![cx.trait_bound(path_debug)], ast::TraitObjectSyntax::Dyn),
+ );
+ let ty_slice = cx.ty(
+ span,
+ ast::TyKind::Slice(cx.ty_rptr(span, ty_dyn_debug, None, ast::Mutability::Not)),
+ );
+ let values_let = cx.stmt_let_ty(
+ span,
+ false,
+ Ident::new(sym::values, span),
+ Some(cx.ty_rptr(span, ty_slice, None, ast::Mutability::Not)),
+ cx.expr_array_ref(span, value_exprs),
+ );
+
+ // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
+ // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
+ let sym_debug = if is_struct {
+ sym::debug_struct_fields_finish
+ } else {
+ sym::debug_tuple_fields_finish
+ };
+ let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
+
+ let mut args = Vec::with_capacity(4);
+ args.push(fmt);
+ args.push(name);
+ if is_struct {
+ args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
+ }
+ args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
+ let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
+
+ let mut stmts = Vec::with_capacity(3);
+ if is_struct {
+ stmts.push(names_let.unwrap());
+ }
+ stmts.push(values_let);
+ BlockOrExpr::new_mixed(stmts, Some(expr))
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/decodable.rs b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
new file mode 100644
index 000000000..d688143a2
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
@@ -0,0 +1,224 @@
+//! The compiler code necessary for `#[derive(RustcDecodable)]`. See encodable.rs for more.
+
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::pathvec_std;
+
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Expr, MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+
+pub fn expand_deriving_rustc_decodable(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let krate = sym::rustc_serialize;
+ let typaram = sym::__D;
+
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: Path::new_(vec![krate, sym::Decodable], vec![], PathKind::Global),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods: vec![MethodDef {
+ name: sym::decode,
+ generics: Bounds {
+ bounds: vec![(
+ typaram,
+ vec![Path::new_(vec![krate, sym::Decoder], vec![], PathKind::Global)],
+ )],
+ },
+ explicit_self: false,
+ nonself_args: vec![(
+ Ref(Box::new(Path(Path::new_local(typaram))), Mutability::Mut),
+ sym::d,
+ )],
+ ret_ty: Path(Path::new_(
+ pathvec_std!(result::Result),
+ vec![
+ Box::new(Self_),
+ Box::new(Path(Path::new_(vec![typaram, sym::Error], vec![], PathKind::Local))),
+ ],
+ PathKind::Std,
+ )),
+ attributes: Vec::new(),
+ unify_fieldless_variants: false,
+ combine_substructure: combine_substructure(Box::new(|a, b, c| {
+ decodable_substructure(a, b, c, krate)
+ })),
+ }],
+ associated_types: Vec::new(),
+ };
+
+ trait_def.expand(cx, mitem, item, push)
+}
+
+fn decodable_substructure(
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substr: &Substructure<'_>,
+ krate: Symbol,
+) -> BlockOrExpr {
+ let decoder = substr.nonselflike_args[0].clone();
+ let recurse = vec![
+ Ident::new(krate, trait_span),
+ Ident::new(sym::Decodable, trait_span),
+ Ident::new(sym::decode, trait_span),
+ ];
+ let exprdecode = cx.expr_path(cx.path_global(trait_span, recurse));
+ // throw an underscore in front to suppress unused variable warnings
+ let blkarg = Ident::new(sym::_d, trait_span);
+ let blkdecoder = cx.expr_ident(trait_span, blkarg);
+
+ let expr = match *substr.fields {
+ StaticStruct(_, ref summary) => {
+ let nfields = match *summary {
+ Unnamed(ref fields, _) => fields.len(),
+ Named(ref fields) => fields.len(),
+ };
+ let fn_read_struct_field_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_struct_field]);
+
+ let path = cx.path_ident(trait_span, substr.type_ident);
+ let result =
+ decode_static_fields(cx, trait_span, path, summary, |cx, span, name, field| {
+ cx.expr_try(
+ span,
+ cx.expr_call_global(
+ span,
+ fn_read_struct_field_path.clone(),
+ vec![
+ blkdecoder.clone(),
+ cx.expr_str(span, name),
+ cx.expr_usize(span, field),
+ exprdecode.clone(),
+ ],
+ ),
+ )
+ });
+ let result = cx.expr_ok(trait_span, result);
+ let fn_read_struct_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_struct]);
+
+ cx.expr_call_global(
+ trait_span,
+ fn_read_struct_path,
+ vec![
+ decoder,
+ cx.expr_str(trait_span, substr.type_ident.name),
+ cx.expr_usize(trait_span, nfields),
+ cx.lambda1(trait_span, result, blkarg),
+ ],
+ )
+ }
+ StaticEnum(_, ref fields) => {
+ let variant = Ident::new(sym::i, trait_span);
+
+ let mut arms = Vec::with_capacity(fields.len() + 1);
+ let mut variants = Vec::with_capacity(fields.len());
+
+ let fn_read_enum_variant_arg_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum_variant_arg]);
+
+ for (i, &(ident, v_span, ref parts)) in fields.iter().enumerate() {
+ variants.push(cx.expr_str(v_span, ident.name));
+
+ let path = cx.path(trait_span, vec![substr.type_ident, ident]);
+ let decoded =
+ decode_static_fields(cx, v_span, path, parts, |cx, span, _, field| {
+ let idx = cx.expr_usize(span, field);
+ cx.expr_try(
+ span,
+ cx.expr_call_global(
+ span,
+ fn_read_enum_variant_arg_path.clone(),
+ vec![blkdecoder.clone(), idx, exprdecode.clone()],
+ ),
+ )
+ });
+
+ arms.push(cx.arm(v_span, cx.pat_lit(v_span, cx.expr_usize(v_span, i)), decoded));
+ }
+
+ arms.push(cx.arm_unreachable(trait_span));
+
+ let result = cx.expr_ok(
+ trait_span,
+ cx.expr_match(trait_span, cx.expr_ident(trait_span, variant), arms),
+ );
+ let lambda = cx.lambda(trait_span, vec![blkarg, variant], result);
+ let variant_array_ref = cx.expr_array_ref(trait_span, variants);
+ let fn_read_enum_variant_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum_variant]);
+ let result = cx.expr_call_global(
+ trait_span,
+ fn_read_enum_variant_path,
+ vec![blkdecoder, variant_array_ref, lambda],
+ );
+ let fn_read_enum_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum]);
+
+ cx.expr_call_global(
+ trait_span,
+ fn_read_enum_path,
+ vec![
+ decoder,
+ cx.expr_str(trait_span, substr.type_ident.name),
+ cx.lambda1(trait_span, result, blkarg),
+ ],
+ )
+ }
+ _ => cx.bug("expected StaticEnum or StaticStruct in derive(Decodable)"),
+ };
+ BlockOrExpr::new_expr(expr)
+}
+
+/// Creates a decoder for a single enum variant/struct:
+/// - `outer_pat_path` is the path to this enum variant/struct
+/// - `getarg` should retrieve the `usize`-th field with name `@str`.
+fn decode_static_fields<F>(
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ outer_pat_path: ast::Path,
+ fields: &StaticFields,
+ mut getarg: F,
+) -> P<Expr>
+where
+ F: FnMut(&mut ExtCtxt<'_>, Span, Symbol, usize) -> P<Expr>,
+{
+ match *fields {
+ Unnamed(ref fields, is_tuple) => {
+ let path_expr = cx.expr_path(outer_pat_path);
+ if !is_tuple {
+ path_expr
+ } else {
+ let fields = fields
+ .iter()
+ .enumerate()
+ .map(|(i, &span)| getarg(cx, span, Symbol::intern(&format!("_field{}", i)), i))
+ .collect();
+
+ cx.expr_call(trait_span, path_expr, fields)
+ }
+ }
+ Named(ref fields) => {
+ // use the field's span to get nicer error messages.
+ let fields = fields
+ .iter()
+ .enumerate()
+ .map(|(i, &(ident, span))| {
+ let arg = getarg(cx, span, ident.name, i);
+ cx.field_imm(span, ident, arg)
+ })
+ .collect();
+ cx.expr_struct(trait_span, outer_pat_path, fields)
+ }
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs
new file mode 100644
index 000000000..517769091
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/default.rs
@@ -0,0 +1,267 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+
+use rustc_ast as ast;
+use rustc_ast::walk_list;
+use rustc_ast::EnumDef;
+use rustc_ast::VariantData;
+use rustc_errors::Applicability;
+use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
+use rustc_span::symbol::Ident;
+use rustc_span::symbol::{kw, sym};
+use rustc_span::Span;
+use smallvec::SmallVec;
+
+pub fn expand_deriving_default(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &ast::MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ item.visit_with(&mut DetectNonVariantDefaultAttr { cx });
+
+ let inline = cx.meta_word(span, sym::inline);
+ let attrs = vec![cx.attribute(inline)];
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: Path::new(vec![kw::Default, sym::Default]),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods: vec![MethodDef {
+ name: kw::Default,
+ generics: Bounds::empty(),
+ explicit_self: false,
+ nonself_args: Vec::new(),
+ ret_ty: Self_,
+ attributes: attrs,
+ unify_fieldless_variants: false,
+ combine_substructure: combine_substructure(Box::new(|cx, trait_span, substr| {
+ match substr.fields {
+ StaticStruct(_, fields) => {
+ default_struct_substructure(cx, trait_span, substr, fields)
+ }
+ StaticEnum(enum_def, _) => default_enum_substructure(cx, trait_span, enum_def),
+ _ => cx.span_bug(trait_span, "method in `derive(Default)`"),
+ }
+ })),
+ }],
+ associated_types: Vec::new(),
+ };
+ trait_def.expand(cx, mitem, item, push)
+}
+
+fn default_struct_substructure(
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substr: &Substructure<'_>,
+ summary: &StaticFields,
+) -> BlockOrExpr {
+ // Note that `kw::Default` is "default" and `sym::Default` is "Default"!
+ let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]);
+ let default_call = |span| cx.expr_call_global(span, default_ident.clone(), Vec::new());
+
+ let expr = match summary {
+ Unnamed(ref fields, is_tuple) => {
+ if !is_tuple {
+ cx.expr_ident(trait_span, substr.type_ident)
+ } else {
+ let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
+ cx.expr_call_ident(trait_span, substr.type_ident, exprs)
+ }
+ }
+ Named(ref fields) => {
+ let default_fields = fields
+ .iter()
+ .map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
+ .collect();
+ cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
+ }
+ };
+ BlockOrExpr::new_expr(expr)
+}
+
+fn default_enum_substructure(
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ enum_def: &EnumDef,
+) -> BlockOrExpr {
+ let expr = if let Ok(default_variant) = extract_default_variant(cx, enum_def, trait_span)
+ && let Ok(_) = validate_default_attribute(cx, default_variant)
+ {
+ // We now know there is exactly one unit variant with exactly one `#[default]` attribute.
+ cx.expr_path(cx.path(
+ default_variant.span,
+ vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
+ ))
+ } else {
+ DummyResult::raw_expr(trait_span, true)
+ };
+ BlockOrExpr::new_expr(expr)
+}
+
+fn extract_default_variant<'a>(
+ cx: &mut ExtCtxt<'_>,
+ enum_def: &'a EnumDef,
+ trait_span: Span,
+) -> Result<&'a rustc_ast::Variant, ()> {
+ let default_variants: SmallVec<[_; 1]> = enum_def
+ .variants
+ .iter()
+ .filter(|variant| cx.sess.contains_name(&variant.attrs, kw::Default))
+ .collect();
+
+ let variant = match default_variants.as_slice() {
+ [variant] => variant,
+ [] => {
+ let possible_defaults = enum_def
+ .variants
+ .iter()
+ .filter(|variant| matches!(variant.data, VariantData::Unit(..)))
+ .filter(|variant| !cx.sess.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();
+
+ 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.ident == variant.ident {
+ 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();
+
+ 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();
+
+ 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();
+
+ return Err(());
+ }
+
+ Ok(variant)
+}
+
+fn validate_default_attribute(
+ cx: &mut ExtCtxt<'_>,
+ default_variant: &rustc_ast::Variant,
+) -> Result<(), ()> {
+ let attrs: SmallVec<[_; 1]> =
+ cx.sess.filter_by_name(&default_variant.attrs, kw::Default).collect();
+
+ let attr = match attrs.as_slice() {
+ [attr] => attr,
+ [] => cx.bug(
+ "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::<Vec<_>>(), 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();
+
+ 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();
+
+ return Err(());
+ }
+ Ok(())
+}
+
+struct DetectNonVariantDefaultAttr<'a, 'b> {
+ cx: &'a ExtCtxt<'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();
+ }
+
+ rustc_ast::visit::walk_attribute(self, attr);
+ }
+ fn visit_variant(&mut self, v: &'a rustc_ast::Variant) {
+ self.visit_ident(v.ident);
+ self.visit_vis(&v.vis);
+ self.visit_variant_data(&v.data);
+ walk_list!(self, visit_anon_const, &v.disr_expr);
+ for attr in &v.attrs {
+ rustc_ast::visit::walk_attribute(self, attr);
+ }
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/encodable.rs b/compiler/rustc_builtin_macros/src/deriving/encodable.rs
new file mode 100644
index 000000000..70167cac6
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/encodable.rs
@@ -0,0 +1,295 @@
+//! The compiler code necessary to implement the `#[derive(RustcEncodable)]`
+//! (and `RustcDecodable`, in `decodable.rs`) extension. The idea here is that
+//! type-defining items may be tagged with
+//! `#[derive(RustcEncodable, RustcDecodable)]`.
+//!
+//! For example, a type like:
+//!
+//! ```ignore (old code)
+//! #[derive(RustcEncodable, RustcDecodable)]
+//! struct Node { id: usize }
+//! ```
+//!
+//! would generate two implementations like:
+//!
+//! ```ignore (old code)
+//! # struct Node { id: usize }
+//! impl<S: Encoder<E>, E> Encodable<S, E> for Node {
+//! fn encode(&self, s: &mut S) -> Result<(), E> {
+//! s.emit_struct("Node", 1, |this| {
+//! this.emit_struct_field("id", 0, |this| {
+//! Encodable::encode(&self.id, this)
+//! /* this.emit_usize(self.id) can also be used */
+//! })
+//! })
+//! }
+//! }
+//!
+//! impl<D: Decoder<E>, E> Decodable<D, E> for Node {
+//! fn decode(d: &mut D) -> Result<Node, E> {
+//! d.read_struct("Node", 1, |this| {
+//! match this.read_struct_field("id", 0, |this| Decodable::decode(this)) {
+//! Ok(id) => Ok(Node { id: id }),
+//! Err(e) => Err(e),
+//! }
+//! })
+//! }
+//! }
+//! ```
+//!
+//! Other interesting scenarios are when the item has type parameters or
+//! references other non-built-in types. A type definition like:
+//!
+//! ```ignore (old code)
+//! # #[derive(RustcEncodable, RustcDecodable)]
+//! # struct Span;
+//! #[derive(RustcEncodable, RustcDecodable)]
+//! struct Spanned<T> { node: T, span: Span }
+//! ```
+//!
+//! would yield functions like:
+//!
+//! ```ignore (old code)
+//! # #[derive(RustcEncodable, RustcDecodable)]
+//! # struct Span;
+//! # struct Spanned<T> { node: T, span: Span }
+//! impl<
+//! S: Encoder<E>,
+//! E,
+//! T: Encodable<S, E>
+//! > Encodable<S, E> for Spanned<T> {
+//! fn encode(&self, s: &mut S) -> Result<(), E> {
+//! s.emit_struct("Spanned", 2, |this| {
+//! this.emit_struct_field("node", 0, |this| self.node.encode(this))
+//! .unwrap();
+//! this.emit_struct_field("span", 1, |this| self.span.encode(this))
+//! })
+//! }
+//! }
+//!
+//! impl<
+//! D: Decoder<E>,
+//! E,
+//! T: Decodable<D, E>
+//! > Decodable<D, E> for Spanned<T> {
+//! fn decode(d: &mut D) -> Result<Spanned<T>, E> {
+//! d.read_struct("Spanned", 2, |this| {
+//! Ok(Spanned {
+//! node: this.read_struct_field("node", 0, |this| Decodable::decode(this))
+//! .unwrap(),
+//! span: this.read_struct_field("span", 1, |this| Decodable::decode(this))
+//! .unwrap(),
+//! })
+//! })
+//! }
+//! }
+//! ```
+
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::pathvec_std;
+
+use rustc_ast::{ExprKind, MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+
+pub fn expand_deriving_rustc_encodable(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let krate = sym::rustc_serialize;
+ let typaram = sym::__S;
+
+ let trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path: Path::new_(vec![krate, sym::Encodable], vec![], PathKind::Global),
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods: vec![MethodDef {
+ name: sym::encode,
+ generics: Bounds {
+ bounds: vec![(
+ typaram,
+ vec![Path::new_(vec![krate, sym::Encoder], vec![], PathKind::Global)],
+ )],
+ },
+ explicit_self: true,
+ nonself_args: vec![(
+ Ref(Box::new(Path(Path::new_local(typaram))), Mutability::Mut),
+ sym::s,
+ )],
+ ret_ty: Path(Path::new_(
+ pathvec_std!(result::Result),
+ vec![
+ Box::new(Unit),
+ Box::new(Path(Path::new_(vec![typaram, sym::Error], vec![], PathKind::Local))),
+ ],
+ PathKind::Std,
+ )),
+ attributes: Vec::new(),
+ unify_fieldless_variants: false,
+ combine_substructure: combine_substructure(Box::new(|a, b, c| {
+ encodable_substructure(a, b, c, krate)
+ })),
+ }],
+ associated_types: Vec::new(),
+ };
+
+ trait_def.expand(cx, mitem, item, push)
+}
+
+fn encodable_substructure(
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substr: &Substructure<'_>,
+ krate: Symbol,
+) -> BlockOrExpr {
+ let encoder = substr.nonselflike_args[0].clone();
+ // throw an underscore in front to suppress unused variable warnings
+ let blkarg = Ident::new(sym::_e, trait_span);
+ let blkencoder = cx.expr_ident(trait_span, blkarg);
+ let fn_path = cx.expr_path(cx.path_global(
+ trait_span,
+ vec![
+ Ident::new(krate, trait_span),
+ Ident::new(sym::Encodable, trait_span),
+ Ident::new(sym::encode, trait_span),
+ ],
+ ));
+
+ match *substr.fields {
+ Struct(_, ref fields) => {
+ let fn_emit_struct_field_path =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_struct_field]);
+ let mut stmts = Vec::new();
+ for (i, &FieldInfo { name, ref self_expr, span, .. }) in fields.iter().enumerate() {
+ let name = match name {
+ Some(id) => id.name,
+ None => Symbol::intern(&format!("_field{}", i)),
+ };
+ let self_ref = cx.expr_addr_of(span, self_expr.clone());
+ let enc = cx.expr_call(span, fn_path.clone(), vec![self_ref, blkencoder.clone()]);
+ let lambda = cx.lambda1(span, enc, blkarg);
+ let call = cx.expr_call_global(
+ span,
+ fn_emit_struct_field_path.clone(),
+ vec![
+ blkencoder.clone(),
+ cx.expr_str(span, name),
+ cx.expr_usize(span, i),
+ lambda,
+ ],
+ );
+
+ // last call doesn't need a try!
+ let last = fields.len() - 1;
+ let call = if i != last {
+ cx.expr_try(span, call)
+ } else {
+ cx.expr(span, ExprKind::Ret(Some(call)))
+ };
+
+ let stmt = cx.stmt_expr(call);
+ stmts.push(stmt);
+ }
+
+ // unit structs have no fields and need to return Ok()
+ let blk = if stmts.is_empty() {
+ let ok = cx.expr_ok(trait_span, cx.expr_tuple(trait_span, vec![]));
+ cx.lambda1(trait_span, ok, blkarg)
+ } else {
+ cx.lambda_stmts_1(trait_span, stmts, blkarg)
+ };
+
+ let fn_emit_struct_path =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_struct]);
+
+ let expr = cx.expr_call_global(
+ trait_span,
+ fn_emit_struct_path,
+ vec![
+ encoder,
+ cx.expr_str(trait_span, substr.type_ident.name),
+ cx.expr_usize(trait_span, fields.len()),
+ blk,
+ ],
+ );
+ BlockOrExpr::new_expr(expr)
+ }
+
+ EnumMatching(idx, _, variant, ref fields) => {
+ // We're not generating an AST that the borrow checker is expecting,
+ // so we need to generate a unique local variable to take the
+ // mutable loan out on, otherwise we get conflicts which don't
+ // actually exist.
+ let me = cx.stmt_let(trait_span, false, blkarg, encoder);
+ let encoder = cx.expr_ident(trait_span, blkarg);
+
+ let fn_emit_enum_variant_arg_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_enum_variant_arg]);
+
+ let mut stmts = Vec::new();
+ if !fields.is_empty() {
+ let last = fields.len() - 1;
+ for (i, &FieldInfo { ref self_expr, span, .. }) in fields.iter().enumerate() {
+ let self_ref = cx.expr_addr_of(span, self_expr.clone());
+ let enc =
+ cx.expr_call(span, fn_path.clone(), vec![self_ref, blkencoder.clone()]);
+ let lambda = cx.lambda1(span, enc, blkarg);
+
+ let call = cx.expr_call_global(
+ span,
+ fn_emit_enum_variant_arg_path.clone(),
+ vec![blkencoder.clone(), cx.expr_usize(span, i), lambda],
+ );
+ let call = if i != last {
+ cx.expr_try(span, call)
+ } else {
+ cx.expr(span, ExprKind::Ret(Some(call)))
+ };
+ stmts.push(cx.stmt_expr(call));
+ }
+ } else {
+ let ok = cx.expr_ok(trait_span, cx.expr_tuple(trait_span, vec![]));
+ let ret_ok = cx.expr(trait_span, ExprKind::Ret(Some(ok)));
+ stmts.push(cx.stmt_expr(ret_ok));
+ }
+
+ let blk = cx.lambda_stmts_1(trait_span, stmts, blkarg);
+ let name = cx.expr_str(trait_span, variant.ident.name);
+
+ let fn_emit_enum_variant_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_enum_variant]);
+
+ let call = cx.expr_call_global(
+ trait_span,
+ fn_emit_enum_variant_path,
+ vec![
+ blkencoder,
+ name,
+ cx.expr_usize(trait_span, idx),
+ cx.expr_usize(trait_span, fields.len()),
+ blk,
+ ],
+ );
+
+ let blk = cx.lambda1(trait_span, call, blkarg);
+ let fn_emit_enum_path: Vec<_> =
+ cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_enum]);
+ let expr = cx.expr_call_global(
+ trait_span,
+ fn_emit_enum_path,
+ vec![encoder, cx.expr_str(trait_span, substr.type_ident.name), blk],
+ );
+ BlockOrExpr::new_mixed(vec![me], Some(expr))
+ }
+
+ _ => cx.bug("expected Struct or EnumMatching in derive(Encodable)"),
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
new file mode 100644
index 000000000..735017aa5
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -0,0 +1,1655 @@
+//! Some code that abstracts away much of the boilerplate of writing
+//! `derive` instances for traits. Among other things it manages getting
+//! access to the fields of the 4 different sorts of structs and enum
+//! variants, as well as creating the method and impl ast instances.
+//!
+//! Supported features (fairly exhaustive):
+//!
+//! - Methods taking any number of parameters of any type, and returning
+//! any type, other than vectors, bottom and closures.
+//! - Generating `impl`s for types with type parameters and lifetimes
+//! (e.g., `Option<T>`), the parameters are automatically given the
+//! current trait as a bound. (This includes separate type parameters
+//! and lifetimes for methods.)
+//! - Additional bounds on the type parameters (`TraitDef.additional_bounds`)
+//!
+//! The most important thing for implementors is the `Substructure` and
+//! `SubstructureFields` objects. The latter groups 5 possibilities of the
+//! arguments:
+//!
+//! - `Struct`, when `Self` is a struct (including tuple structs, e.g
+//! `struct T(i32, char)`).
+//! - `EnumMatching`, when `Self` is an enum and all the arguments are the
+//! same variant of the enum (e.g., `Some(1)`, `Some(3)` and `Some(4)`)
+//! - `EnumTag` when `Self` is an enum, for comparing the enum tags.
+//! - `StaticEnum` and `StaticStruct` for static methods, where the type
+//! being derived upon is either an enum or struct respectively. (Any
+//! argument with type Self is just grouped among the non-self
+//! arguments.)
+//!
+//! In the first two cases, the values from the corresponding fields in
+//! all the arguments are grouped together.
+//!
+//! The non-static cases have `Option<ident>` in several places associated
+//! with field `expr`s. This represents the name of the field it is
+//! associated with. It is only not `None` when the associated field has
+//! an identifier in the source code. For example, the `x`s in the
+//! following snippet
+//!
+//! ```rust
+//! # #![allow(dead_code)]
+//! struct A { x : i32 }
+//!
+//! struct B(i32);
+//!
+//! enum C {
+//! C0(i32),
+//! C1 { x: i32 }
+//! }
+//! ```
+//!
+//! The `i32`s in `B` and `C0` don't have an identifier, so the
+//! `Option<ident>`s would be `None` for them.
+//!
+//! In the static cases, the structure is summarized, either into the just
+//! spans of the fields or a list of spans and the field idents (for tuple
+//! structs and record structs, respectively), or a list of these, for
+//! enums (one for each variant). For empty struct and empty enum
+//! variants, it is represented as a count of 0.
+//!
+//! # "`cs`" functions
+//!
+//! The `cs_...` functions ("combine substructure") are designed to
+//! make life easier by providing some pre-made recipes for common
+//! threads; mostly calling the function being derived on all the
+//! arguments and then combining them back together in some way (or
+//! letting the user chose that). They are not meant to be the only
+//! way to handle the structures that this code creates.
+//!
+//! # Examples
+//!
+//! The following simplified `PartialEq` is used for in-code examples:
+//!
+//! ```rust
+//! trait PartialEq {
+//! fn eq(&self, other: &Self) -> bool;
+//! }
+//! impl PartialEq for i32 {
+//! fn eq(&self, other: &i32) -> bool {
+//! *self == *other
+//! }
+//! }
+//! ```
+//!
+//! Some examples of the values of `SubstructureFields` follow, using the
+//! above `PartialEq`, `A`, `B` and `C`.
+//!
+//! ## Structs
+//!
+//! When generating the `expr` for the `A` impl, the `SubstructureFields` is
+//!
+//! ```{.text}
+//! Struct(vec![FieldInfo {
+//! span: <span of x>
+//! name: Some(<ident of x>),
+//! self_: <expr for &self.x>,
+//! other: vec![<expr for &other.x]
+//! }])
+//! ```
+//!
+//! For the `B` impl, called with `B(a)` and `B(b)`,
+//!
+//! ```{.text}
+//! Struct(vec![FieldInfo {
+//! span: <span of `i32`>,
+//! name: None,
+//! self_: <expr for &a>
+//! other: vec![<expr for &b>]
+//! }])
+//! ```
+//!
+//! ## Enums
+//!
+//! When generating the `expr` for a call with `self == C0(a)` and `other
+//! == C0(b)`, the SubstructureFields is
+//!
+//! ```{.text}
+//! EnumMatching(0, <ast::Variant for C0>,
+//! vec![FieldInfo {
+//! span: <span of i32>
+//! name: None,
+//! self_: <expr for &a>,
+//! other: vec![<expr for &b>]
+//! }])
+//! ```
+//!
+//! For `C1 {x}` and `C1 {x}`,
+//!
+//! ```{.text}
+//! EnumMatching(1, <ast::Variant for C1>,
+//! vec![FieldInfo {
+//! span: <span of x>
+//! name: Some(<ident of x>),
+//! self_: <expr for &self.x>,
+//! other: vec![<expr for &other.x>]
+//! }])
+//! ```
+//!
+//! For the tags,
+//!
+//! ```{.text}
+//! EnumTag(
+//! &[<ident of self tag>, <ident of other tag>], <expr to combine with>)
+//! ```
+//! Note that this setup doesn't allow for the brute-force "match every variant
+//! against every other variant" approach, which is bad because it produces a
+//! quadratic amount of code (see #15375).
+//!
+//! ## Static
+//!
+//! A static method on the types above would result in,
+//!
+//! ```{.text}
+//! StaticStruct(<ast::VariantData of A>, Named(vec![(<ident of x>, <span of x>)]))
+//!
+//! StaticStruct(<ast::VariantData of B>, Unnamed(vec![<span of x>]))
+//!
+//! StaticEnum(<ast::EnumDef of C>,
+//! vec![(<ident of C0>, <span of C0>, Unnamed(vec![<span of i32>])),
+//! (<ident of C1>, <span of C1>, Named(vec![(<ident of x>, <span of x>)]))])
+//! ```
+
+pub use StaticFields::*;
+pub use SubstructureFields::*;
+
+use std::cell::RefCell;
+use std::iter;
+use std::vec;
+
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, EnumDef, Expr, Generics, PatKind};
+use rustc_ast::{GenericArg, GenericParamKind, VariantData};
+use rustc_attr as attr;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::Span;
+
+use ty::{Bounds, Path, Ref, Self_, Ty};
+
+use crate::deriving;
+
+pub mod ty;
+
+pub struct TraitDef<'a> {
+ /// The span for the current #[derive(Foo)] header.
+ pub span: Span,
+
+ pub attributes: Vec<ast::Attribute>,
+
+ /// Path of the trait, including any type parameters
+ pub path: Path,
+
+ /// Additional bounds required of any type parameters of the type,
+ /// other than the current trait
+ pub additional_bounds: Vec<Ty>,
+
+ /// Any extra lifetimes and/or bounds, e.g., `D: serialize::Decoder`
+ pub generics: Bounds,
+
+ /// Can this trait be derived for unions?
+ pub supports_unions: bool,
+
+ pub methods: Vec<MethodDef<'a>>,
+
+ pub associated_types: Vec<(Ident, Ty)>,
+}
+
+pub struct MethodDef<'a> {
+ /// name of the method
+ pub name: Symbol,
+ /// List of generics, e.g., `R: rand::Rng`
+ pub generics: Bounds,
+
+ /// Is there is a `&self` argument? If not, it is a static function.
+ pub explicit_self: bool,
+
+ /// Arguments other than the self argument.
+ pub nonself_args: Vec<(Ty, Symbol)>,
+
+ /// Returns type
+ pub ret_ty: Ty,
+
+ pub attributes: Vec<ast::Attribute>,
+
+ /// Can we combine fieldless variants for enums into a single match arm?
+ /// If true, indicates that the trait operation uses the enum tag in some
+ /// way.
+ pub unify_fieldless_variants: bool,
+
+ pub combine_substructure: RefCell<CombineSubstructureFunc<'a>>,
+}
+
+/// All the data about the data structure/method being derived upon.
+pub struct Substructure<'a> {
+ /// ident of self
+ pub type_ident: Ident,
+ /// Verbatim access to any non-selflike arguments, i.e. arguments that
+ /// don't have type `&Self`.
+ pub nonselflike_args: &'a [P<Expr>],
+ pub fields: &'a SubstructureFields<'a>,
+}
+
+/// Summary of the relevant parts of a struct/enum field.
+pub struct FieldInfo {
+ pub span: Span,
+ /// None for tuple structs/normal enum variants, Some for normal
+ /// structs/struct enum variants.
+ pub name: Option<Ident>,
+ /// The expression corresponding to this field of `self`
+ /// (specifically, a reference to it).
+ pub self_expr: P<Expr>,
+ /// The expressions corresponding to references to this field in
+ /// the other selflike arguments.
+ pub other_selflike_exprs: Vec<P<Expr>>,
+}
+
+/// Fields for a static method
+pub enum StaticFields {
+ /// Tuple and unit structs/enum variants like this.
+ Unnamed(Vec<Span>, bool /*is tuple*/),
+ /// Normal structs/struct variants.
+ Named(Vec<(Ident, Span)>),
+}
+
+/// A summary of the possible sets of fields.
+pub enum SubstructureFields<'a> {
+ /// A non-static method with `Self` is a struct.
+ Struct(&'a ast::VariantData, Vec<FieldInfo>),
+
+ /// Matching variants of the enum: variant index, variant count, ast::Variant,
+ /// fields: the field name is only non-`None` in the case of a struct
+ /// variant.
+ EnumMatching(usize, usize, &'a ast::Variant, Vec<FieldInfo>),
+
+ /// The tag of an enum. The first field is a `FieldInfo` for the tags, as
+ /// if they were fields. The second field is the expression to combine the
+ /// tag expression with; it will be `None` if no match is necessary.
+ EnumTag(FieldInfo, Option<P<Expr>>),
+
+ /// A static method where `Self` is a struct.
+ StaticStruct(&'a ast::VariantData, StaticFields),
+
+ /// A static method where `Self` is an enum.
+ StaticEnum(&'a ast::EnumDef, Vec<(Ident, Span, StaticFields)>),
+}
+
+/// Combine the values of all the fields together. The last argument is
+/// all the fields of all the structures.
+pub type CombineSubstructureFunc<'a> =
+ Box<dyn FnMut(&mut ExtCtxt<'_>, Span, &Substructure<'_>) -> BlockOrExpr + 'a>;
+
+pub fn combine_substructure(
+ f: CombineSubstructureFunc<'_>,
+) -> RefCell<CombineSubstructureFunc<'_>> {
+ RefCell::new(f)
+}
+
+struct TypeParameter {
+ bound_generic_params: Vec<ast::GenericParam>,
+ ty: P<ast::Ty>,
+}
+
+// The code snippets built up for derived code are sometimes used as blocks
+// (e.g. in a function body) and sometimes used as expressions (e.g. in a match
+// arm). This structure avoids committing to either form until necessary,
+// avoiding the insertion of any unnecessary blocks.
+//
+// The statements come before the expression.
+pub struct BlockOrExpr(Vec<ast::Stmt>, Option<P<Expr>>);
+
+impl BlockOrExpr {
+ pub fn new_stmts(stmts: Vec<ast::Stmt>) -> BlockOrExpr {
+ BlockOrExpr(stmts, None)
+ }
+
+ pub fn new_expr(expr: P<Expr>) -> BlockOrExpr {
+ BlockOrExpr(vec![], Some(expr))
+ }
+
+ pub fn new_mixed(stmts: Vec<ast::Stmt>, expr: Option<P<Expr>>) -> BlockOrExpr {
+ BlockOrExpr(stmts, expr)
+ }
+
+ // Converts it into a block.
+ fn into_block(mut self, cx: &ExtCtxt<'_>, span: Span) -> P<ast::Block> {
+ if let Some(expr) = self.1 {
+ self.0.push(cx.stmt_expr(expr));
+ }
+ cx.block(span, self.0)
+ }
+
+ // Converts it into an expression.
+ fn into_expr(self, cx: &ExtCtxt<'_>, span: Span) -> P<Expr> {
+ if self.0.is_empty() {
+ match self.1 {
+ None => cx.expr_block(cx.block(span, vec![])),
+ Some(expr) => expr,
+ }
+ } else if self.0.len() == 1
+ && let ast::StmtKind::Expr(expr) = &self.0[0].kind
+ && self.1.is_none()
+ {
+ // There's only a single statement expression. Pull it out.
+ expr.clone()
+ } else {
+ // Multiple statements and/or expressions.
+ cx.expr_block(self.into_block(cx, span))
+ }
+ }
+}
+
+/// This method helps to extract all the type parameters referenced from a
+/// type. For a type parameter `<T>`, it looks for either a `TyPath` that
+/// is not global and starts with `T`, or a `TyQPath`.
+/// Also include bound generic params from the input type.
+fn find_type_parameters(
+ ty: &ast::Ty,
+ ty_param_names: &[Symbol],
+ cx: &ExtCtxt<'_>,
+) -> Vec<TypeParameter> {
+ use rustc_ast::visit;
+
+ struct Visitor<'a, 'b> {
+ cx: &'a ExtCtxt<'b>,
+ ty_param_names: &'a [Symbol],
+ bound_generic_params_stack: Vec<ast::GenericParam>,
+ type_params: Vec<TypeParameter>,
+ }
+
+ impl<'a, 'b> visit::Visitor<'a> for Visitor<'a, 'b> {
+ fn visit_ty(&mut self, ty: &'a ast::Ty) {
+ if let ast::TyKind::Path(_, ref path) = ty.kind {
+ if let Some(segment) = path.segments.first() {
+ if self.ty_param_names.contains(&segment.ident.name) {
+ self.type_params.push(TypeParameter {
+ bound_generic_params: self.bound_generic_params_stack.clone(),
+ ty: P(ty.clone()),
+ });
+ }
+ }
+ }
+
+ visit::walk_ty(self, ty)
+ }
+
+ // Place bound generic params on a stack, to extract them when a type is encountered.
+ fn visit_poly_trait_ref(
+ &mut self,
+ trait_ref: &'a ast::PolyTraitRef,
+ modifier: &'a ast::TraitBoundModifier,
+ ) {
+ let stack_len = self.bound_generic_params_stack.len();
+ self.bound_generic_params_stack
+ .extend(trait_ref.bound_generic_params.clone().into_iter());
+
+ visit::walk_poly_trait_ref(self, trait_ref, modifier);
+
+ self.bound_generic_params_stack.truncate(stack_len);
+ }
+
+ fn visit_mac_call(&mut self, mac: &ast::MacCall) {
+ self.cx.span_err(mac.span(), "`derive` cannot be used on items with type macros");
+ }
+ }
+
+ let mut visitor = Visitor {
+ cx,
+ ty_param_names,
+ bound_generic_params_stack: Vec::new(),
+ type_params: Vec::new(),
+ };
+ visit::Visitor::visit_ty(&mut visitor, ty);
+
+ visitor.type_params
+}
+
+impl<'a> TraitDef<'a> {
+ pub fn expand(
+ self,
+ cx: &mut ExtCtxt<'_>,
+ mitem: &ast::MetaItem,
+ item: &'a Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+ ) {
+ self.expand_ext(cx, mitem, item, push, false);
+ }
+
+ pub fn expand_ext(
+ self,
+ cx: &mut ExtCtxt<'_>,
+ mitem: &ast::MetaItem,
+ item: &'a Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+ from_scratch: bool,
+ ) {
+ match *item {
+ Annotatable::Item(ref item) => {
+ let is_packed = item.attrs.iter().any(|attr| {
+ for r in attr::find_repr_attrs(&cx.sess, attr) {
+ if let attr::ReprPacked(_) = r {
+ return true;
+ }
+ }
+ false
+ });
+ let has_no_type_params = match item.kind {
+ ast::ItemKind::Struct(_, ref generics)
+ | ast::ItemKind::Enum(_, ref generics)
+ | ast::ItemKind::Union(_, ref generics) => !generics
+ .params
+ .iter()
+ .any(|param| matches!(param.kind, ast::GenericParamKind::Type { .. })),
+ _ => unreachable!(),
+ };
+ let container_id = cx.current_expansion.id.expn_data().parent.expect_local();
+ let always_copy = has_no_type_params && cx.resolver.has_derive_copy(container_id);
+
+ let newitem = match item.kind {
+ ast::ItemKind::Struct(ref struct_def, ref generics) => self.expand_struct_def(
+ cx,
+ &struct_def,
+ item.ident,
+ generics,
+ from_scratch,
+ is_packed,
+ always_copy,
+ ),
+ ast::ItemKind::Enum(ref enum_def, ref generics) => {
+ // We ignore `is_packed`/`always_copy` here, because
+ // `repr(packed)` enums cause an error later on.
+ //
+ // This can only cause further compilation errors
+ // downstream in blatantly illegal code, so it
+ // is fine.
+ self.expand_enum_def(cx, enum_def, item.ident, generics, from_scratch)
+ }
+ ast::ItemKind::Union(ref struct_def, ref generics) => {
+ if self.supports_unions {
+ self.expand_struct_def(
+ cx,
+ &struct_def,
+ item.ident,
+ generics,
+ from_scratch,
+ is_packed,
+ always_copy,
+ )
+ } else {
+ cx.span_err(mitem.span, "this trait cannot be derived for unions");
+ return;
+ }
+ }
+ _ => unreachable!(),
+ };
+ // Keep the lint attributes of the previous item to control how the
+ // generated implementations are linted
+ let mut attrs = newitem.attrs.clone();
+ attrs.extend(
+ item.attrs
+ .iter()
+ .filter(|a| {
+ [
+ sym::allow,
+ sym::warn,
+ sym::deny,
+ sym::forbid,
+ sym::stable,
+ sym::unstable,
+ ]
+ .contains(&a.name_or_empty())
+ })
+ .cloned(),
+ );
+ push(Annotatable::Item(P(ast::Item { attrs, ..(*newitem).clone() })))
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ /// Given that we are deriving a trait `DerivedTrait` for a type like:
+ ///
+ /// ```ignore (only-for-syntax-highlight)
+ /// struct Struct<'a, ..., 'z, A, B: DeclaredTrait, C, ..., Z> where C: WhereTrait {
+ /// a: A,
+ /// b: B::Item,
+ /// b1: <B as DeclaredTrait>::Item,
+ /// c1: <C as WhereTrait>::Item,
+ /// c2: Option<<C as WhereTrait>::Item>,
+ /// ...
+ /// }
+ /// ```
+ ///
+ /// create an impl like:
+ ///
+ /// ```ignore (only-for-syntax-highlight)
+ /// impl<'a, ..., 'z, A, B: DeclaredTrait, C, ... Z> where
+ /// C: WhereTrait,
+ /// A: DerivedTrait + B1 + ... + BN,
+ /// B: DerivedTrait + B1 + ... + BN,
+ /// C: DerivedTrait + B1 + ... + BN,
+ /// B::Item: DerivedTrait + B1 + ... + BN,
+ /// <C as WhereTrait>::Item: DerivedTrait + B1 + ... + BN,
+ /// ...
+ /// {
+ /// ...
+ /// }
+ /// ```
+ ///
+ /// where B1, ..., BN are the bounds given by `bounds_paths`.'. Z is a phantom type, and
+ /// therefore does not get bound by the derived trait.
+ fn create_derived_impl(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ type_ident: Ident,
+ generics: &Generics,
+ field_tys: Vec<P<ast::Ty>>,
+ methods: Vec<P<ast::AssocItem>>,
+ ) -> P<ast::Item> {
+ let trait_path = self.path.to_path(cx, self.span, type_ident, generics);
+
+ // Transform associated types from `deriving::ty::Ty` into `ast::AssocItem`
+ let associated_types = self.associated_types.iter().map(|&(ident, ref type_def)| {
+ P(ast::AssocItem {
+ id: ast::DUMMY_NODE_ID,
+ span: self.span,
+ ident,
+ vis: ast::Visibility {
+ span: self.span.shrink_to_lo(),
+ kind: ast::VisibilityKind::Inherited,
+ tokens: None,
+ },
+ attrs: Vec::new(),
+ kind: ast::AssocItemKind::TyAlias(Box::new(ast::TyAlias {
+ defaultness: ast::Defaultness::Final,
+ generics: Generics::default(),
+ where_clauses: (
+ ast::TyAliasWhereClause::default(),
+ ast::TyAliasWhereClause::default(),
+ ),
+ where_predicates_split: 0,
+ bounds: Vec::new(),
+ ty: Some(type_def.to_ty(cx, self.span, type_ident, generics)),
+ })),
+ tokens: None,
+ })
+ });
+
+ let Generics { mut params, mut where_clause, .. } =
+ self.generics.to_generics(cx, self.span, type_ident, generics);
+ where_clause.span = generics.where_clause.span;
+ let ctxt = self.span.ctxt();
+ let span = generics.span.with_ctxt(ctxt);
+
+ // Create the generic parameters
+ params.extend(generics.params.iter().map(|param| match &param.kind {
+ GenericParamKind::Lifetime { .. } => param.clone(),
+ GenericParamKind::Type { .. } => {
+ // I don't think this can be moved out of the loop, since
+ // a GenericBound requires an ast id
+ let bounds: Vec<_> =
+ // extra restrictions on the generics parameters to the
+ // type being derived upon
+ self.additional_bounds.iter().map(|p| {
+ cx.trait_bound(p.to_path(cx, self.span, type_ident, generics))
+ }).chain(
+ // require the current trait
+ iter::once(cx.trait_bound(trait_path.clone()))
+ ).chain(
+ // also add in any bounds from the declaration
+ param.bounds.iter().cloned()
+ ).collect();
+
+ cx.typaram(param.ident.span.with_ctxt(ctxt), param.ident, vec![], bounds, None)
+ }
+ GenericParamKind::Const { ty, kw_span, .. } => {
+ let const_nodefault_kind = GenericParamKind::Const {
+ ty: ty.clone(),
+ kw_span: kw_span.with_ctxt(ctxt),
+
+ // We can't have default values inside impl block
+ default: None,
+ };
+ let mut param_clone = param.clone();
+ param_clone.kind = const_nodefault_kind;
+ param_clone
+ }
+ }));
+
+ // and similarly for where clauses
+ where_clause.predicates.extend(generics.where_clause.predicates.iter().map(|clause| {
+ match clause {
+ ast::WherePredicate::BoundPredicate(wb) => {
+ let span = wb.span.with_ctxt(ctxt);
+ ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
+ span,
+ ..wb.clone()
+ })
+ }
+ ast::WherePredicate::RegionPredicate(wr) => {
+ let span = wr.span.with_ctxt(ctxt);
+ ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate {
+ span,
+ ..wr.clone()
+ })
+ }
+ ast::WherePredicate::EqPredicate(we) => {
+ let span = we.span.with_ctxt(ctxt);
+ ast::WherePredicate::EqPredicate(ast::WhereEqPredicate {
+ id: ast::DUMMY_NODE_ID,
+ span,
+ ..we.clone()
+ })
+ }
+ }
+ }));
+
+ {
+ // Extra scope required here so ty_params goes out of scope before params is moved
+
+ let mut ty_params = params
+ .iter()
+ .filter(|param| matches!(param.kind, ast::GenericParamKind::Type { .. }))
+ .peekable();
+
+ if ty_params.peek().is_some() {
+ let ty_param_names: Vec<Symbol> =
+ ty_params.map(|ty_param| ty_param.ident.name).collect();
+
+ for field_ty in field_tys {
+ let field_ty_params = find_type_parameters(&field_ty, &ty_param_names, cx);
+
+ for field_ty_param in field_ty_params {
+ // if we have already handled this type, skip it
+ if let ast::TyKind::Path(_, ref p) = field_ty_param.ty.kind {
+ if p.segments.len() == 1
+ && ty_param_names.contains(&p.segments[0].ident.name)
+ {
+ continue;
+ };
+ }
+ let mut bounds: Vec<_> = self
+ .additional_bounds
+ .iter()
+ .map(|p| cx.trait_bound(p.to_path(cx, self.span, type_ident, generics)))
+ .collect();
+
+ // require the current trait
+ bounds.push(cx.trait_bound(trait_path.clone()));
+
+ let predicate = ast::WhereBoundPredicate {
+ span: self.span,
+ bound_generic_params: field_ty_param.bound_generic_params,
+ bounded_ty: field_ty_param.ty,
+ bounds,
+ };
+
+ let predicate = ast::WherePredicate::BoundPredicate(predicate);
+ where_clause.predicates.push(predicate);
+ }
+ }
+ }
+ }
+
+ let trait_generics = Generics { params, where_clause, span };
+
+ // Create the reference to the trait.
+ let trait_ref = cx.trait_ref(trait_path);
+
+ let self_params: Vec<_> = generics
+ .params
+ .iter()
+ .map(|param| match param.kind {
+ GenericParamKind::Lifetime { .. } => {
+ GenericArg::Lifetime(cx.lifetime(param.ident.span.with_ctxt(ctxt), param.ident))
+ }
+ GenericParamKind::Type { .. } => {
+ GenericArg::Type(cx.ty_ident(param.ident.span.with_ctxt(ctxt), param.ident))
+ }
+ GenericParamKind::Const { .. } => {
+ GenericArg::Const(cx.const_ident(param.ident.span.with_ctxt(ctxt), param.ident))
+ }
+ })
+ .collect();
+
+ // Create the type of `self`.
+ let path = cx.path_all(self.span, false, vec![type_ident], self_params);
+ let self_type = cx.ty_path(path);
+
+ let attr = cx.attribute(cx.meta_word(self.span, sym::automatically_derived));
+ let opt_trait_ref = Some(trait_ref);
+
+ let mut a = vec![attr];
+ a.extend(self.attributes.iter().cloned());
+
+ cx.item(
+ self.span,
+ Ident::empty(),
+ a,
+ ast::ItemKind::Impl(Box::new(ast::Impl {
+ unsafety: ast::Unsafe::No,
+ polarity: ast::ImplPolarity::Positive,
+ defaultness: ast::Defaultness::Final,
+ constness: ast::Const::No,
+ generics: trait_generics,
+ of_trait: opt_trait_ref,
+ self_ty: self_type,
+ items: methods.into_iter().chain(associated_types).collect(),
+ })),
+ )
+ }
+
+ fn expand_struct_def(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ struct_def: &'a VariantData,
+ type_ident: Ident,
+ generics: &Generics,
+ from_scratch: bool,
+ is_packed: bool,
+ always_copy: bool,
+ ) -> P<ast::Item> {
+ let field_tys: Vec<P<ast::Ty>> =
+ struct_def.fields().iter().map(|field| field.ty.clone()).collect();
+
+ let methods = self
+ .methods
+ .iter()
+ .map(|method_def| {
+ let (explicit_self, selflike_args, nonselflike_args, nonself_arg_tys) =
+ method_def.extract_arg_details(cx, self, type_ident, generics);
+
+ let body = if from_scratch || method_def.is_static() {
+ method_def.expand_static_struct_method_body(
+ cx,
+ self,
+ struct_def,
+ type_ident,
+ &nonselflike_args,
+ )
+ } else {
+ method_def.expand_struct_method_body(
+ cx,
+ self,
+ struct_def,
+ type_ident,
+ &selflike_args,
+ &nonselflike_args,
+ is_packed,
+ always_copy,
+ )
+ };
+
+ method_def.create_method(
+ cx,
+ self,
+ type_ident,
+ generics,
+ explicit_self,
+ nonself_arg_tys,
+ body,
+ )
+ })
+ .collect();
+
+ self.create_derived_impl(cx, type_ident, generics, field_tys, methods)
+ }
+
+ fn expand_enum_def(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ enum_def: &'a EnumDef,
+ type_ident: Ident,
+ generics: &Generics,
+ from_scratch: bool,
+ ) -> P<ast::Item> {
+ let mut field_tys = Vec::new();
+
+ for variant in &enum_def.variants {
+ field_tys.extend(variant.data.fields().iter().map(|field| field.ty.clone()));
+ }
+
+ let methods = self
+ .methods
+ .iter()
+ .map(|method_def| {
+ let (explicit_self, selflike_args, nonselflike_args, nonself_arg_tys) =
+ method_def.extract_arg_details(cx, self, type_ident, generics);
+
+ let body = if from_scratch || method_def.is_static() {
+ method_def.expand_static_enum_method_body(
+ cx,
+ self,
+ enum_def,
+ type_ident,
+ &nonselflike_args,
+ )
+ } else {
+ method_def.expand_enum_method_body(
+ cx,
+ self,
+ enum_def,
+ type_ident,
+ selflike_args,
+ &nonselflike_args,
+ )
+ };
+
+ method_def.create_method(
+ cx,
+ self,
+ type_ident,
+ generics,
+ explicit_self,
+ nonself_arg_tys,
+ body,
+ )
+ })
+ .collect();
+
+ self.create_derived_impl(cx, type_ident, generics, field_tys, methods)
+ }
+}
+
+impl<'a> MethodDef<'a> {
+ fn call_substructure_method(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'_>,
+ type_ident: Ident,
+ nonselflike_args: &[P<Expr>],
+ fields: &SubstructureFields<'_>,
+ ) -> BlockOrExpr {
+ let span = trait_.span;
+ let substructure = Substructure { type_ident, nonselflike_args, fields };
+ let mut f = self.combine_substructure.borrow_mut();
+ let f: &mut CombineSubstructureFunc<'_> = &mut *f;
+ f(cx, span, &substructure)
+ }
+
+ fn get_ret_ty(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'_>,
+ generics: &Generics,
+ type_ident: Ident,
+ ) -> P<ast::Ty> {
+ self.ret_ty.to_ty(cx, trait_.span, type_ident, generics)
+ }
+
+ fn is_static(&self) -> bool {
+ !self.explicit_self
+ }
+
+ // The return value includes:
+ // - explicit_self: The `&self` arg, if present.
+ // - selflike_args: Expressions for `&self` (if present) and also any other
+ // args with the same type (e.g. the `other` arg in `PartialEq::eq`).
+ // - nonselflike_args: Expressions for all the remaining args.
+ // - nonself_arg_tys: Additional information about all the args other than
+ // `&self`.
+ fn extract_arg_details(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'_>,
+ type_ident: Ident,
+ generics: &Generics,
+ ) -> (Option<ast::ExplicitSelf>, Vec<P<Expr>>, Vec<P<Expr>>, Vec<(Ident, P<ast::Ty>)>) {
+ let mut selflike_args = Vec::new();
+ let mut nonselflike_args = Vec::new();
+ let mut nonself_arg_tys = Vec::new();
+ let span = trait_.span;
+
+ let explicit_self = if self.explicit_self {
+ let (self_expr, explicit_self) = ty::get_explicit_self(cx, span);
+ selflike_args.push(self_expr);
+ Some(explicit_self)
+ } else {
+ None
+ };
+
+ for (ty, name) in self.nonself_args.iter() {
+ let ast_ty = ty.to_ty(cx, span, type_ident, generics);
+ let ident = Ident::new(*name, span);
+ nonself_arg_tys.push((ident, ast_ty));
+
+ let arg_expr = cx.expr_ident(span, ident);
+
+ match ty {
+ // Selflike (`&Self`) arguments only occur in non-static methods.
+ Ref(box Self_, _) if !self.is_static() => selflike_args.push(arg_expr),
+ Self_ => cx.span_bug(span, "`Self` in non-return position"),
+ _ => nonselflike_args.push(arg_expr),
+ }
+ }
+
+ (explicit_self, selflike_args, nonselflike_args, nonself_arg_tys)
+ }
+
+ fn create_method(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'_>,
+ type_ident: Ident,
+ generics: &Generics,
+ explicit_self: Option<ast::ExplicitSelf>,
+ nonself_arg_tys: Vec<(Ident, P<ast::Ty>)>,
+ body: BlockOrExpr,
+ ) -> P<ast::AssocItem> {
+ let span = trait_.span;
+ // Create the generics that aren't for `Self`.
+ let fn_generics = self.generics.to_generics(cx, span, type_ident, generics);
+
+ let args = {
+ let self_arg = explicit_self.map(|explicit_self| {
+ let ident = Ident::with_dummy_span(kw::SelfLower).with_span_pos(span);
+ ast::Param::from_self(ast::AttrVec::default(), explicit_self, ident)
+ });
+ let nonself_args =
+ nonself_arg_tys.into_iter().map(|(name, ty)| cx.param(span, name, ty));
+ self_arg.into_iter().chain(nonself_args).collect()
+ };
+
+ let ret_type = self.get_ret_ty(cx, trait_, generics, type_ident);
+
+ let method_ident = Ident::new(self.name, span);
+ let fn_decl = cx.fn_decl(args, ast::FnRetTy::Ty(ret_type));
+ let body_block = body.into_block(cx, span);
+
+ let trait_lo_sp = span.shrink_to_lo();
+
+ let sig = ast::FnSig { header: ast::FnHeader::default(), decl: fn_decl, span };
+ let defaultness = ast::Defaultness::Final;
+
+ // Create the method.
+ P(ast::AssocItem {
+ id: ast::DUMMY_NODE_ID,
+ attrs: self.attributes.clone(),
+ span,
+ vis: ast::Visibility {
+ span: trait_lo_sp,
+ kind: ast::VisibilityKind::Inherited,
+ tokens: None,
+ },
+ ident: method_ident,
+ kind: ast::AssocItemKind::Fn(Box::new(ast::Fn {
+ defaultness,
+ sig,
+ generics: fn_generics,
+ body: Some(body_block),
+ })),
+ tokens: None,
+ })
+ }
+
+ /// The normal case uses field access.
+ /// ```
+ /// #[derive(PartialEq)]
+ /// # struct Dummy;
+ /// struct A { x: u8, y: u8 }
+ ///
+ /// // equivalent to:
+ /// impl PartialEq for A {
+ /// fn eq(&self, other: &A) -> bool {
+ /// self.x == other.x && self.y == other.y
+ /// }
+ /// }
+ /// ```
+ /// But if the struct is `repr(packed)`, we can't use something like
+ /// `&self.x` because that might cause an unaligned ref. So for any trait
+ /// method that takes a reference, if the struct impls `Copy` then we use a
+ /// local block to force a copy:
+ /// ```
+ /// # struct A { x: u8, y: u8 }
+ /// impl PartialEq for A {
+ /// fn eq(&self, other: &A) -> bool {
+ /// // Desugars to `{ self.x }.eq(&{ other.y }) && ...`
+ /// { self.x } == { other.y } && { self.y } == { other.y }
+ /// }
+ /// }
+ /// impl Hash for A {
+ /// fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
+ /// ::core::hash::Hash::hash(&{ self.x }, state);
+ /// ::core::hash::Hash::hash(&{ self.y }, state)
+ /// }
+ /// }
+ /// ```
+ /// If the struct doesn't impl `Copy`, we use let-destructuring with `ref`:
+ /// ```
+ /// # struct A { x: u8, y: u8 }
+ /// impl PartialEq for A {
+ /// fn eq(&self, other: &A) -> bool {
+ /// let Self { x: ref __self_0_0, y: ref __self_0_1 } = *self;
+ /// let Self { x: ref __self_1_0, y: ref __self_1_1 } = *other;
+ /// *__self_0_0 == *__self_1_0 && *__self_0_1 == *__self_1_1
+ /// }
+ /// }
+ /// ```
+ /// This latter case only works if the fields match the alignment required
+ /// by the `packed(N)` attribute. (We'll get errors later on if not.)
+ fn expand_struct_method_body<'b>(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'b>,
+ struct_def: &'b VariantData,
+ type_ident: Ident,
+ selflike_args: &[P<Expr>],
+ nonselflike_args: &[P<Expr>],
+ is_packed: bool,
+ always_copy: bool,
+ ) -> BlockOrExpr {
+ let span = trait_.span;
+ assert!(selflike_args.len() == 1 || selflike_args.len() == 2);
+
+ let mk_body = |cx, selflike_fields| {
+ self.call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &Struct(struct_def, selflike_fields),
+ )
+ };
+
+ if !is_packed {
+ let selflike_fields =
+ trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, false);
+ mk_body(cx, selflike_fields)
+ } else if always_copy {
+ let selflike_fields =
+ trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, true);
+ mk_body(cx, selflike_fields)
+ } else {
+ // Neither packed nor copy. Need to use ref patterns.
+ let prefixes: Vec<_> =
+ (0..selflike_args.len()).map(|i| format!("__self_{}", i)).collect();
+ let addr_of = always_copy;
+ let selflike_fields =
+ trait_.create_struct_pattern_fields(cx, struct_def, &prefixes, addr_of);
+ let mut body = mk_body(cx, selflike_fields);
+
+ let struct_path = cx.path(span, vec![Ident::new(kw::SelfUpper, type_ident.span)]);
+ let use_ref_pat = is_packed && !always_copy;
+ let patterns =
+ trait_.create_struct_patterns(cx, struct_path, struct_def, &prefixes, use_ref_pat);
+
+ // Do the let-destructuring.
+ let mut stmts: Vec<_> = iter::zip(selflike_args, patterns)
+ .map(|(selflike_arg_expr, pat)| {
+ let selflike_arg_expr = cx.expr_deref(span, selflike_arg_expr.clone());
+ cx.stmt_let_pat(span, pat, selflike_arg_expr)
+ })
+ .collect();
+ stmts.extend(std::mem::take(&mut body.0));
+ BlockOrExpr(stmts, body.1)
+ }
+ }
+
+ fn expand_static_struct_method_body(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'_>,
+ struct_def: &VariantData,
+ type_ident: Ident,
+ nonselflike_args: &[P<Expr>],
+ ) -> BlockOrExpr {
+ let summary = trait_.summarise_struct(cx, struct_def);
+
+ self.call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &StaticStruct(struct_def, summary),
+ )
+ }
+
+ /// ```
+ /// #[derive(PartialEq)]
+ /// # struct Dummy;
+ /// enum A {
+ /// A1,
+ /// A2(i32)
+ /// }
+ /// ```
+ /// is equivalent to:
+ /// ```
+ /// impl ::core::cmp::PartialEq for A {
+ /// #[inline]
+ /// fn eq(&self, other: &A) -> bool {
+ /// let __self_tag = ::core::intrinsics::discriminant_value(self);
+ /// let __arg1_tag = ::core::intrinsics::discriminant_value(other);
+ /// __self_tag == __arg1_tag &&
+ /// match (self, other) {
+ /// (A::A2(__self_0), A::A2(__arg1_0)) =>
+ /// *__self_0 == *__arg1_0,
+ /// _ => true,
+ /// }
+ /// }
+ /// }
+ /// ```
+ /// Creates a tag check combined with a match for a tuple of all
+ /// `selflike_args`, with an arm for each variant with fields, possibly an
+ /// arm for each fieldless variant (if `!unify_fieldless_variants` is not
+ /// true), and possibly a default arm.
+ fn expand_enum_method_body<'b>(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'b>,
+ enum_def: &'b EnumDef,
+ type_ident: Ident,
+ selflike_args: Vec<P<Expr>>,
+ nonselflike_args: &[P<Expr>],
+ ) -> BlockOrExpr {
+ let span = trait_.span;
+ let variants = &enum_def.variants;
+
+ // Traits that unify fieldless variants always use the tag(s).
+ let uses_tags = self.unify_fieldless_variants;
+
+ // There is no sensible code to be generated for *any* deriving on a
+ // zero-variant enum. So we just generate a failing expression.
+ if variants.is_empty() {
+ return BlockOrExpr(vec![], Some(deriving::call_unreachable(cx, span)));
+ }
+
+ let prefixes = iter::once("__self".to_string())
+ .chain(
+ selflike_args
+ .iter()
+ .enumerate()
+ .skip(1)
+ .map(|(arg_count, _selflike_arg)| format!("__arg{}", arg_count)),
+ )
+ .collect::<Vec<String>>();
+
+ // Build a series of let statements mapping each selflike_arg
+ // to its discriminant value.
+ //
+ // e.g. for `PartialEq::eq` builds two statements:
+ // ```
+ // let __self_tag = ::core::intrinsics::discriminant_value(self);
+ // let __arg1_tag = ::core::intrinsics::discriminant_value(other);
+ // ```
+ let get_tag_pieces = |cx: &ExtCtxt<'_>| {
+ let tag_idents: Vec<_> = prefixes
+ .iter()
+ .map(|name| Ident::from_str_and_span(&format!("{}_tag", name), span))
+ .collect();
+
+ let mut tag_exprs: Vec<_> = tag_idents
+ .iter()
+ .map(|&ident| cx.expr_addr_of(span, cx.expr_ident(span, ident)))
+ .collect();
+
+ let self_expr = tag_exprs.remove(0);
+ let other_selflike_exprs = tag_exprs;
+ let tag_field = FieldInfo { span, name: None, self_expr, other_selflike_exprs };
+
+ let tag_let_stmts: Vec<_> = iter::zip(&tag_idents, &selflike_args)
+ .map(|(&ident, selflike_arg)| {
+ let variant_value = deriving::call_intrinsic(
+ cx,
+ span,
+ sym::discriminant_value,
+ vec![selflike_arg.clone()],
+ );
+ cx.stmt_let(span, false, ident, variant_value)
+ })
+ .collect();
+
+ (tag_field, tag_let_stmts)
+ };
+
+ // There are some special cases involving fieldless enums where no
+ // match is necessary.
+ let all_fieldless = variants.iter().all(|v| v.data.fields().is_empty());
+ if all_fieldless {
+ if uses_tags && variants.len() > 1 {
+ // If the type is fieldless and the trait uses the tag and
+ // there are multiple variants, we need just an operation on
+ // the tag(s).
+ let (tag_field, mut tag_let_stmts) = get_tag_pieces(cx);
+ let mut tag_check = self.call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &EnumTag(tag_field, None),
+ );
+ tag_let_stmts.append(&mut tag_check.0);
+ return BlockOrExpr(tag_let_stmts, tag_check.1);
+ }
+
+ if variants.len() == 1 {
+ // If there is a single variant, we don't need an operation on
+ // the tag(s). Just use the most degenerate result.
+ return self.call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &EnumMatching(0, 1, &variants[0], Vec::new()),
+ );
+ };
+ }
+
+ // These arms are of the form:
+ // (Variant1, Variant1, ...) => Body1
+ // (Variant2, Variant2, ...) => Body2
+ // ...
+ // where each tuple has length = selflike_args.len()
+ let mut match_arms: Vec<ast::Arm> = variants
+ .iter()
+ .enumerate()
+ .filter(|&(_, v)| !(self.unify_fieldless_variants && v.data.fields().is_empty()))
+ .map(|(index, variant)| {
+ // A single arm has form (&VariantK, &VariantK, ...) => BodyK
+ // (see "Final wrinkle" note below for why.)
+
+ let addr_of = false; // because enums can't be repr(packed)
+ let fields =
+ trait_.create_struct_pattern_fields(cx, &variant.data, &prefixes, addr_of);
+
+ let sp = variant.span.with_ctxt(trait_.span.ctxt());
+ let variant_path = cx.path(sp, vec![type_ident, variant.ident]);
+ let use_ref_pat = false; // because enums can't be repr(packed)
+ let mut subpats: Vec<_> = trait_.create_struct_patterns(
+ cx,
+ variant_path,
+ &variant.data,
+ &prefixes,
+ use_ref_pat,
+ );
+
+ // `(VariantK, VariantK, ...)` or just `VariantK`.
+ let single_pat = if subpats.len() == 1 {
+ subpats.pop().unwrap()
+ } else {
+ cx.pat_tuple(span, subpats)
+ };
+
+ // For the BodyK, we need to delegate to our caller,
+ // passing it an EnumMatching to indicate which case
+ // we are in.
+ //
+ // Now, for some given VariantK, we have built up
+ // expressions for referencing every field of every
+ // Self arg, assuming all are instances of VariantK.
+ // Build up code associated with such a case.
+ let substructure = EnumMatching(index, variants.len(), variant, fields);
+ let arm_expr = self
+ .call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &substructure,
+ )
+ .into_expr(cx, span);
+
+ cx.arm(span, single_pat, arm_expr)
+ })
+ .collect();
+
+ // Add a default arm to the match, if necessary.
+ let first_fieldless = variants.iter().find(|v| v.data.fields().is_empty());
+ let default = match first_fieldless {
+ Some(v) if self.unify_fieldless_variants => {
+ // We need a default case that handles all the fieldless
+ // variants. The index and actual variant aren't meaningful in
+ // this case, so just use dummy values.
+ Some(
+ self.call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &EnumMatching(0, variants.len(), v, Vec::new()),
+ )
+ .into_expr(cx, span),
+ )
+ }
+ _ if variants.len() > 1 && selflike_args.len() > 1 => {
+ // Because we know that all the arguments will match if we reach
+ // the match expression we add the unreachable intrinsics as the
+ // result of the default which should help llvm in optimizing it.
+ Some(deriving::call_unreachable(cx, span))
+ }
+ _ => None,
+ };
+ if let Some(arm) = default {
+ match_arms.push(cx.arm(span, cx.pat_wild(span), arm));
+ }
+
+ // Create a match expression with one arm per discriminant plus
+ // possibly a default arm, e.g.:
+ // match (self, other) {
+ // (Variant1, Variant1, ...) => Body1
+ // (Variant2, Variant2, ...) => Body2,
+ // ...
+ // _ => ::core::intrinsics::unreachable()
+ // }
+ let get_match_expr = |mut selflike_args: Vec<P<Expr>>| {
+ let match_arg = if selflike_args.len() == 1 {
+ selflike_args.pop().unwrap()
+ } else {
+ cx.expr(span, ast::ExprKind::Tup(selflike_args))
+ };
+ cx.expr_match(span, match_arg, match_arms)
+ };
+
+ // If the trait uses the tag and there are multiple variants, we need
+ // to add a tag check operation before the match. Otherwise, the match
+ // is enough.
+ if uses_tags && variants.len() > 1 {
+ let (tag_field, mut tag_let_stmts) = get_tag_pieces(cx);
+
+ // Combine a tag check with the match.
+ let mut tag_check_plus_match = self.call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &EnumTag(tag_field, Some(get_match_expr(selflike_args))),
+ );
+ tag_let_stmts.append(&mut tag_check_plus_match.0);
+ BlockOrExpr(tag_let_stmts, tag_check_plus_match.1)
+ } else {
+ BlockOrExpr(vec![], Some(get_match_expr(selflike_args)))
+ }
+ }
+
+ fn expand_static_enum_method_body(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ trait_: &TraitDef<'_>,
+ enum_def: &EnumDef,
+ type_ident: Ident,
+ nonselflike_args: &[P<Expr>],
+ ) -> BlockOrExpr {
+ let summary = enum_def
+ .variants
+ .iter()
+ .map(|v| {
+ let sp = v.span.with_ctxt(trait_.span.ctxt());
+ let summary = trait_.summarise_struct(cx, &v.data);
+ (v.ident, sp, summary)
+ })
+ .collect();
+ self.call_substructure_method(
+ cx,
+ trait_,
+ type_ident,
+ nonselflike_args,
+ &StaticEnum(enum_def, summary),
+ )
+ }
+}
+
+// general helper methods.
+impl<'a> TraitDef<'a> {
+ fn summarise_struct(&self, cx: &mut ExtCtxt<'_>, struct_def: &VariantData) -> StaticFields {
+ let mut named_idents = Vec::new();
+ let mut just_spans = Vec::new();
+ for field in struct_def.fields() {
+ let sp = field.span.with_ctxt(self.span.ctxt());
+ match field.ident {
+ Some(ident) => named_idents.push((ident, sp)),
+ _ => just_spans.push(sp),
+ }
+ }
+
+ let is_tuple = matches!(struct_def, ast::VariantData::Tuple(..));
+ match (just_spans.is_empty(), named_idents.is_empty()) {
+ (false, false) => {
+ cx.span_bug(self.span, "a struct with named and unnamed fields in generic `derive`")
+ }
+ // named fields
+ (_, false) => Named(named_idents),
+ // unnamed fields
+ (false, _) => Unnamed(just_spans, is_tuple),
+ // empty
+ _ => Named(Vec::new()),
+ }
+ }
+
+ fn create_struct_patterns(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ struct_path: ast::Path,
+ struct_def: &'a VariantData,
+ prefixes: &[String],
+ use_ref_pat: bool,
+ ) -> Vec<P<ast::Pat>> {
+ prefixes
+ .iter()
+ .map(|prefix| {
+ let pieces_iter =
+ struct_def.fields().iter().enumerate().map(|(i, struct_field)| {
+ let sp = struct_field.span.with_ctxt(self.span.ctxt());
+ let binding_mode = if use_ref_pat {
+ ast::BindingMode::ByRef(ast::Mutability::Not)
+ } else {
+ ast::BindingMode::ByValue(ast::Mutability::Not)
+ };
+ let ident = self.mk_pattern_ident(prefix, i);
+ let path = ident.with_span_pos(sp);
+ (
+ sp,
+ struct_field.ident,
+ cx.pat(path.span, PatKind::Ident(binding_mode, path, None)),
+ )
+ });
+
+ let struct_path = struct_path.clone();
+ match *struct_def {
+ VariantData::Struct(..) => {
+ let field_pats = pieces_iter
+ .map(|(sp, ident, pat)| {
+ if ident.is_none() {
+ cx.span_bug(
+ sp,
+ "a braced struct with unnamed fields in `derive`",
+ );
+ }
+ ast::PatField {
+ ident: ident.unwrap(),
+ is_shorthand: false,
+ attrs: ast::AttrVec::new(),
+ id: ast::DUMMY_NODE_ID,
+ span: pat.span.with_ctxt(self.span.ctxt()),
+ pat,
+ is_placeholder: false,
+ }
+ })
+ .collect();
+ cx.pat_struct(self.span, struct_path, field_pats)
+ }
+ VariantData::Tuple(..) => {
+ let subpats = pieces_iter.map(|(_, _, subpat)| subpat).collect();
+ cx.pat_tuple_struct(self.span, struct_path, subpats)
+ }
+ VariantData::Unit(..) => cx.pat_path(self.span, struct_path),
+ }
+ })
+ .collect()
+ }
+
+ fn create_fields<F>(&self, struct_def: &'a VariantData, mk_exprs: F) -> Vec<FieldInfo>
+ where
+ F: Fn(usize, &ast::FieldDef, Span) -> Vec<P<ast::Expr>>,
+ {
+ struct_def
+ .fields()
+ .iter()
+ .enumerate()
+ .map(|(i, struct_field)| {
+ // For this field, get an expr for each selflike_arg. E.g. for
+ // `PartialEq::eq`, one for each of `&self` and `other`.
+ let sp = struct_field.span.with_ctxt(self.span.ctxt());
+ let mut exprs: Vec<_> = mk_exprs(i, struct_field, sp);
+ let self_expr = exprs.remove(0);
+ let other_selflike_exprs = exprs;
+ FieldInfo {
+ span: sp.with_ctxt(self.span.ctxt()),
+ name: struct_field.ident,
+ self_expr,
+ other_selflike_exprs,
+ }
+ })
+ .collect()
+ }
+
+ fn mk_pattern_ident(&self, prefix: &str, i: usize) -> Ident {
+ Ident::from_str_and_span(&format!("{}_{}", prefix, i), self.span)
+ }
+
+ fn create_struct_pattern_fields(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ struct_def: &'a VariantData,
+ prefixes: &[String],
+ addr_of: bool,
+ ) -> Vec<FieldInfo> {
+ self.create_fields(struct_def, |i, _struct_field, sp| {
+ prefixes
+ .iter()
+ .map(|prefix| {
+ let ident = self.mk_pattern_ident(prefix, i);
+ let expr = cx.expr_path(cx.path_ident(sp, ident));
+ if addr_of { cx.expr_addr_of(sp, expr) } else { expr }
+ })
+ .collect()
+ })
+ }
+
+ fn create_struct_field_access_fields(
+ &self,
+ cx: &mut ExtCtxt<'_>,
+ selflike_args: &[P<Expr>],
+ struct_def: &'a VariantData,
+ copy: bool,
+ ) -> Vec<FieldInfo> {
+ self.create_fields(struct_def, |i, struct_field, sp| {
+ selflike_args
+ .iter()
+ .map(|selflike_arg| {
+ // Note: we must use `struct_field.span` rather than `sp` in the
+ // `unwrap_or_else` case otherwise the hygiene is wrong and we get
+ // "field `0` of struct `Point` is private" errors on tuple
+ // structs.
+ let mut field_expr = cx.expr(
+ sp,
+ ast::ExprKind::Field(
+ selflike_arg.clone(),
+ struct_field.ident.unwrap_or_else(|| {
+ Ident::from_str_and_span(&i.to_string(), struct_field.span)
+ }),
+ ),
+ );
+ if copy {
+ field_expr = cx.expr_block(
+ cx.block(struct_field.span, vec![cx.stmt_expr(field_expr)]),
+ );
+ }
+ cx.expr_addr_of(sp, field_expr)
+ })
+ .collect()
+ })
+ }
+}
+
+/// The function passed to `cs_fold` is called repeatedly with a value of this
+/// type. It describes one part of the code generation. The result is always an
+/// expression.
+pub enum CsFold<'a> {
+ /// The basic case: a field expression for one or more selflike args. E.g.
+ /// for `PartialEq::eq` this is something like `self.x == other.x`.
+ Single(&'a FieldInfo),
+
+ /// The combination of two field expressions. E.g. for `PartialEq::eq` this
+ /// is something like `<field1 equality> && <field2 equality>`.
+ Combine(Span, P<Expr>, P<Expr>),
+
+ // The fallback case for a struct or enum variant with no fields.
+ Fieldless,
+}
+
+/// Folds over fields, combining the expressions for each field in a sequence.
+/// Statics may not be folded over.
+pub fn cs_fold<F>(
+ use_foldl: bool,
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substructure: &Substructure<'_>,
+ mut f: F,
+) -> P<Expr>
+where
+ F: FnMut(&mut ExtCtxt<'_>, CsFold<'_>) -> P<Expr>,
+{
+ match substructure.fields {
+ EnumMatching(.., all_fields) | Struct(_, all_fields) => {
+ if all_fields.is_empty() {
+ return f(cx, CsFold::Fieldless);
+ }
+
+ let (base_field, rest) = if use_foldl {
+ all_fields.split_first().unwrap()
+ } else {
+ all_fields.split_last().unwrap()
+ };
+
+ let base_expr = f(cx, CsFold::Single(base_field));
+
+ let op = |old, field: &FieldInfo| {
+ let new = f(cx, CsFold::Single(field));
+ f(cx, CsFold::Combine(field.span, old, new))
+ };
+
+ if use_foldl {
+ rest.iter().fold(base_expr, op)
+ } else {
+ rest.iter().rfold(base_expr, op)
+ }
+ }
+ EnumTag(tag_field, match_expr) => {
+ let tag_check_expr = f(cx, CsFold::Single(tag_field));
+ if let Some(match_expr) = match_expr {
+ if use_foldl {
+ f(cx, CsFold::Combine(trait_span, tag_check_expr, match_expr.clone()))
+ } else {
+ f(cx, CsFold::Combine(trait_span, match_expr.clone(), tag_check_expr))
+ }
+ } else {
+ tag_check_expr
+ }
+ }
+ StaticEnum(..) | StaticStruct(..) => cx.span_bug(trait_span, "static function in `derive`"),
+ }
+}
+
+/// Returns `true` if the type has no value fields
+/// (for an enum, no variant has any fields)
+pub fn is_type_without_fields(item: &Annotatable) -> bool {
+ if let Annotatable::Item(ref item) = *item {
+ match item.kind {
+ ast::ItemKind::Enum(ref enum_def, _) => {
+ enum_def.variants.iter().all(|v| v.data.fields().is_empty())
+ }
+ ast::ItemKind::Struct(ref variant_data, _) => variant_data.fields().is_empty(),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
new file mode 100644
index 000000000..4d46f7cd4
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
@@ -0,0 +1,203 @@
+//! A mini version of ast::Ty, which is easier to use, and features an explicit `Self` type to use
+//! when specifying impls to be derived.
+
+pub use Ty::*;
+
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind};
+use rustc_expand::base::ExtCtxt;
+use rustc_span::source_map::{respan, DUMMY_SP};
+use rustc_span::symbol::{kw, Ident, Symbol};
+use rustc_span::Span;
+
+/// A path, e.g., `::std::option::Option::<i32>` (global). Has support
+/// for type parameters.
+#[derive(Clone)]
+pub struct Path {
+ path: Vec<Symbol>,
+ params: Vec<Box<Ty>>,
+ kind: PathKind,
+}
+
+#[derive(Clone)]
+pub enum PathKind {
+ Local,
+ Global,
+ Std,
+}
+
+impl Path {
+ pub fn new(path: Vec<Symbol>) -> Path {
+ Path::new_(path, Vec::new(), PathKind::Std)
+ }
+ pub fn new_local(path: Symbol) -> Path {
+ Path::new_(vec![path], Vec::new(), PathKind::Local)
+ }
+ pub fn new_(path: Vec<Symbol>, params: Vec<Box<Ty>>, kind: PathKind) -> Path {
+ Path { path, params, kind }
+ }
+
+ pub fn to_ty(
+ &self,
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ self_ty: Ident,
+ self_generics: &Generics,
+ ) -> P<ast::Ty> {
+ cx.ty_path(self.to_path(cx, span, self_ty, self_generics))
+ }
+ pub fn to_path(
+ &self,
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ self_ty: Ident,
+ self_generics: &Generics,
+ ) -> ast::Path {
+ let mut idents = self.path.iter().map(|s| Ident::new(*s, span)).collect();
+ let tys = self.params.iter().map(|t| t.to_ty(cx, span, self_ty, self_generics));
+ let params = tys.map(GenericArg::Type).collect();
+
+ match self.kind {
+ PathKind::Global => cx.path_all(span, true, idents, params),
+ PathKind::Local => cx.path_all(span, false, idents, params),
+ PathKind::Std => {
+ let def_site = cx.with_def_site_ctxt(DUMMY_SP);
+ idents.insert(0, Ident::new(kw::DollarCrate, def_site));
+ cx.path_all(span, false, idents, params)
+ }
+ }
+ }
+}
+
+/// A type. Supports pointers, Self, and literals.
+#[derive(Clone)]
+pub enum Ty {
+ Self_,
+ /// A reference.
+ Ref(Box<Ty>, ast::Mutability),
+ /// `mod::mod::Type<[lifetime], [Params...]>`, including a plain type
+ /// parameter, and things like `i32`
+ Path(Path),
+ /// For () return types.
+ Unit,
+}
+
+pub fn self_ref() -> Ty {
+ Ref(Box::new(Self_), ast::Mutability::Not)
+}
+
+impl Ty {
+ pub fn to_ty(
+ &self,
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ self_ty: Ident,
+ self_generics: &Generics,
+ ) -> P<ast::Ty> {
+ match self {
+ Ref(ty, mutbl) => {
+ let raw_ty = ty.to_ty(cx, span, self_ty, self_generics);
+ cx.ty_rptr(span, raw_ty, None, *mutbl)
+ }
+ Path(p) => p.to_ty(cx, span, self_ty, self_generics),
+ Self_ => cx.ty_path(self.to_path(cx, span, self_ty, self_generics)),
+ Unit => {
+ let ty = ast::TyKind::Tup(vec![]);
+ cx.ty(span, ty)
+ }
+ }
+ }
+
+ pub fn to_path(
+ &self,
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ self_ty: Ident,
+ generics: &Generics,
+ ) -> ast::Path {
+ match *self {
+ Self_ => {
+ let params: Vec<_> = generics
+ .params
+ .iter()
+ .map(|param| match param.kind {
+ GenericParamKind::Lifetime { .. } => {
+ GenericArg::Lifetime(ast::Lifetime { id: param.id, ident: param.ident })
+ }
+ GenericParamKind::Type { .. } => {
+ GenericArg::Type(cx.ty_ident(span, param.ident))
+ }
+ GenericParamKind::Const { .. } => {
+ GenericArg::Const(cx.const_ident(span, param.ident))
+ }
+ })
+ .collect();
+
+ cx.path_all(span, false, vec![self_ty], params)
+ }
+ Path(ref p) => p.to_path(cx, span, self_ty, generics),
+ Ref(..) => cx.span_bug(span, "ref in a path in generic `derive`"),
+ Unit => cx.span_bug(span, "unit in a path in generic `derive`"),
+ }
+ }
+}
+
+fn mk_ty_param(
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ name: Symbol,
+ attrs: &[ast::Attribute],
+ bounds: &[Path],
+ self_ident: Ident,
+ self_generics: &Generics,
+) -> ast::GenericParam {
+ let bounds = bounds
+ .iter()
+ .map(|b| {
+ let path = b.to_path(cx, span, self_ident, self_generics);
+ cx.trait_bound(path)
+ })
+ .collect();
+ cx.typaram(span, Ident::new(name, span), attrs.to_owned(), bounds, None)
+}
+
+/// Bounds on type parameters.
+#[derive(Clone)]
+pub struct Bounds {
+ pub bounds: Vec<(Symbol, Vec<Path>)>,
+}
+
+impl Bounds {
+ pub fn empty() -> Bounds {
+ Bounds { bounds: Vec::new() }
+ }
+ pub fn to_generics(
+ &self,
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ self_ty: Ident,
+ self_generics: &Generics,
+ ) -> Generics {
+ let params = self
+ .bounds
+ .iter()
+ .map(|t| {
+ let (name, ref bounds) = *t;
+ mk_ty_param(cx, span, name, &[], &bounds, self_ty, self_generics)
+ })
+ .collect();
+
+ Generics {
+ params,
+ where_clause: ast::WhereClause { has_where_token: false, predicates: Vec::new(), span },
+ span,
+ }
+ }
+}
+
+pub fn get_explicit_self(cx: &ExtCtxt<'_>, span: Span) -> (P<Expr>, ast::ExplicitSelf) {
+ // This constructs a fresh `self` path.
+ let self_path = cx.expr_self(span);
+ let self_ty = respan(span, SelfKind::Region(None, ast::Mutability::Not));
+ (self_path, self_ty)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/hash.rs b/compiler/rustc_builtin_macros/src/deriving/hash.rs
new file mode 100644
index 000000000..32ae3d344
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/hash.rs
@@ -0,0 +1,80 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::{path_std, pathvec_std};
+
+use rustc_ast::{MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+pub fn expand_deriving_hash(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ mitem: &MetaItem,
+ item: &Annotatable,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let path = Path::new_(pathvec_std!(hash::Hash), vec![], PathKind::Std);
+
+ let typaram = sym::__H;
+
+ let arg = Path::new_local(typaram);
+ let hash_trait_def = TraitDef {
+ span,
+ attributes: Vec::new(),
+ path,
+ additional_bounds: Vec::new(),
+ generics: Bounds::empty(),
+ supports_unions: false,
+ methods: vec![MethodDef {
+ name: sym::hash,
+ generics: Bounds { bounds: vec![(typaram, vec![path_std!(hash::Hasher)])] },
+ explicit_self: true,
+ nonself_args: vec![(Ref(Box::new(Path(arg)), Mutability::Mut), sym::state)],
+ ret_ty: Unit,
+ attributes: vec![],
+ unify_fieldless_variants: true,
+ combine_substructure: combine_substructure(Box::new(|a, b, c| {
+ hash_substructure(a, b, c)
+ })),
+ }],
+ associated_types: Vec::new(),
+ };
+
+ hash_trait_def.expand(cx, mitem, item, push);
+}
+
+fn hash_substructure(
+ cx: &mut ExtCtxt<'_>,
+ trait_span: Span,
+ substr: &Substructure<'_>,
+) -> BlockOrExpr {
+ let [state_expr] = substr.nonselflike_args else {
+ cx.span_bug(trait_span, "incorrect number of arguments in `derive(Hash)`");
+ };
+ let call_hash = |span, expr| {
+ let hash_path = {
+ let strs = cx.std_path(&[sym::hash, sym::Hash, sym::hash]);
+
+ cx.expr_path(cx.path_global(span, strs))
+ };
+ let expr = cx.expr_call(span, hash_path, vec![expr, state_expr.clone()]);
+ cx.stmt_expr(expr)
+ };
+
+ let (stmts, match_expr) = match substr.fields {
+ Struct(_, fields) | EnumMatching(.., fields) => {
+ let stmts =
+ fields.iter().map(|field| call_hash(field.span, field.self_expr.clone())).collect();
+ (stmts, None)
+ }
+ EnumTag(tag_field, match_expr) => {
+ assert!(tag_field.other_selflike_exprs.is_empty());
+ let stmts = vec![call_hash(tag_field.span, tag_field.self_expr.clone())];
+ (stmts, match_expr.clone())
+ }
+ _ => cx.span_bug(trait_span, "impossible substructure in `derive(Hash)`"),
+ };
+
+ BlockOrExpr::new_mixed(stmts, match_expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs
new file mode 100644
index 000000000..c1ca089da
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs
@@ -0,0 +1,208 @@
+//! The compiler code necessary to implement the `#[derive]` extensions.
+
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::{GenericArg, Impl, ItemKind, MetaItem};
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+
+macro path_local($x:ident) {
+ generic::ty::Path::new_local(sym::$x)
+}
+
+macro pathvec_std($($rest:ident)::+) {{
+ vec![ $( sym::$rest ),+ ]
+}}
+
+macro path_std($($x:tt)*) {
+ generic::ty::Path::new( pathvec_std!( $($x)* ) )
+}
+
+pub mod bounds;
+pub mod clone;
+pub mod debug;
+pub mod decodable;
+pub mod default;
+pub mod encodable;
+pub mod hash;
+
+#[path = "cmp/eq.rs"]
+pub mod eq;
+#[path = "cmp/ord.rs"]
+pub mod ord;
+#[path = "cmp/partial_eq.rs"]
+pub mod partial_eq;
+#[path = "cmp/partial_ord.rs"]
+pub mod partial_ord;
+
+pub mod generic;
+
+pub(crate) struct BuiltinDerive(
+ pub(crate) fn(&mut ExtCtxt<'_>, Span, &MetaItem, &Annotatable, &mut dyn FnMut(Annotatable)),
+);
+
+impl MultiItemModifier for BuiltinDerive {
+ fn expand(
+ &self,
+ ecx: &mut ExtCtxt<'_>,
+ span: Span,
+ meta_item: &MetaItem,
+ item: Annotatable,
+ ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
+ // FIXME: Built-in derives often forget to give spans contexts,
+ // so we are doing it here in a centralized way.
+ let span = ecx.with_def_site_ctxt(span);
+ let mut items = Vec::new();
+ match item {
+ Annotatable::Stmt(stmt) => {
+ if let ast::StmtKind::Item(item) = stmt.into_inner().kind {
+ (self.0)(ecx, span, meta_item, &Annotatable::Item(item), &mut |a| {
+ // Cannot use 'ecx.stmt_item' here, because we need to pass 'ecx'
+ // to the function
+ items.push(Annotatable::Stmt(P(ast::Stmt {
+ id: ast::DUMMY_NODE_ID,
+ kind: ast::StmtKind::Item(a.expect_item()),
+ span,
+ })));
+ });
+ } else {
+ unreachable!("should have already errored on non-item statement")
+ }
+ }
+ _ => {
+ (self.0)(ecx, span, meta_item, &item, &mut |a| items.push(a));
+ }
+ }
+ ExpandResult::Ready(items)
+ }
+}
+
+/// Constructs an expression that calls an intrinsic
+fn call_intrinsic(
+ cx: &ExtCtxt<'_>,
+ span: Span,
+ intrinsic: Symbol,
+ args: Vec<P<ast::Expr>>,
+) -> P<ast::Expr> {
+ let span = cx.with_def_site_ctxt(span);
+ let path = cx.std_path(&[sym::intrinsics, intrinsic]);
+ cx.expr_call_global(span, path, args)
+}
+
+/// Constructs an expression that calls the `unreachable` intrinsic.
+fn call_unreachable(cx: &ExtCtxt<'_>, span: Span) -> P<ast::Expr> {
+ let span = cx.with_def_site_ctxt(span);
+ let path = cx.std_path(&[sym::intrinsics, sym::unreachable]);
+ let call = cx.expr_call_global(span, path, vec![]);
+
+ cx.expr_block(P(ast::Block {
+ stmts: vec![cx.stmt_expr(call)],
+ id: ast::DUMMY_NODE_ID,
+ rules: ast::BlockCheckMode::Unsafe(ast::CompilerGenerated),
+ span,
+ tokens: None,
+ could_be_bare_literal: false,
+ }))
+}
+
+// Injects `impl<...> Structural for ItemType<...> { }`. In particular,
+// does *not* add `where T: Structural` for parameters `T` in `...`.
+// (That's the main reason we cannot use TraitDef here.)
+fn inject_impl_of_structural_trait(
+ cx: &mut ExtCtxt<'_>,
+ span: Span,
+ item: &Annotatable,
+ structural_path: generic::ty::Path,
+ push: &mut dyn FnMut(Annotatable),
+) {
+ let Annotatable::Item(ref item) = *item else {
+ unreachable!();
+ };
+
+ let generics = match item.kind {
+ ItemKind::Struct(_, ref generics) | ItemKind::Enum(_, ref generics) => generics,
+ // Do not inject `impl Structural for Union`. (`PartialEq` does not
+ // support unions, so we will see error downstream.)
+ ItemKind::Union(..) => return,
+ _ => unreachable!(),
+ };
+
+ // Create generics param list for where clauses and impl headers
+ let mut generics = generics.clone();
+
+ // Create the type of `self`.
+ //
+ // in addition, remove defaults from generic params (impls cannot have them).
+ let self_params: Vec<_> = generics
+ .params
+ .iter_mut()
+ .map(|param| match &mut param.kind {
+ ast::GenericParamKind::Lifetime => {
+ ast::GenericArg::Lifetime(cx.lifetime(span, param.ident))
+ }
+ ast::GenericParamKind::Type { default } => {
+ *default = None;
+ ast::GenericArg::Type(cx.ty_ident(span, param.ident))
+ }
+ ast::GenericParamKind::Const { ty: _, kw_span: _, default } => {
+ *default = None;
+ ast::GenericArg::Const(cx.const_ident(span, param.ident))
+ }
+ })
+ .collect();
+
+ let type_ident = item.ident;
+
+ let trait_ref = cx.trait_ref(structural_path.to_path(cx, span, type_ident, &generics));
+ let self_type = cx.ty_path(cx.path_all(span, false, vec![type_ident], self_params));
+
+ // It would be nice to also encode constraint `where Self: Eq` (by adding it
+ // onto `generics` cloned above). Unfortunately, that strategy runs afoul of
+ // rust-lang/rust#48214. So we perform that additional check in the compiler
+ // itself, instead of encoding it here.
+
+ // Keep the lint and stability attributes of the original item, to control
+ // how the generated implementation is linted.
+ let mut attrs = Vec::new();
+ attrs.extend(
+ item.attrs
+ .iter()
+ .filter(|a| {
+ [sym::allow, sym::warn, sym::deny, sym::forbid, sym::stable, sym::unstable]
+ .contains(&a.name_or_empty())
+ })
+ .cloned(),
+ );
+
+ let newitem = cx.item(
+ span,
+ Ident::empty(),
+ attrs,
+ ItemKind::Impl(Box::new(Impl {
+ unsafety: ast::Unsafe::No,
+ polarity: ast::ImplPolarity::Positive,
+ defaultness: ast::Defaultness::Final,
+ constness: ast::Const::No,
+ generics,
+ of_trait: Some(trait_ref),
+ self_ty: self_type,
+ items: Vec::new(),
+ })),
+ );
+
+ push(Annotatable::Item(newitem));
+}
+
+fn assert_ty_bounds(
+ cx: &mut ExtCtxt<'_>,
+ stmts: &mut Vec<ast::Stmt>,
+ ty: P<ast::Ty>,
+ span: Span,
+ assert_path: &[Symbol],
+) {
+ // Generate statement `let _: assert_path<ty>;`.
+ let span = cx.with_def_site_ctxt(span);
+ let assert_path = cx.path_all(span, true, cx.std_path(assert_path), vec![GenericArg::Type(ty)]);
+ stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path)));
+}
diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs
new file mode 100644
index 000000000..ea0e768a5
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/edition_panic.rs
@@ -0,0 +1,86 @@
+use rustc_ast::ptr::P;
+use rustc_ast::tokenstream::{DelimSpan, TokenStream};
+use rustc_ast::*;
+use rustc_expand::base::*;
+use rustc_span::edition::Edition;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+// This expands to either
+// - `$crate::panic::panic_2015!(...)` or
+// - `$crate::panic::panic_2021!(...)`
+// depending on the edition.
+//
+// This is used for both std::panic!() and core::panic!().
+//
+// `$crate` will refer to either the `std` or `core` crate depending on which
+// one we're expanding from.
+pub fn expand_panic<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn MacResult + 'cx> {
+ let mac = if use_panic_2021(sp) { sym::panic_2021 } else { sym::panic_2015 };
+ expand(mac, cx, sp, tts)
+}
+
+// This expands to either
+// - `$crate::panic::unreachable_2015!(...)` or
+// - `$crate::panic::unreachable_2021!(...)`
+// depending on the edition.
+pub fn expand_unreachable<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn MacResult + 'cx> {
+ let mac = if use_panic_2021(sp) { sym::unreachable_2021 } else { sym::unreachable_2015 };
+ expand(mac, cx, sp, tts)
+}
+
+fn expand<'cx>(
+ mac: rustc_span::Symbol,
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn MacResult + 'cx> {
+ let sp = cx.with_call_site_ctxt(sp);
+
+ MacEager::expr(
+ cx.expr(
+ sp,
+ ExprKind::MacCall(MacCall {
+ path: Path {
+ span: sp,
+ segments: cx
+ .std_path(&[sym::panic, mac])
+ .into_iter()
+ .map(|ident| PathSegment::from_ident(ident))
+ .collect(),
+ tokens: None,
+ },
+ args: P(MacArgs::Delimited(
+ DelimSpan::from_single(sp),
+ MacDelimiter::Parenthesis,
+ tts,
+ )),
+ prior_type_ascription: None,
+ }),
+ ),
+ )
+}
+
+pub fn use_panic_2021(mut span: Span) -> bool {
+ // To determine the edition, we check the first span up the expansion
+ // stack that does not have #[allow_internal_unstable(edition_panic)].
+ // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.)
+ loop {
+ let expn = span.ctxt().outer_expn_data();
+ if let Some(features) = expn.allow_internal_unstable {
+ if features.iter().any(|&f| f == sym::edition_panic) {
+ span = expn.call_site;
+ continue;
+ }
+ }
+ break expn.edition >= Edition::Edition2021;
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs
new file mode 100644
index 000000000..b8828fa67
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/env.rs
@@ -0,0 +1,91 @@
+// The compiler code necessary to support the env! extension. Eventually this
+// should all get sucked into either the compiler syntax extension plugin
+// interface.
+//
+
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::{self as ast, GenericArg};
+use rustc_expand::base::{self, *};
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::Span;
+
+use std::env;
+
+pub fn expand_option_env<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ let Some(var) = get_single_str_from_tts(cx, sp, tts, "option_env!") else {
+ return DummyResult::any(sp);
+ };
+
+ let sp = cx.with_def_site_ctxt(sp);
+ let value = env::var(var.as_str()).ok().as_deref().map(Symbol::intern);
+ cx.sess.parse_sess.env_depinfo.borrow_mut().insert((var, value));
+ let e = match value {
+ None => {
+ let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp));
+ cx.expr_path(cx.path_all(
+ sp,
+ true,
+ cx.std_path(&[sym::option, sym::Option, sym::None]),
+ vec![GenericArg::Type(cx.ty_rptr(
+ sp,
+ cx.ty_ident(sp, Ident::new(sym::str, sp)),
+ Some(lt),
+ ast::Mutability::Not,
+ ))],
+ ))
+ }
+ Some(value) => cx.expr_call_global(
+ sp,
+ cx.std_path(&[sym::option, sym::Option, sym::Some]),
+ vec![cx.expr_str(sp, value)],
+ ),
+ };
+ MacEager::expr(e)
+}
+
+pub fn expand_env<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ let mut exprs = match get_exprs_from_tts(cx, sp, tts) {
+ Some(ref exprs) if exprs.is_empty() => {
+ cx.span_err(sp, "env! takes 1 or 2 arguments");
+ return DummyResult::any(sp);
+ }
+ None => return DummyResult::any(sp),
+ Some(exprs) => exprs.into_iter(),
+ };
+
+ let Some((var, _style)) = expr_to_string(cx, exprs.next().unwrap(), "expected string literal") else {
+ return DummyResult::any(sp);
+ };
+ let msg = match exprs.next() {
+ None => Symbol::intern(&format!("environment variable `{}` not defined", var)),
+ Some(second) => match expr_to_string(cx, second, "expected string literal") {
+ None => return DummyResult::any(sp),
+ Some((s, _style)) => s,
+ },
+ };
+
+ if exprs.next().is_some() {
+ cx.span_err(sp, "env! takes 1 or 2 arguments");
+ return DummyResult::any(sp);
+ }
+
+ let sp = cx.with_def_site_ctxt(sp);
+ let value = env::var(var.as_str()).ok().as_deref().map(Symbol::intern);
+ cx.sess.parse_sess.env_depinfo.borrow_mut().insert((var, value));
+ let e = match value {
+ None => {
+ cx.span_err(sp, msg.as_str());
+ return DummyResult::any(sp);
+ }
+ Some(value) => cx.expr_str(sp, value),
+ };
+ MacEager::expr(e)
+}
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
new file mode 100644
index 000000000..9eb96ec76
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -0,0 +1,1573 @@
+use ArgumentType::*;
+use Position::*;
+
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::visit::{self, Visitor};
+use rustc_ast::{token, BlockCheckMode, UnsafeSource};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
+use rustc_expand::base::{self, *};
+use rustc_parse_format as parse;
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::{BytePos, InnerSpan, Span};
+use smallvec::SmallVec;
+
+use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
+use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
+use rustc_parse_format::Count;
+use std::borrow::Cow;
+use std::collections::hash_map::Entry;
+
+#[derive(PartialEq)]
+enum ArgumentType {
+ Placeholder(&'static str),
+ Count,
+}
+
+enum Position {
+ Exact(usize),
+ Capture(usize),
+ Named(Symbol, InnerSpan),
+}
+
+/// Indicates how positional named argument (i.e. an named argument which is used by position
+/// instead of by name) is used in format string
+/// * `Arg` is the actual argument to print
+/// * `Width` is width format argument
+/// * `Precision` is precion format argument
+/// Example: `{Arg:Width$.Precision$}
+#[derive(Debug, Eq, PartialEq)]
+enum PositionalNamedArgType {
+ Arg,
+ Width,
+ Precision,
+}
+
+/// Contains information necessary to create a lint for a positional named argument
+#[derive(Debug)]
+struct PositionalNamedArg {
+ ty: PositionalNamedArgType,
+ /// The piece of the using this argument (multiple pieces can use the same argument)
+ cur_piece: usize,
+ /// The InnerSpan for in the string to be replaced with the named argument
+ /// This will be None when the position is implicit
+ inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
+ /// The name to use instead of the position
+ replacement: Symbol,
+ /// The span for the positional named argument (so the lint can point a message to it)
+ positional_named_arg_span: Span,
+ has_formatting: bool,
+}
+
+impl PositionalNamedArg {
+ /// Determines:
+ /// 1) span to be replaced with the name of the named argument and
+ /// 2) span to be underlined for error messages
+ fn get_positional_arg_spans(&self, cx: &Context<'_, '_>) -> (Option<Span>, Option<Span>) {
+ if let Some(inner_span) = &self.inner_span_to_replace {
+ let span =
+ cx.fmtsp.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end });
+ (Some(span), Some(span))
+ } else if self.ty == PositionalNamedArgType::Arg {
+ // In the case of a named argument whose position is implicit, if the argument *has*
+ // formatting, there will not be a span to replace. Instead, we insert the name after
+ // the `{`, which will be the first character of arg_span. If the argument does *not*
+ // have formatting, there may or may not be a span to replace. This is because
+ // whitespace is allowed in arguments without formatting (such as `format!("{ }", 1);`)
+ // but is not allowed in arguments with formatting (an error will be generated in cases
+ // like `format!("{ :1.1}", 1.0f32);`.
+ // For the message span, if there is formatting, we want to use the opening `{` and the
+ // next character, which will the `:` indicating the start of formatting. If there is
+ // not any formatting, we want to underline the entire span.
+ cx.arg_spans.get(self.cur_piece).map_or((None, None), |arg_span| {
+ if self.has_formatting {
+ (
+ Some(arg_span.with_lo(arg_span.lo() + BytePos(1)).shrink_to_lo()),
+ Some(arg_span.with_hi(arg_span.lo() + BytePos(2))),
+ )
+ } else {
+ let replace_start = arg_span.lo() + BytePos(1);
+ let replace_end = arg_span.hi() - BytePos(1);
+ let to_replace = arg_span.with_lo(replace_start).with_hi(replace_end);
+ (Some(to_replace), Some(*arg_span))
+ }
+ })
+ } else {
+ (None, None)
+ }
+ }
+}
+
+/// Encapsulates all the named arguments that have been used positionally
+#[derive(Debug)]
+struct PositionalNamedArgsLint {
+ positional_named_args: Vec<PositionalNamedArg>,
+}
+
+impl PositionalNamedArgsLint {
+ /// For a given positional argument, check if the index is for a named argument.
+ ///
+ /// Since positional arguments are required to come before named arguments, if the positional
+ /// index is greater than or equal to the start of named arguments, we know it's a named
+ /// argument used positionally.
+ ///
+ /// Example:
+ /// println!("{} {} {2}", 0, a=1, b=2);
+ ///
+ /// In this case, the first piece (`{}`) would be ArgumentImplicitlyIs with an index of 0. The
+ /// total number of arguments is 3 and the number of named arguments is 2, so the start of named
+ /// arguments is index 1. Therefore, the index of 0 is okay.
+ ///
+ /// The second piece (`{}`) would be ArgumentImplicitlyIs with an index of 1, which is the start
+ /// of named arguments, and so we should add a lint to use the named argument `a`.
+ ///
+ /// The third piece (`{2}`) would be ArgumentIs with an index of 2, which is greater than the
+ /// start of named arguments, and so we should add a lint to use the named argument `b`.
+ ///
+ /// This same check also works for width and precision formatting when either or both are
+ /// CountIsParam, which contains an index into the arguments.
+ fn maybe_add_positional_named_arg(
+ &mut self,
+ current_positional_arg: usize,
+ total_args_length: usize,
+ format_argument_index: usize,
+ ty: PositionalNamedArgType,
+ cur_piece: usize,
+ inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
+ names: &FxHashMap<Symbol, (usize, Span)>,
+ has_formatting: bool,
+ ) {
+ let start_of_named_args = total_args_length - names.len();
+ if current_positional_arg >= start_of_named_args {
+ self.maybe_push(
+ format_argument_index,
+ ty,
+ cur_piece,
+ inner_span_to_replace,
+ names,
+ has_formatting,
+ )
+ }
+ }
+
+ /// Try constructing a PositionalNamedArg struct and pushing it into the vec of positional
+ /// named arguments. If a named arg associated with `format_argument_index` cannot be found,
+ /// a new item will not be added as the lint cannot be emitted in this case.
+ fn maybe_push(
+ &mut self,
+ format_argument_index: usize,
+ ty: PositionalNamedArgType,
+ cur_piece: usize,
+ inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
+ names: &FxHashMap<Symbol, (usize, Span)>,
+ has_formatting: bool,
+ ) {
+ let named_arg = names
+ .iter()
+ .find(|&(_, &(index, _))| index == format_argument_index)
+ .map(|found| found.clone());
+
+ if let Some((&replacement, &(_, positional_named_arg_span))) = named_arg {
+ // In FormatSpec, `precision_span` starts at the leading `.`, which we want to keep in
+ // the lint suggestion, so increment `start` by 1 when `PositionalArgumentType` is
+ // `Precision`.
+ let inner_span_to_replace = if ty == PositionalNamedArgType::Precision {
+ inner_span_to_replace
+ .map(|is| rustc_parse_format::InnerSpan { start: is.start + 1, end: is.end })
+ } else {
+ inner_span_to_replace
+ };
+ self.positional_named_args.push(PositionalNamedArg {
+ ty,
+ cur_piece,
+ inner_span_to_replace,
+ replacement,
+ positional_named_arg_span,
+ has_formatting,
+ });
+ }
+ }
+}
+
+struct Context<'a, 'b> {
+ ecx: &'a mut ExtCtxt<'b>,
+ /// The macro's call site. References to unstable formatting internals must
+ /// use this span to pass the stability checker.
+ macsp: Span,
+ /// The span of the format string literal.
+ fmtsp: Span,
+
+ /// List of parsed argument expressions.
+ /// Named expressions are resolved early, and are appended to the end of
+ /// argument expressions.
+ ///
+ /// Example showing the various data structures in motion:
+ ///
+ /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"`
+ /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
+ /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
+ /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]`
+ /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]`
+ /// * `names` (in JSON): `{"foo": 2}`
+ args: Vec<P<ast::Expr>>,
+ /// The number of arguments that were added by implicit capturing.
+ num_captured_args: usize,
+ /// Placeholder slot numbers indexed by argument.
+ arg_types: Vec<Vec<usize>>,
+ /// Unique format specs seen for each argument.
+ arg_unique_types: Vec<Vec<ArgumentType>>,
+ /// Map from named arguments to their resolved indices.
+ names: FxHashMap<Symbol, (usize, Span)>,
+
+ /// The latest consecutive literal strings, or empty if there weren't any.
+ literal: String,
+
+ /// Collection of the compiled `rt::Argument` structures
+ pieces: Vec<P<ast::Expr>>,
+ /// Collection of string literals
+ str_pieces: Vec<P<ast::Expr>>,
+ /// Stays `true` if all formatting parameters are default (as in "{}{}").
+ all_pieces_simple: bool,
+
+ /// Mapping between positional argument references and indices into the
+ /// final generated static argument array. We record the starting indices
+ /// corresponding to each positional argument, and number of references
+ /// consumed so far for each argument, to facilitate correct `Position`
+ /// mapping in `build_piece`. In effect this can be seen as a "flattened"
+ /// version of `arg_unique_types`.
+ ///
+ /// Again with the example described above in docstring for `args`:
+ ///
+ /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]`
+ arg_index_map: Vec<Vec<usize>>,
+
+ /// Starting offset of count argument slots.
+ count_args_index_offset: usize,
+
+ /// Count argument slots and tracking data structures.
+ /// Count arguments are separately tracked for de-duplication in case
+ /// multiple references are made to one argument. For example, in this
+ /// format string:
+ ///
+ /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"`
+ /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"`
+ /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"`
+ /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}`
+ /// * `count_args`: `vec![0, 5, 3]`
+ count_args: Vec<usize>,
+ /// Relative slot numbers for count arguments.
+ count_positions: FxHashMap<usize, usize>,
+ /// Number of count slots assigned.
+ count_positions_count: usize,
+
+ /// Current position of the implicit positional arg pointer, as if it
+ /// still existed in this phase of processing.
+ /// Used only for `all_pieces_simple` tracking in `build_piece`.
+ curarg: usize,
+ /// Current piece being evaluated, used for error reporting.
+ curpiece: usize,
+ /// Keep track of invalid references to positional arguments.
+ invalid_refs: Vec<(usize, usize)>,
+ /// Spans of all the formatting arguments, in order.
+ arg_spans: Vec<Span>,
+ /// All the formatting arguments that have formatting flags set, in order for diagnostics.
+ arg_with_formatting: Vec<parse::FormatSpec<'a>>,
+
+ /// Whether this format string came from a string literal, as opposed to a macro.
+ is_literal: bool,
+ unused_names_lint: PositionalNamedArgsLint,
+}
+
+pub struct FormatArg {
+ expr: P<ast::Expr>,
+ named: 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.
+///
+/// If parsing succeeds, the return value is:
+///
+/// ```text
+/// Some((fmtstr, parsed arguments, index map for named arguments))
+/// ```
+fn parse_args<'a>(
+ ecx: &mut ExtCtxt<'a>,
+ sp: Span,
+ tts: TokenStream,
+) -> PResult<'a, (P<ast::Expr>, Vec<FormatArg>, FxHashMap<Symbol, (usize, Span)>)> {
+ let mut args = Vec::<FormatArg>::new();
+ let mut names = FxHashMap::<Symbol, (usize, Span)>::default();
+
+ 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"));
+ }
+
+ 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 mut first = true;
+ let mut named = false;
+
+ while p.token != token::Eof {
+ if !p.eat(&token::Comma) {
+ if first {
+ p.clear_expected_tokens();
+ }
+
+ match p.expect(&token::Comma) {
+ Err(mut err) => {
+ match token::TokenKind::Comma.similar_tokens() {
+ Some(tks) if tks.contains(&p.token.kind) => {
+ // If a similar token is found, then it may be a typo. We
+ // consider it as a comma, and continue parsing.
+ err.emit();
+ p.bump();
+ }
+ // Otherwise stop the parsing and return the error.
+ _ => return Err(err),
+ }
+ }
+ Ok(recovered) => {
+ assert!(recovered);
+ }
+ }
+ }
+ first = false;
+ if p.token == token::Eof {
+ break;
+ } // accept trailing commas
+ match p.token.ident() {
+ Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
+ named = true;
+ p.bump();
+ p.expect(&token::Eq)?;
+ let e = p.parse_expr()?;
+ if let Some((prev, _)) = names.get(&ident.name) {
+ ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", ident))
+ .span_label(args[*prev].expr.span, "previously here")
+ .span_label(e.span, "duplicate argument")
+ .emit();
+ continue;
+ }
+
+ // Resolve names into slots early.
+ // Since all the positional args are already seen at this point
+ // if the input is valid, we can simply append to the positional
+ // args. And remember the names.
+ let slot = args.len();
+ names.insert(ident.name, (slot, ident.span));
+ args.push(FormatArg { expr: e, named: true });
+ }
+ _ => {
+ let e = p.parse_expr()?;
+ if named {
+ let mut err = ecx.struct_span_err(
+ e.span,
+ "positional arguments cannot follow named arguments",
+ );
+ err.span_label(e.span, "positional arguments must be before named arguments");
+ for pos in names.values() {
+ err.span_label(args[pos.0].expr.span, "named argument");
+ }
+ err.emit();
+ }
+ args.push(FormatArg { expr: e, named: false });
+ }
+ }
+ }
+ Ok((fmtstr, args, names))
+}
+
+impl<'a, 'b> Context<'a, 'b> {
+ /// The number of arguments that were explicitly given.
+ fn num_args(&self) -> usize {
+ self.args.len() - self.num_captured_args
+ }
+
+ fn resolve_name_inplace(&mut self, p: &mut parse::Piece<'_>) {
+ // NOTE: the `unwrap_or` branch is needed in case of invalid format
+ // arguments, e.g., `format_args!("{foo}")`.
+ let lookup =
+ |s: &str| self.names.get(&Symbol::intern(s)).unwrap_or(&(0, Span::default())).0;
+
+ match *p {
+ parse::String(_) => {}
+ parse::NextArgument(ref mut arg) => {
+ if let parse::ArgumentNamed(s) = arg.position {
+ arg.position = parse::ArgumentIs(lookup(s));
+ }
+ if let parse::CountIsName(s, _) = arg.format.width {
+ arg.format.width = parse::CountIsParam(lookup(s));
+ }
+ if let parse::CountIsName(s, _) = arg.format.precision {
+ arg.format.precision = parse::CountIsParam(lookup(s));
+ }
+ }
+ }
+ }
+
+ /// Verifies one piece of a parse string, and remembers it if valid.
+ /// All errors are not emitted as fatal so we can continue giving errors
+ /// about this and possibly other format strings.
+ fn verify_piece(&mut self, p: &parse::Piece<'_>) {
+ match *p {
+ parse::String(..) => {}
+ parse::NextArgument(ref arg) => {
+ // width/precision first, if they have implicit positional
+ // parameters it makes more sense to consume them first.
+ self.verify_count(
+ arg.format.width,
+ &arg.format.width_span,
+ PositionalNamedArgType::Width,
+ );
+ self.verify_count(
+ arg.format.precision,
+ &arg.format.precision_span,
+ PositionalNamedArgType::Precision,
+ );
+
+ let has_precision = arg.format.precision != Count::CountImplied;
+ let has_width = arg.format.width != Count::CountImplied;
+
+ // argument second, if it's an implicit positional parameter
+ // it's written second, so it should come after width/precision.
+ let pos = match arg.position {
+ parse::ArgumentIs(i) => {
+ self.unused_names_lint.maybe_add_positional_named_arg(
+ i,
+ self.args.len(),
+ i,
+ PositionalNamedArgType::Arg,
+ self.curpiece,
+ Some(arg.position_span),
+ &self.names,
+ has_precision || has_width,
+ );
+
+ Exact(i)
+ }
+ parse::ArgumentImplicitlyIs(i) => {
+ self.unused_names_lint.maybe_add_positional_named_arg(
+ i,
+ self.args.len(),
+ i,
+ PositionalNamedArgType::Arg,
+ self.curpiece,
+ None,
+ &self.names,
+ has_precision || has_width,
+ );
+ Exact(i)
+ }
+ parse::ArgumentNamed(s) => {
+ let symbol = Symbol::intern(s);
+ let span = arg.position_span;
+ Named(symbol, InnerSpan::new(span.start, span.end))
+ }
+ };
+
+ let ty = Placeholder(match arg.format.ty {
+ "" => "Display",
+ "?" => "Debug",
+ "e" => "LowerExp",
+ "E" => "UpperExp",
+ "o" => "Octal",
+ "p" => "Pointer",
+ "b" => "Binary",
+ "x" => "LowerHex",
+ "X" => "UpperHex",
+ _ => {
+ let fmtsp = self.fmtsp;
+ let sp = arg
+ .format
+ .ty_span
+ .map(|sp| fmtsp.from_inner(InnerSpan::new(sp.start, sp.end)));
+ let mut err = self.ecx.struct_span_err(
+ sp.unwrap_or(fmtsp),
+ &format!("unknown format trait `{}`", arg.format.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 &[
+ ("", "Display"),
+ ("?", "Debug"),
+ ("e", "LowerExp"),
+ ("E", "UpperExp"),
+ ("o", "Octal"),
+ ("p", "Pointer"),
+ ("b", "Binary"),
+ ("x", "LowerHex"),
+ ("X", "UpperHex"),
+ ] {
+ // FIXME: rustfix (`run-rustfix`) fails to apply suggestions.
+ // > "Cannot replace slice of data that was already replaced"
+ err.tool_only_span_suggestion(
+ sp,
+ &format!("use the `{}` trait", name),
+ *fmt,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ err.emit();
+ "<invalid>"
+ }
+ });
+ self.verify_arg_type(pos, ty);
+ self.curpiece += 1;
+ }
+ }
+ }
+
+ fn verify_count(
+ &mut self,
+ c: parse::Count<'_>,
+ inner_span: &Option<rustc_parse_format::InnerSpan>,
+ named_arg_type: PositionalNamedArgType,
+ ) {
+ match c {
+ parse::CountImplied | parse::CountIs(..) => {}
+ parse::CountIsParam(i) => {
+ self.unused_names_lint.maybe_add_positional_named_arg(
+ i,
+ self.args.len(),
+ i,
+ named_arg_type,
+ self.curpiece,
+ *inner_span,
+ &self.names,
+ true,
+ );
+ self.verify_arg_type(Exact(i), Count);
+ }
+ parse::CountIsName(s, span) => {
+ self.verify_arg_type(
+ Named(Symbol::intern(s), InnerSpan::new(span.start, span.end)),
+ Count,
+ );
+ }
+ }
+ }
+
+ fn describe_num_args(&self) -> Cow<'_, str> {
+ match self.num_args() {
+ 0 => "no arguments were given".into(),
+ 1 => "there is 1 argument".into(),
+ x => format!("there are {} arguments", x).into(),
+ }
+ }
+
+ /// Handle invalid references to positional arguments. Output different
+ /// errors for the case where all arguments are positional and for when
+ /// there are named arguments or numbered positional arguments in the
+ /// format string.
+ fn report_invalid_references(&self, numbered_position_args: bool) {
+ let mut e;
+ let sp = if !self.arg_spans.is_empty() {
+ // Point at the formatting arguments.
+ MultiSpan::from_spans(self.arg_spans.clone())
+ } else {
+ MultiSpan::from_span(self.fmtsp)
+ };
+ let refs =
+ self.invalid_refs.iter().map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos)));
+
+ let mut zero_based_note = false;
+
+ let count = self.pieces.len()
+ + self.arg_with_formatting.iter().filter(|fmt| fmt.precision_span.is_some()).count();
+ if self.names.is_empty() && !numbered_position_args && count != self.num_args() {
+ e = self.ecx.struct_span_err(
+ sp,
+ &format!(
+ "{} positional argument{} in format string, but {}",
+ count,
+ pluralize!(count),
+ self.describe_num_args(),
+ ),
+ );
+ for arg in &self.args {
+ // Point at the arguments that will be formatted.
+ e.span_label(arg.span, "");
+ }
+ } else {
+ let (mut refs, spans): (Vec<_>, Vec<_>) = refs.unzip();
+ // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)`
+ // for `println!("{7:7$}", 1);`
+ refs.sort();
+ refs.dedup();
+ let spans: Vec<_> = spans.into_iter().filter_map(|sp| sp.copied()).collect();
+ let sp = if self.arg_spans.is_empty() || spans.is_empty() {
+ MultiSpan::from_span(self.fmtsp)
+ } else {
+ MultiSpan::from_spans(spans)
+ };
+ let arg_list = if refs.len() == 1 {
+ format!("argument {}", refs[0])
+ } else {
+ let reg = refs.pop().unwrap();
+ format!("arguments {head} and {tail}", head = refs.join(", "), tail = reg)
+ };
+
+ e = self.ecx.struct_span_err(
+ sp,
+ &format!(
+ "invalid reference to positional {} ({})",
+ arg_list,
+ self.describe_num_args()
+ ),
+ );
+ zero_based_note = true;
+ };
+
+ for fmt in &self.arg_with_formatting {
+ if let Some(span) = fmt.precision_span {
+ let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end));
+ match fmt.precision {
+ parse::CountIsParam(pos) if pos > self.num_args() => {
+ e.span_label(
+ span,
+ &format!(
+ "this precision flag expects an `usize` argument at position {}, \
+ but {}",
+ pos,
+ self.describe_num_args(),
+ ),
+ );
+ zero_based_note = true;
+ }
+ parse::CountIsParam(pos) => {
+ let count = self.pieces.len()
+ + self
+ .arg_with_formatting
+ .iter()
+ .filter(|fmt| fmt.precision_span.is_some())
+ .count();
+ e.span_label(
+ span,
+ &format!(
+ "this precision flag adds an extra required argument at position {}, \
+ which is why there {} expected",
+ pos,
+ if count == 1 {
+ "is 1 argument".to_string()
+ } else {
+ format!("are {} arguments", count)
+ },
+ ),
+ );
+ if let Some(arg) = self.args.get(pos) {
+ e.span_label(
+ arg.span,
+ "this parameter corresponds to the precision flag",
+ );
+ }
+ zero_based_note = true;
+ }
+ _ => {}
+ }
+ }
+ if let Some(span) = fmt.width_span {
+ let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end));
+ match fmt.width {
+ parse::CountIsParam(pos) if pos >= self.num_args() => {
+ e.span_label(
+ span,
+ &format!(
+ "this width flag expects an `usize` argument at position {}, \
+ but {}",
+ pos,
+ self.describe_num_args(),
+ ),
+ );
+ zero_based_note = true;
+ }
+ _ => {}
+ }
+ }
+ }
+ if zero_based_note {
+ e.note("positional arguments are zero-based");
+ }
+ if !self.arg_with_formatting.is_empty() {
+ e.note(
+ "for information about formatting flags, visit \
+ https://doc.rust-lang.org/std/fmt/index.html",
+ );
+ }
+
+ e.emit();
+ }
+
+ /// Actually verifies and tracks a given format placeholder
+ /// (a.k.a. argument).
+ fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
+ if let Exact(arg) = arg {
+ if arg >= self.num_args() {
+ self.invalid_refs.push((arg, self.curpiece));
+ return;
+ }
+ }
+
+ match arg {
+ Exact(arg) | Capture(arg) => {
+ match ty {
+ Placeholder(_) => {
+ // record every (position, type) combination only once
+ let seen_ty = &mut self.arg_unique_types[arg];
+ let i = seen_ty.iter().position(|x| *x == ty).unwrap_or_else(|| {
+ let i = seen_ty.len();
+ seen_ty.push(ty);
+ i
+ });
+ self.arg_types[arg].push(i);
+ }
+ Count => {
+ if let Entry::Vacant(e) = self.count_positions.entry(arg) {
+ let i = self.count_positions_count;
+ e.insert(i);
+ self.count_args.push(arg);
+ self.count_positions_count += 1;
+ }
+ }
+ }
+ }
+
+ Named(name, span) => {
+ match self.names.get(&name) {
+ Some(&idx) => {
+ // Treat as positional arg.
+ self.verify_arg_type(Capture(idx.0), ty)
+ }
+ None => {
+ // For the moment capturing variables from format strings expanded from macros is
+ // disabled (see RFC #2795)
+ if self.is_literal {
+ // Treat this name as a variable to capture from the surrounding scope
+ let idx = self.args.len();
+ self.arg_types.push(Vec::new());
+ self.arg_unique_types.push(Vec::new());
+ let span = if self.is_literal {
+ self.fmtsp.from_inner(span)
+ } else {
+ self.fmtsp
+ };
+ self.num_captured_args += 1;
+ self.args.push(self.ecx.expr_ident(span, Ident::new(name, span)));
+ self.names.insert(name, (idx, span));
+ self.verify_arg_type(Capture(idx), ty)
+ } else {
+ let msg = format!("there is no argument named `{}`", name);
+ let sp = if self.is_literal {
+ self.fmtsp.from_inner(span)
+ } else {
+ self.fmtsp
+ };
+ let mut err = self.ecx.struct_span_err(sp, &msg);
+
+ err.note(&format!(
+ "did you intend to capture a variable `{}` from \
+ the surrounding scope?",
+ name
+ ));
+ err.note(
+ "to avoid ambiguity, `format_args!` cannot capture variables \
+ when the format string is expanded from a macro",
+ );
+
+ err.emit();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /// Builds the mapping between format placeholders and argument objects.
+ fn build_index_map(&mut self) {
+ // NOTE: Keep the ordering the same as `into_expr`'s expansion would do!
+ let args_len = self.args.len();
+ self.arg_index_map.reserve(args_len);
+
+ let mut sofar = 0usize;
+
+ // Map the arguments
+ for i in 0..args_len {
+ let arg_types = &self.arg_types[i];
+ let arg_offsets = arg_types.iter().map(|offset| sofar + *offset).collect::<Vec<_>>();
+ self.arg_index_map.push(arg_offsets);
+ sofar += self.arg_unique_types[i].len();
+ }
+
+ // Record starting index for counts, which appear just after arguments
+ self.count_args_index_offset = sofar;
+ }
+
+ fn rtpath(ecx: &ExtCtxt<'_>, s: Symbol) -> Vec<Ident> {
+ ecx.std_path(&[sym::fmt, sym::rt, sym::v1, s])
+ }
+
+ fn build_count(&self, c: parse::Count<'_>) -> P<ast::Expr> {
+ let sp = self.macsp;
+ let count = |c, arg| {
+ let mut path = Context::rtpath(self.ecx, sym::Count);
+ path.push(Ident::new(c, sp));
+ match arg {
+ Some(arg) => self.ecx.expr_call_global(sp, path, vec![arg]),
+ None => self.ecx.expr_path(self.ecx.path_global(sp, path)),
+ }
+ };
+ match c {
+ parse::CountIs(i) => count(sym::Is, Some(self.ecx.expr_usize(sp, i))),
+ parse::CountIsParam(i) => {
+ // This needs mapping too, as `i` is referring to a macro
+ // argument. If `i` is not found in `count_positions` then
+ // the error had already been emitted elsewhere.
+ let i = self.count_positions.get(&i).cloned().unwrap_or(0)
+ + self.count_args_index_offset;
+ count(sym::Param, Some(self.ecx.expr_usize(sp, i)))
+ }
+ parse::CountImplied => count(sym::Implied, None),
+ // should never be the case, names are already resolved
+ parse::CountIsName(..) => panic!("should never happen"),
+ }
+ }
+
+ /// Build a literal expression from the accumulated string literals
+ fn build_literal_string(&mut self) -> P<ast::Expr> {
+ let sp = self.fmtsp;
+ let s = Symbol::intern(&self.literal);
+ self.literal.clear();
+ self.ecx.expr_str(sp, s)
+ }
+
+ /// Builds a static `rt::Argument` from a `parse::Piece` or append
+ /// to the `literal` string.
+ fn build_piece(
+ &mut self,
+ piece: &parse::Piece<'a>,
+ arg_index_consumed: &mut Vec<usize>,
+ ) -> Option<P<ast::Expr>> {
+ let sp = self.macsp;
+ match *piece {
+ parse::String(s) => {
+ self.literal.push_str(s);
+ None
+ }
+ parse::NextArgument(ref arg) => {
+ // Build the position
+ let pos = {
+ match arg.position {
+ parse::ArgumentIs(i, ..) | parse::ArgumentImplicitlyIs(i) => {
+ // Map to index in final generated argument array
+ // in case of multiple types specified
+ let arg_idx = match arg_index_consumed.get_mut(i) {
+ None => 0, // error already emitted elsewhere
+ Some(offset) => {
+ let idx_map = &self.arg_index_map[i];
+ // unwrap_or branch: error already emitted elsewhere
+ let arg_idx = *idx_map.get(*offset).unwrap_or(&0);
+ *offset += 1;
+ arg_idx
+ }
+ };
+ self.ecx.expr_usize(sp, arg_idx)
+ }
+
+ // should never be the case, because names are already
+ // resolved.
+ parse::ArgumentNamed(..) => panic!("should never happen"),
+ }
+ };
+
+ let simple_arg = parse::Argument {
+ position: {
+ // We don't have ArgumentNext any more, so we have to
+ // track the current argument ourselves.
+ let i = self.curarg;
+ self.curarg += 1;
+ parse::ArgumentIs(i)
+ },
+ position_span: arg.position_span,
+ format: parse::FormatSpec {
+ fill: arg.format.fill,
+ align: parse::AlignUnknown,
+ flags: 0,
+ precision: parse::CountImplied,
+ precision_span: None,
+ width: parse::CountImplied,
+ width_span: None,
+ ty: arg.format.ty,
+ ty_span: arg.format.ty_span,
+ },
+ };
+
+ let fill = arg.format.fill.unwrap_or(' ');
+
+ let pos_simple = arg.position.index() == simple_arg.position.index();
+
+ if arg.format.precision_span.is_some() || arg.format.width_span.is_some() {
+ self.arg_with_formatting.push(arg.format);
+ }
+ if !pos_simple || arg.format != simple_arg.format || fill != ' ' {
+ self.all_pieces_simple = false;
+ }
+
+ // Build the format
+ let fill = self.ecx.expr_lit(sp, ast::LitKind::Char(fill));
+ let align = |name| {
+ let mut p = Context::rtpath(self.ecx, sym::Alignment);
+ p.push(Ident::new(name, sp));
+ self.ecx.path_global(sp, p)
+ };
+ let align = match arg.format.align {
+ parse::AlignLeft => align(sym::Left),
+ parse::AlignRight => align(sym::Right),
+ parse::AlignCenter => align(sym::Center),
+ parse::AlignUnknown => align(sym::Unknown),
+ };
+ let align = self.ecx.expr_path(align);
+ let flags = self.ecx.expr_u32(sp, arg.format.flags);
+ let prec = self.build_count(arg.format.precision);
+ let width = self.build_count(arg.format.width);
+ let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::FormatSpec));
+ let fmt = self.ecx.expr_struct(
+ sp,
+ path,
+ vec![
+ self.ecx.field_imm(sp, Ident::new(sym::fill, sp), fill),
+ self.ecx.field_imm(sp, Ident::new(sym::align, sp), align),
+ self.ecx.field_imm(sp, Ident::new(sym::flags, sp), flags),
+ self.ecx.field_imm(sp, Ident::new(sym::precision, sp), prec),
+ self.ecx.field_imm(sp, Ident::new(sym::width, sp), width),
+ ],
+ );
+
+ let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::Argument));
+ Some(self.ecx.expr_struct(
+ sp,
+ path,
+ vec![
+ self.ecx.field_imm(sp, Ident::new(sym::position, sp), pos),
+ self.ecx.field_imm(sp, Ident::new(sym::format, sp), fmt),
+ ],
+ ))
+ }
+ }
+ }
+
+ /// Actually builds the expression which the format_args! block will be
+ /// expanded to.
+ fn into_expr(self) -> P<ast::Expr> {
+ let mut original_args = self.args;
+ let mut fmt_args = Vec::with_capacity(
+ self.arg_unique_types.iter().map(|v| v.len()).sum::<usize>() + self.count_args.len(),
+ );
+
+ // First, build up the static array which will become our precompiled
+ // format "string"
+ let pieces = self.ecx.expr_array_ref(self.fmtsp, self.str_pieces);
+
+ // We need to construct a &[ArgumentV1] to pass into the fmt::Arguments
+ // constructor. In general the expressions in this slice might be
+ // permuted from their order in original_args (such as in the case of
+ // "{1} {0}"), or may have multiple entries referring to the same
+ // element of original_args ("{0} {0}").
+ //
+ // The following vector has one item per element of our output slice,
+ // identifying the index of which element of original_args it's passing,
+ // and that argument's type.
+ let mut fmt_arg_index_and_ty = SmallVec::<[(usize, &ArgumentType); 8]>::new();
+ for (i, unique_types) in self.arg_unique_types.iter().enumerate() {
+ fmt_arg_index_and_ty.extend(unique_types.iter().map(|ty| (i, ty)));
+ }
+ fmt_arg_index_and_ty.extend(self.count_args.iter().map(|&i| (i, &Count)));
+
+ // Figure out whether there are permuted or repeated elements. If not,
+ // we can generate simpler code.
+ //
+ // The sequence has no indices out of order or repeated if: for every
+ // adjacent pair of elements, the first one's index is less than the
+ // second one's index.
+ let nicely_ordered =
+ fmt_arg_index_and_ty.array_windows().all(|[(i, _i_ty), (j, _j_ty)]| i < j);
+
+ // We want to emit:
+ //
+ // [ArgumentV1::new(&$arg0, …), ArgumentV1::new(&$arg1, …), …]
+ //
+ // However, it's only legal to do so if $arg0, $arg1, … were written in
+ // exactly that order by the programmer. When arguments are permuted, we
+ // want them evaluated in the order written by the programmer, not in
+ // the order provided to fmt::Arguments. When arguments are repeated, we
+ // want the expression evaluated only once.
+ //
+ // Further, if any arg _after the first one_ contains a yield point such
+ // as `await` or `yield`, the above short form is inconvenient for the
+ // caller because it would keep a temporary of type ArgumentV1 alive
+ // across the yield point. ArgumentV1 can't implement Send since it
+ // holds a type-erased arbitrary type.
+ //
+ // Thus in the not nicely ordered case, and in the yielding case, we
+ // emit the following instead:
+ //
+ // match (&$arg0, &$arg1, …) {
+ // args => [ArgumentV1::new(args.$i, …), ArgumentV1::new(args.$j, …), …]
+ // }
+ //
+ // for the sequence of indices $i, $j, … governed by fmt_arg_index_and_ty.
+ // This more verbose representation ensures that all arguments are
+ // evaluated a single time each, in the order written by the programmer,
+ // and that the surrounding future/generator (if any) is Send whenever
+ // possible.
+ let no_need_for_match =
+ nicely_ordered && !original_args.iter().skip(1).any(|e| may_contain_yield_point(e));
+
+ for (arg_index, arg_ty) in fmt_arg_index_and_ty {
+ let e = &mut original_args[arg_index];
+ let span = e.span;
+ let arg = if no_need_for_match {
+ let expansion_span = e.span.with_ctxt(self.macsp.ctxt());
+ // The indices are strictly ordered so e has not been taken yet.
+ self.ecx.expr_addr_of(expansion_span, P(e.take()))
+ } else {
+ let def_site = self.ecx.with_def_site_ctxt(span);
+ let args_tuple = self.ecx.expr_ident(def_site, Ident::new(sym::args, def_site));
+ let member = Ident::new(sym::integer(arg_index), def_site);
+ self.ecx.expr(def_site, ast::ExprKind::Field(args_tuple, member))
+ };
+ fmt_args.push(Context::format_arg(self.ecx, self.macsp, span, arg_ty, arg));
+ }
+
+ let args_array = self.ecx.expr_array(self.macsp, fmt_args);
+ let args_slice = self.ecx.expr_addr_of(
+ self.macsp,
+ if no_need_for_match {
+ args_array
+ } else {
+ // In the !no_need_for_match case, none of the exprs were moved
+ // away in the previous loop.
+ //
+ // This uses the arg span for `&arg` so that borrowck errors
+ // point to the specific expression passed to the macro (the
+ // span is otherwise unavailable in the MIR used by borrowck).
+ let heads = original_args
+ .into_iter()
+ .map(|e| self.ecx.expr_addr_of(e.span.with_ctxt(self.macsp.ctxt()), e))
+ .collect();
+
+ let pat = self.ecx.pat_ident(self.macsp, Ident::new(sym::args, self.macsp));
+ let arm = self.ecx.arm(self.macsp, pat, args_array);
+ let head = self.ecx.expr(self.macsp, ast::ExprKind::Tup(heads));
+ self.ecx.expr_match(self.macsp, head, vec![arm])
+ },
+ );
+
+ // Now create the fmt::Arguments struct with all our locals we created.
+ let (fn_name, fn_args) = if self.all_pieces_simple {
+ ("new_v1", vec![pieces, args_slice])
+ } else {
+ // Build up the static array which will store our precompiled
+ // nonstandard placeholders, if there are any.
+ let fmt = self.ecx.expr_array_ref(self.macsp, self.pieces);
+
+ let path = self.ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]);
+ let unsafe_arg = self.ecx.expr_call_global(self.macsp, path, Vec::new());
+ let unsafe_expr = self.ecx.expr_block(P(ast::Block {
+ stmts: vec![self.ecx.stmt_expr(unsafe_arg)],
+ id: ast::DUMMY_NODE_ID,
+ rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated),
+ span: self.macsp,
+ tokens: None,
+ could_be_bare_literal: false,
+ }));
+
+ ("new_v1_formatted", vec![pieces, args_slice, fmt, unsafe_expr])
+ };
+
+ let path = self.ecx.std_path(&[sym::fmt, sym::Arguments, Symbol::intern(fn_name)]);
+ self.ecx.expr_call_global(self.macsp, path, fn_args)
+ }
+
+ fn format_arg(
+ ecx: &ExtCtxt<'_>,
+ macsp: Span,
+ mut sp: Span,
+ ty: &ArgumentType,
+ arg: P<ast::Expr>,
+ ) -> P<ast::Expr> {
+ sp = ecx.with_def_site_ctxt(sp);
+ let trait_ = match *ty {
+ Placeholder(trait_) if trait_ == "<invalid>" => return DummyResult::raw_expr(sp, true),
+ Placeholder(trait_) => trait_,
+ Count => {
+ let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::from_usize]);
+ return ecx.expr_call_global(macsp, path, vec![arg]);
+ }
+ };
+ let new_fn_name = match trait_ {
+ "Display" => "new_display",
+ "Debug" => "new_debug",
+ "LowerExp" => "new_lower_exp",
+ "UpperExp" => "new_upper_exp",
+ "Octal" => "new_octal",
+ "Pointer" => "new_pointer",
+ "Binary" => "new_binary",
+ "LowerHex" => "new_lower_hex",
+ "UpperHex" => "new_upper_hex",
+ _ => unreachable!(),
+ };
+
+ let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, Symbol::intern(new_fn_name)]);
+ ecx.expr_call_global(sp, path, vec![arg])
+ }
+}
+
+fn expand_format_args_impl<'cx>(
+ ecx: &'cx mut ExtCtxt<'_>,
+ mut sp: Span,
+ tts: TokenStream,
+ nl: bool,
+) -> Box<dyn base::MacResult + 'cx> {
+ sp = ecx.with_def_site_ctxt(sp);
+ match parse_args(ecx, sp, tts) {
+ Ok((efmt, args, names)) => {
+ MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, nl))
+ }
+ Err(mut err) => {
+ err.emit();
+ DummyResult::any(sp)
+ }
+ }
+}
+
+pub fn expand_format_args<'cx>(
+ ecx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ expand_format_args_impl(ecx, sp, tts, false)
+}
+
+pub fn expand_format_args_nl<'cx>(
+ ecx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ expand_format_args_impl(ecx, sp, tts, true)
+}
+
+fn create_lints_for_named_arguments_used_positionally(cx: &mut Context<'_, '_>) {
+ for named_arg in &cx.unused_names_lint.positional_named_args {
+ let (position_sp_to_replace, position_sp_for_msg) = named_arg.get_positional_arg_spans(cx);
+
+ let msg = format!("named argument `{}` is not used by name", named_arg.replacement);
+
+ cx.ecx.buffered_early_lint.push(BufferedEarlyLint {
+ span: MultiSpan::from_span(named_arg.positional_named_arg_span),
+ msg: msg.clone(),
+ node_id: ast::CRATE_NODE_ID,
+ lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
+ diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally {
+ position_sp_to_replace,
+ position_sp_for_msg,
+ named_arg_sp: named_arg.positional_named_arg_span,
+ named_arg_name: named_arg.replacement.to_string(),
+ is_formatting_arg: named_arg.ty != PositionalNamedArgType::Arg,
+ },
+ });
+ }
+}
+
+/// Take the various parts of `format_args!(efmt, args..., name=names...)`
+/// and construct the appropriate formatting expression.
+pub fn expand_preparsed_format_args(
+ ecx: &mut ExtCtxt<'_>,
+ sp: Span,
+ efmt: P<ast::Expr>,
+ args: Vec<FormatArg>,
+ names: FxHashMap<Symbol, (usize, Span)>,
+ append_newline: bool,
+) -> P<ast::Expr> {
+ // NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because
+ // `ArgumentType` does not derive `Clone`.
+ let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
+ let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
+
+ let mut macsp = ecx.call_site();
+ macsp = ecx.with_def_site_ctxt(macsp);
+
+ let msg = "format argument must be a string literal";
+ let fmt_sp = efmt.span;
+ let efmt_kind_is_lit: bool = matches!(efmt.kind, ast::ExprKind::Lit(_));
+ 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));
+ fmt
+ }
+ Ok(fmt) => fmt,
+ Err(err) => {
+ if let Some((mut err, suggested)) = err {
+ let sugg_fmt = match args.len() {
+ 0 => "{}".to_string(),
+ _ => format!("{}{{}}", "{} ".repeat(args.len())),
+ };
+ if !suggested {
+ err.span_suggestion(
+ fmt_sp.shrink_to_lo(),
+ "you might be missing a string literal to format with",
+ format!("\"{}\", ", sugg_fmt),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ err.emit();
+ }
+ return DummyResult::raw_expr(sp, true);
+ }
+ };
+
+ let str_style = match fmt_style {
+ ast::StrStyle::Cooked => None,
+ ast::StrStyle::Raw(raw) => Some(raw as usize),
+ };
+
+ let fmt_str = fmt_str.as_str(); // for the suggestions below
+ let fmt_snippet = ecx.source_map().span_to_snippet(fmt_sp).ok();
+ let mut parser = parse::Parser::new(
+ fmt_str,
+ str_style,
+ fmt_snippet,
+ append_newline,
+ parse::ParseMode::Format,
+ );
+
+ let mut unverified_pieces = Vec::new();
+ while let Some(piece) = parser.next() {
+ if !parser.errors.is_empty() {
+ break;
+ } else {
+ unverified_pieces.push(piece);
+ }
+ }
+
+ if !parser.errors.is_empty() {
+ let err = parser.errors.remove(0);
+ let sp = if efmt_kind_is_lit {
+ fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
+ } else {
+ // The format string could be another macro invocation, e.g.:
+ // format!(concat!("abc", "{}"), 4);
+ // However, `err.span` is an inner span relative to the *result* of
+ // the macro invocation, which is why we would get a nonsensical
+ // result calling `fmt_span.from_inner(err.span)` as above, and
+ // might even end up inside a multibyte character (issue #86085).
+ // Therefore, we conservatively report the error for the entire
+ // 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");
+ if let Some(note) = err.note {
+ e.note(&note);
+ }
+ if let Some((label, span)) = err.secondary_label {
+ if efmt_kind_is_lit {
+ e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label);
+ }
+ }
+ if err.should_be_replaced_with_positional_argument {
+ let captured_arg_span =
+ fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
+ let positional_args = args.iter().filter(|arg| !arg.named).collect::<Vec<_>>();
+ if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
+ let span = match positional_args.last() {
+ Some(arg) => arg.expr.span,
+ None => fmt_sp,
+ };
+ e.multipart_suggestion_verbose(
+ "consider using a positional formatting argument instead",
+ vec![
+ (captured_arg_span, positional_args.len().to_string()),
+ (span.shrink_to_hi(), format!(", {}", arg)),
+ ],
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ e.emit();
+ return DummyResult::raw_expr(sp, true);
+ }
+
+ let arg_spans = parser
+ .arg_places
+ .iter()
+ .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end)))
+ .collect();
+
+ let named_pos: FxHashSet<usize> = names.values().cloned().map(|(i, _)| i).collect();
+
+ let mut cx = Context {
+ ecx,
+ args: args.into_iter().map(|arg| arg.expr).collect(),
+ num_captured_args: 0,
+ arg_types,
+ arg_unique_types,
+ names,
+ curarg: 0,
+ curpiece: 0,
+ arg_index_map: Vec::new(),
+ count_args: Vec::new(),
+ count_positions: FxHashMap::default(),
+ count_positions_count: 0,
+ count_args_index_offset: 0,
+ literal: String::new(),
+ pieces: Vec::with_capacity(unverified_pieces.len()),
+ str_pieces: Vec::with_capacity(unverified_pieces.len()),
+ all_pieces_simple: true,
+ macsp,
+ fmtsp: fmt_span,
+ invalid_refs: Vec::new(),
+ arg_spans,
+ arg_with_formatting: Vec::new(),
+ is_literal: parser.is_literal,
+ unused_names_lint: PositionalNamedArgsLint { positional_named_args: vec![] },
+ };
+
+ // This needs to happen *after* the Parser has consumed all pieces to create all the spans
+ let pieces = unverified_pieces
+ .into_iter()
+ .map(|mut piece| {
+ cx.verify_piece(&piece);
+ cx.resolve_name_inplace(&mut piece);
+ piece
+ })
+ .collect::<Vec<_>>();
+
+ let numbered_position_args = pieces.iter().any(|arg: &parse::Piece<'_>| match *arg {
+ parse::String(_) => false,
+ parse::NextArgument(arg) => matches!(arg.position, parse::Position::ArgumentIs(..)),
+ });
+
+ cx.build_index_map();
+
+ let mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()];
+
+ for piece in pieces {
+ if let Some(piece) = cx.build_piece(&piece, &mut arg_index_consumed) {
+ let s = cx.build_literal_string();
+ cx.str_pieces.push(s);
+ cx.pieces.push(piece);
+ }
+ }
+
+ if !cx.literal.is_empty() {
+ let s = cx.build_literal_string();
+ cx.str_pieces.push(s);
+ }
+
+ if !cx.invalid_refs.is_empty() {
+ cx.report_invalid_references(numbered_position_args);
+ }
+
+ // Make sure that all arguments were used and all arguments have types.
+ let errs = cx
+ .arg_types
+ .iter()
+ .enumerate()
+ .filter(|(i, ty)| ty.is_empty() && !cx.count_positions.contains_key(&i))
+ .map(|(i, _)| {
+ let msg = if named_pos.contains(&i) {
+ // named argument
+ "named argument never used"
+ } else {
+ // positional argument
+ "argument never used"
+ };
+ (cx.args[i].span, msg)
+ })
+ .collect::<Vec<_>>();
+
+ let errs_len = errs.len();
+ if !errs.is_empty() {
+ let args_used = cx.arg_types.len() - errs_len;
+ let args_unused = errs_len;
+
+ let mut diag = {
+ if let [(sp, msg)] = &errs[..] {
+ let mut diag = cx.ecx.struct_span_err(*sp, *msg);
+ diag.span_label(*sp, *msg);
+ diag
+ } else {
+ let mut diag = cx.ecx.struct_span_err(
+ errs.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
+ "multiple unused formatting arguments",
+ );
+ diag.span_label(cx.fmtsp, "multiple missing formatting specifiers");
+ for (sp, msg) in errs {
+ diag.span_label(sp, msg);
+ }
+ diag
+ }
+ };
+
+ // Used to ensure we only report translations for *one* kind of foreign format.
+ let mut found_foreign = false;
+ // Decide if we want to look for foreign formatting directives.
+ if args_used < args_unused {
+ use super::format_foreign as foreign;
+
+ // The set of foreign substitutions we've explained. This prevents spamming the user
+ // with `%d should be written as {}` over and over again.
+ let mut explained = FxHashSet::default();
+
+ macro_rules! check_foreign {
+ ($kind:ident) => {{
+ let mut show_doc_note = false;
+
+ let mut suggestions = vec![];
+ // account for `"` and account for raw strings `r#`
+ let padding = str_style.map(|i| i + 2).unwrap_or(1);
+ for sub in foreign::$kind::iter_subs(fmt_str, padding) {
+ let (trn, success) = match sub.translate() {
+ Ok(trn) => (trn, true),
+ Err(Some(msg)) => (msg, false),
+
+ // If it has no translation, don't call it out specifically.
+ _ => continue,
+ };
+
+ let pos = sub.position();
+ let sub = String::from(sub.as_str());
+ if explained.contains(&sub) {
+ continue;
+ }
+ explained.insert(sub.clone());
+
+ if !found_foreign {
+ found_foreign = true;
+ show_doc_note = true;
+ }
+
+ if let Some(inner_sp) = pos {
+ let sp = fmt_sp.from_inner(inner_sp);
+
+ if success {
+ suggestions.push((sp, trn));
+ } else {
+ diag.span_note(
+ sp,
+ &format!("format specifiers use curly braces, and {}", trn),
+ );
+ }
+ } else {
+ if success {
+ diag.help(&format!("`{}` should be written as `{}`", sub, trn));
+ } else {
+ diag.note(&format!(
+ "`{}` should use curly braces, and {}",
+ sub, trn
+ ));
+ }
+ }
+ }
+
+ if show_doc_note {
+ diag.note(concat!(
+ stringify!($kind),
+ " formatting not supported; see the documentation for `std::fmt`",
+ ));
+ }
+ if suggestions.len() > 0 {
+ diag.multipart_suggestion(
+ "format specifiers use curly braces",
+ suggestions,
+ Applicability::MachineApplicable,
+ );
+ }
+ }};
+ }
+
+ check_foreign!(printf);
+ if !found_foreign {
+ check_foreign!(shell);
+ }
+ }
+ if !found_foreign && errs_len == 1 {
+ diag.span_label(cx.fmtsp, "formatting specifier missing");
+ }
+
+ diag.emit();
+ } else if cx.invalid_refs.is_empty() && cx.ecx.sess.err_count() == 0 {
+ // Only check for unused named argument names if there are no other errors to avoid causing
+ // too much noise in output errors, such as when a named argument is entirely unused.
+ create_lints_for_named_arguments_used_positionally(&mut cx);
+ }
+
+ cx.into_expr()
+}
+
+fn may_contain_yield_point(e: &ast::Expr) -> bool {
+ struct MayContainYieldPoint(bool);
+
+ impl Visitor<'_> for MayContainYieldPoint {
+ fn visit_expr(&mut self, e: &ast::Expr) {
+ if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind {
+ self.0 = true;
+ } else {
+ visit::walk_expr(self, e);
+ }
+ }
+
+ fn visit_mac_call(&mut self, _: &ast::MacCall) {
+ self.0 = true;
+ }
+
+ fn visit_attribute(&mut self, _: &ast::Attribute) {
+ // Conservatively assume this may be a proc macro attribute in
+ // expression position.
+ self.0 = true;
+ }
+
+ fn visit_item(&mut self, _: &ast::Item) {
+ // Do not recurse into nested items.
+ }
+ }
+
+ let mut visitor = MayContainYieldPoint(false);
+ visitor.visit_expr(e);
+ visitor.0
+}
diff --git a/compiler/rustc_builtin_macros/src/format_foreign.rs b/compiler/rustc_builtin_macros/src/format_foreign.rs
new file mode 100644
index 000000000..ecd16736e
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format_foreign.rs
@@ -0,0 +1,829 @@
+pub(crate) mod printf {
+ use super::strcursor::StrCursor as Cur;
+ use rustc_span::InnerSpan;
+
+ /// Represents a single `printf`-style substitution.
+ #[derive(Clone, PartialEq, Debug)]
+ pub enum Substitution<'a> {
+ /// A formatted output substitution with its internal byte offset.
+ Format(Format<'a>),
+ /// A literal `%%` escape, with its start and end indices.
+ Escape((usize, usize)),
+ }
+
+ impl<'a> Substitution<'a> {
+ pub fn as_str(&self) -> &str {
+ match *self {
+ Substitution::Format(ref fmt) => fmt.span,
+ Substitution::Escape(_) => "%%",
+ }
+ }
+
+ pub fn position(&self) -> Option<InnerSpan> {
+ match *self {
+ Substitution::Format(ref fmt) => Some(fmt.position),
+ Substitution::Escape((start, end)) => Some(InnerSpan::new(start, end)),
+ }
+ }
+
+ pub fn set_position(&mut self, start: usize, end: usize) {
+ match self {
+ Substitution::Format(ref mut fmt) => fmt.position = InnerSpan::new(start, end),
+ Substitution::Escape(ref mut pos) => *pos = (start, end),
+ }
+ }
+
+ /// Translate this substitution into an equivalent Rust formatting directive.
+ ///
+ /// This ignores cases where the substitution does not have an exact equivalent, or where
+ /// the substitution would be unnecessary.
+ pub fn translate(&self) -> Result<String, Option<String>> {
+ match *self {
+ Substitution::Format(ref fmt) => fmt.translate(),
+ Substitution::Escape(_) => Err(None),
+ }
+ }
+ }
+
+ #[derive(Clone, PartialEq, Debug)]
+ /// A single `printf`-style formatting directive.
+ pub struct Format<'a> {
+ /// The entire original formatting directive.
+ pub span: &'a str,
+ /// The (1-based) parameter to be converted.
+ pub parameter: Option<u16>,
+ /// Formatting flags.
+ pub flags: &'a str,
+ /// Minimum width of the output.
+ pub width: Option<Num>,
+ /// Precision of the conversion.
+ pub precision: Option<Num>,
+ /// Length modifier for the conversion.
+ pub length: Option<&'a str>,
+ /// Type of parameter being converted.
+ pub type_: &'a str,
+ /// Byte offset for the start and end of this formatting directive.
+ pub position: InnerSpan,
+ }
+
+ impl Format<'_> {
+ /// Translate this directive into an equivalent Rust formatting directive.
+ ///
+ /// Returns `Err` in cases where the `printf` directive does not have an exact Rust
+ /// equivalent, rather than guessing.
+ pub fn translate(&self) -> Result<String, Option<String>> {
+ use std::fmt::Write;
+
+ let (c_alt, c_zero, c_left, c_plus) = {
+ let mut c_alt = false;
+ let mut c_zero = false;
+ let mut c_left = false;
+ let mut c_plus = false;
+ for c in self.flags.chars() {
+ match c {
+ '#' => c_alt = true,
+ '0' => c_zero = true,
+ '-' => c_left = true,
+ '+' => c_plus = true,
+ _ => {
+ return Err(Some(format!(
+ "the flag `{}` is unknown or unsupported",
+ c
+ )));
+ }
+ }
+ }
+ (c_alt, c_zero, c_left, c_plus)
+ };
+
+ // Has a special form in Rust for numbers.
+ let fill = c_zero.then_some("0");
+
+ let align = c_left.then_some("<");
+
+ // Rust doesn't have an equivalent to the `' '` flag.
+ let sign = c_plus.then_some("+");
+
+ // Not *quite* the same, depending on the type...
+ let alt = c_alt;
+
+ let width = match self.width {
+ Some(Num::Next) => {
+ // NOTE: Rust doesn't support this.
+ return Err(Some(
+ "you have to use a positional or named parameter for the width".to_string(),
+ ));
+ }
+ w @ Some(Num::Arg(_)) => w,
+ w @ Some(Num::Num(_)) => w,
+ None => None,
+ };
+
+ let precision = self.precision;
+
+ // NOTE: although length *can* have an effect, we can't duplicate the effect in Rust, so
+ // we just ignore it.
+
+ let (type_, use_zero_fill, is_int) = match self.type_ {
+ "d" | "i" | "u" => (None, true, true),
+ "f" | "F" => (None, false, false),
+ "s" | "c" => (None, false, false),
+ "e" | "E" => (Some(self.type_), true, false),
+ "x" | "X" | "o" => (Some(self.type_), true, true),
+ "p" => (Some(self.type_), false, true),
+ "g" => (Some("e"), true, false),
+ "G" => (Some("E"), true, false),
+ _ => {
+ return Err(Some(format!(
+ "the conversion specifier `{}` is unknown or unsupported",
+ self.type_
+ )));
+ }
+ };
+
+ let (fill, width, precision) = match (is_int, width, precision) {
+ (true, Some(_), Some(_)) => {
+ // Rust can't duplicate this insanity.
+ return Err(Some(
+ "width and precision cannot both be specified for integer conversions"
+ .to_string(),
+ ));
+ }
+ (true, None, Some(p)) => (Some("0"), Some(p), None),
+ (true, w, None) => (fill, w, None),
+ (false, w, p) => (fill, w, p),
+ };
+
+ let align = match (self.type_, width.is_some(), align.is_some()) {
+ ("s", true, false) => Some(">"),
+ _ => align,
+ };
+
+ let (fill, zero_fill) = match (fill, use_zero_fill) {
+ (Some("0"), true) => (None, true),
+ (fill, _) => (fill, false),
+ };
+
+ let alt = match type_ {
+ Some("x" | "X") => alt,
+ _ => false,
+ };
+
+ let has_options = fill.is_some()
+ || align.is_some()
+ || sign.is_some()
+ || alt
+ || zero_fill
+ || width.is_some()
+ || precision.is_some()
+ || type_.is_some();
+
+ // Initialise with a rough guess.
+ let cap = self.span.len() + if has_options { 2 } else { 0 };
+ let mut s = String::with_capacity(cap);
+
+ s.push('{');
+
+ if let Some(arg) = self.parameter {
+ match write!(
+ s,
+ "{}",
+ match arg.checked_sub(1) {
+ Some(a) => a,
+ None => return Err(None),
+ }
+ ) {
+ Err(_) => return Err(None),
+ _ => {}
+ }
+ }
+
+ if has_options {
+ s.push(':');
+
+ let align = if let Some(fill) = fill {
+ s.push_str(fill);
+ align.or(Some(">"))
+ } else {
+ align
+ };
+
+ if let Some(align) = align {
+ s.push_str(align);
+ }
+
+ if let Some(sign) = sign {
+ s.push_str(sign);
+ }
+
+ if alt {
+ s.push('#');
+ }
+
+ if zero_fill {
+ s.push('0');
+ }
+
+ if let Some(width) = width {
+ match width.translate(&mut s) {
+ Err(_) => return Err(None),
+ _ => {}
+ }
+ }
+
+ if let Some(precision) = precision {
+ s.push('.');
+ match precision.translate(&mut s) {
+ Err(_) => return Err(None),
+ _ => {}
+ }
+ }
+
+ if let Some(type_) = type_ {
+ s.push_str(type_);
+ }
+ }
+
+ s.push('}');
+ Ok(s)
+ }
+ }
+
+ /// A general number used in a `printf` formatting directive.
+ #[derive(Copy, Clone, PartialEq, Debug)]
+ pub enum Num {
+ // The range of these values is technically bounded by `NL_ARGMAX`... but, at least for GNU
+ // libc, it apparently has no real fixed limit. A `u16` is used here on the basis that it
+ // is *vanishingly* unlikely that *anyone* is going to try formatting something wider, or
+ // with more precision, than 32 thousand positions which is so wide it couldn't possibly fit
+ // on a screen.
+ /// A specific, fixed value.
+ Num(u16),
+ /// The value is derived from a positional argument.
+ Arg(u16),
+ /// The value is derived from the "next" unconverted argument.
+ Next,
+ }
+
+ impl Num {
+ fn from_str(s: &str, arg: Option<&str>) -> Self {
+ if let Some(arg) = arg {
+ Num::Arg(arg.parse().unwrap_or_else(|_| panic!("invalid format arg `{:?}`", arg)))
+ } else if s == "*" {
+ Num::Next
+ } else {
+ Num::Num(s.parse().unwrap_or_else(|_| panic!("invalid format num `{:?}`", s)))
+ }
+ }
+
+ fn translate(&self, s: &mut String) -> std::fmt::Result {
+ use std::fmt::Write;
+ match *self {
+ Num::Num(n) => write!(s, "{}", n),
+ Num::Arg(n) => {
+ let n = n.checked_sub(1).ok_or(std::fmt::Error)?;
+ write!(s, "{}$", n)
+ }
+ Num::Next => write!(s, "*"),
+ }
+ }
+ }
+
+ /// Returns an iterator over all substitutions in a given string.
+ pub fn iter_subs(s: &str, start_pos: usize) -> Substitutions<'_> {
+ Substitutions { s, pos: start_pos }
+ }
+
+ /// Iterator over substitutions in a string.
+ pub struct Substitutions<'a> {
+ s: &'a str,
+ pos: usize,
+ }
+
+ impl<'a> Iterator for Substitutions<'a> {
+ type Item = Substitution<'a>;
+ fn next(&mut self) -> Option<Self::Item> {
+ let (mut sub, tail) = parse_next_substitution(self.s)?;
+ self.s = tail;
+ if let Some(InnerSpan { start, end }) = sub.position() {
+ sub.set_position(start + self.pos, end + self.pos);
+ self.pos += end;
+ }
+ Some(sub)
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ // Substitutions are at least 2 characters long.
+ (0, Some(self.s.len() / 2))
+ }
+ }
+
+ enum State {
+ Start,
+ Flags,
+ Width,
+ WidthArg,
+ Prec,
+ PrecInner,
+ Length,
+ Type,
+ }
+
+ /// Parse the next substitution from the input string.
+ pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
+ use self::State::*;
+
+ let at = {
+ let start = s.find('%')?;
+ if let '%' = s[start + 1..].chars().next()? {
+ return Some((Substitution::Escape((start, start + 2)), &s[start + 2..]));
+ }
+
+ Cur::new_at(s, start)
+ };
+
+ // This is meant to be a translation of the following regex:
+ //
+ // ```regex
+ // (?x)
+ // ^ %
+ // (?: (?P<parameter> \d+) \$ )?
+ // (?P<flags> [-+ 0\#']* )
+ // (?P<width> \d+ | \* (?: (?P<widtha> \d+) \$ )? )?
+ // (?: \. (?P<precision> \d+ | \* (?: (?P<precisiona> \d+) \$ )? ) )?
+ // (?P<length>
+ // # Standard
+ // hh | h | ll | l | L | z | j | t
+ //
+ // # Other
+ // | I32 | I64 | I | q
+ // )?
+ // (?P<type> . )
+ // ```
+
+ // Used to establish the full span at the end.
+ let start = at;
+ // The current position within the string.
+ let mut at = at.at_next_cp()?;
+ // `c` is the next codepoint, `next` is a cursor after it.
+ let (mut c, mut next) = at.next_cp()?;
+
+ // Update `at`, `c`, and `next`, exiting if we're out of input.
+ macro_rules! move_to {
+ ($cur:expr) => {{
+ at = $cur;
+ let (c_, next_) = at.next_cp()?;
+ c = c_;
+ next = next_;
+ }};
+ }
+
+ // Constructs a result when parsing fails.
+ //
+ // Note: `move` used to capture copies of the cursors as they are *now*.
+ let fallback = move || {
+ Some((
+ Substitution::Format(Format {
+ span: start.slice_between(next).unwrap(),
+ parameter: None,
+ flags: "",
+ width: None,
+ precision: None,
+ length: None,
+ type_: at.slice_between(next).unwrap(),
+ position: InnerSpan::new(start.at, next.at),
+ }),
+ next.slice_after(),
+ ))
+ };
+
+ // Next parsing state.
+ let mut state = Start;
+
+ // Sadly, Rust isn't *quite* smart enough to know these *must* be initialised by the end.
+ let mut parameter: Option<u16> = None;
+ let mut flags: &str = "";
+ let mut width: Option<Num> = None;
+ let mut precision: Option<Num> = None;
+ let mut length: Option<&str> = None;
+ let mut type_: &str = "";
+ let end: Cur<'_>;
+
+ if let Start = state {
+ match c {
+ '1'..='9' => {
+ let end = at_next_cp_while(next, char::is_ascii_digit);
+ match end.next_cp() {
+ // Yes, this *is* the parameter.
+ Some(('$', end2)) => {
+ state = Flags;
+ parameter = Some(at.slice_between(end).unwrap().parse().unwrap());
+ move_to!(end2);
+ }
+ // Wait, no, actually, it's the width.
+ Some(_) => {
+ state = Prec;
+ parameter = None;
+ flags = "";
+ width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
+ move_to!(end);
+ }
+ // It's invalid, is what it is.
+ None => return fallback(),
+ }
+ }
+ _ => {
+ state = Flags;
+ parameter = None;
+ move_to!(at);
+ }
+ }
+ }
+
+ if let Flags = state {
+ let end = at_next_cp_while(at, is_flag);
+ state = Width;
+ flags = at.slice_between(end).unwrap();
+ move_to!(end);
+ }
+
+ if let Width = state {
+ match c {
+ '*' => {
+ state = WidthArg;
+ move_to!(next);
+ }
+ '1'..='9' => {
+ let end = at_next_cp_while(next, char::is_ascii_digit);
+ state = Prec;
+ width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
+ move_to!(end);
+ }
+ _ => {
+ state = Prec;
+ width = None;
+ move_to!(at);
+ }
+ }
+ }
+
+ if let WidthArg = state {
+ let end = at_next_cp_while(at, char::is_ascii_digit);
+ match end.next_cp() {
+ Some(('$', end2)) => {
+ state = Prec;
+ width = Some(Num::from_str("", Some(at.slice_between(end).unwrap())));
+ move_to!(end2);
+ }
+ _ => {
+ state = Prec;
+ width = Some(Num::Next);
+ move_to!(end);
+ }
+ }
+ }
+
+ if let Prec = state {
+ match c {
+ '.' => {
+ state = PrecInner;
+ move_to!(next);
+ }
+ _ => {
+ state = Length;
+ precision = None;
+ move_to!(at);
+ }
+ }
+ }
+
+ if let PrecInner = state {
+ match c {
+ '*' => {
+ let end = at_next_cp_while(next, char::is_ascii_digit);
+ match end.next_cp() {
+ Some(('$', end2)) => {
+ state = Length;
+ precision = Some(Num::from_str("*", next.slice_between(end)));
+ move_to!(end2);
+ }
+ _ => {
+ state = Length;
+ precision = Some(Num::Next);
+ move_to!(end);
+ }
+ }
+ }
+ '0'..='9' => {
+ let end = at_next_cp_while(next, char::is_ascii_digit);
+ state = Length;
+ precision = Some(Num::from_str(at.slice_between(end).unwrap(), None));
+ move_to!(end);
+ }
+ _ => return fallback(),
+ }
+ }
+
+ if let Length = state {
+ let c1_next1 = next.next_cp();
+ match (c, c1_next1) {
+ ('h', Some(('h', next1))) | ('l', Some(('l', next1))) => {
+ state = Type;
+ length = Some(at.slice_between(next1).unwrap());
+ move_to!(next1);
+ }
+
+ ('h' | 'l' | 'L' | 'z' | 'j' | 't' | 'q', _) => {
+ state = Type;
+ length = Some(at.slice_between(next).unwrap());
+ move_to!(next);
+ }
+
+ ('I', _) => {
+ let end = next
+ .at_next_cp()
+ .and_then(|end| end.at_next_cp())
+ .map(|end| (next.slice_between(end).unwrap(), end));
+ let end = match end {
+ Some(("32" | "64", end)) => end,
+ _ => next,
+ };
+ state = Type;
+ length = Some(at.slice_between(end).unwrap());
+ move_to!(end);
+ }
+
+ _ => {
+ state = Type;
+ length = None;
+ move_to!(at);
+ }
+ }
+ }
+
+ if let Type = state {
+ drop(c);
+ type_ = at.slice_between(next).unwrap();
+
+ // Don't use `move_to!` here, as we *can* be at the end of the input.
+ at = next;
+ }
+
+ drop(c);
+ drop(next);
+
+ end = at;
+ let position = InnerSpan::new(start.at, end.at);
+
+ let f = Format {
+ span: start.slice_between(end).unwrap(),
+ parameter,
+ flags,
+ width,
+ precision,
+ length,
+ type_,
+ position,
+ };
+ Some((Substitution::Format(f), end.slice_after()))
+ }
+
+ fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
+ where
+ F: FnMut(&char) -> bool,
+ {
+ loop {
+ match cur.next_cp() {
+ Some((c, next)) => {
+ if pred(&c) {
+ cur = next;
+ } else {
+ return cur;
+ }
+ }
+ None => return cur,
+ }
+ }
+ }
+
+ fn is_flag(c: &char) -> bool {
+ matches!(c, '0' | '-' | '+' | ' ' | '#' | '\'')
+ }
+
+ #[cfg(test)]
+ mod tests;
+}
+
+pub mod shell {
+ use super::strcursor::StrCursor as Cur;
+ use rustc_span::InnerSpan;
+
+ #[derive(Clone, PartialEq, Debug)]
+ pub enum Substitution<'a> {
+ Ordinal(u8, (usize, usize)),
+ Name(&'a str, (usize, usize)),
+ Escape((usize, usize)),
+ }
+
+ impl Substitution<'_> {
+ pub fn as_str(&self) -> String {
+ match self {
+ Substitution::Ordinal(n, _) => format!("${}", n),
+ Substitution::Name(n, _) => format!("${}", n),
+ Substitution::Escape(_) => "$$".into(),
+ }
+ }
+
+ pub fn position(&self) -> Option<InnerSpan> {
+ match self {
+ Substitution::Ordinal(_, pos)
+ | Substitution::Name(_, pos)
+ | Substitution::Escape(pos) => Some(InnerSpan::new(pos.0, pos.1)),
+ }
+ }
+
+ pub fn set_position(&mut self, start: usize, end: usize) {
+ match self {
+ Substitution::Ordinal(_, ref mut pos)
+ | Substitution::Name(_, ref mut pos)
+ | Substitution::Escape(ref mut pos) => *pos = (start, end),
+ }
+ }
+
+ pub fn translate(&self) -> Result<String, Option<String>> {
+ match *self {
+ Substitution::Ordinal(n, _) => Ok(format!("{{{}}}", n)),
+ Substitution::Name(n, _) => Ok(format!("{{{}}}", n)),
+ Substitution::Escape(_) => Err(None),
+ }
+ }
+ }
+
+ /// Returns an iterator over all substitutions in a given string.
+ pub fn iter_subs(s: &str, start_pos: usize) -> Substitutions<'_> {
+ Substitutions { s, pos: start_pos }
+ }
+
+ /// Iterator over substitutions in a string.
+ pub struct Substitutions<'a> {
+ s: &'a str,
+ pos: usize,
+ }
+
+ impl<'a> Iterator for Substitutions<'a> {
+ type Item = Substitution<'a>;
+ fn next(&mut self) -> Option<Self::Item> {
+ let (mut sub, tail) = parse_next_substitution(self.s)?;
+ self.s = tail;
+ if let Some(InnerSpan { start, end }) = sub.position() {
+ sub.set_position(start + self.pos, end + self.pos);
+ self.pos += end;
+ }
+ Some(sub)
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (0, Some(self.s.len()))
+ }
+ }
+
+ /// Parse the next substitution from the input string.
+ pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
+ let at = {
+ let start = s.find('$')?;
+ match s[start + 1..].chars().next()? {
+ '$' => return Some((Substitution::Escape((start, start + 2)), &s[start + 2..])),
+ c @ '0'..='9' => {
+ let n = (c as u8) - b'0';
+ return Some((Substitution::Ordinal(n, (start, start + 2)), &s[start + 2..]));
+ }
+ _ => { /* fall-through */ }
+ }
+
+ Cur::new_at(s, start)
+ };
+
+ let at = at.at_next_cp()?;
+ let (c, inner) = at.next_cp()?;
+
+ if !is_ident_head(c) {
+ None
+ } else {
+ let end = at_next_cp_while(inner, is_ident_tail);
+ let slice = at.slice_between(end).unwrap();
+ let start = at.at - 1;
+ let end_pos = at.at + slice.len();
+ Some((Substitution::Name(slice, (start, end_pos)), end.slice_after()))
+ }
+ }
+
+ fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
+ where
+ F: FnMut(char) -> bool,
+ {
+ loop {
+ match cur.next_cp() {
+ Some((c, next)) => {
+ if pred(c) {
+ cur = next;
+ } else {
+ return cur;
+ }
+ }
+ None => return cur,
+ }
+ }
+ }
+
+ fn is_ident_head(c: char) -> bool {
+ c.is_ascii_alphabetic() || c == '_'
+ }
+
+ fn is_ident_tail(c: char) -> bool {
+ c.is_ascii_alphanumeric() || c == '_'
+ }
+
+ #[cfg(test)]
+ mod tests;
+}
+
+mod strcursor {
+ pub struct StrCursor<'a> {
+ s: &'a str,
+ pub at: usize,
+ }
+
+ impl<'a> StrCursor<'a> {
+ pub fn new_at(s: &'a str, at: usize) -> StrCursor<'a> {
+ StrCursor { s, at }
+ }
+
+ pub fn at_next_cp(mut self) -> Option<StrCursor<'a>> {
+ match self.try_seek_right_cp() {
+ true => Some(self),
+ false => None,
+ }
+ }
+
+ pub fn next_cp(mut self) -> Option<(char, StrCursor<'a>)> {
+ let cp = self.cp_after()?;
+ self.seek_right(cp.len_utf8());
+ Some((cp, self))
+ }
+
+ fn slice_before(&self) -> &'a str {
+ &self.s[0..self.at]
+ }
+
+ pub fn slice_after(&self) -> &'a str {
+ &self.s[self.at..]
+ }
+
+ pub fn slice_between(&self, until: StrCursor<'a>) -> Option<&'a str> {
+ if !str_eq_literal(self.s, until.s) {
+ None
+ } else {
+ use std::cmp::{max, min};
+ let beg = min(self.at, until.at);
+ let end = max(self.at, until.at);
+ Some(&self.s[beg..end])
+ }
+ }
+
+ fn cp_after(&self) -> Option<char> {
+ self.slice_after().chars().next()
+ }
+
+ fn try_seek_right_cp(&mut self) -> bool {
+ match self.slice_after().chars().next() {
+ Some(c) => {
+ self.at += c.len_utf8();
+ true
+ }
+ None => false,
+ }
+ }
+
+ fn seek_right(&mut self, bytes: usize) {
+ self.at += bytes;
+ }
+ }
+
+ impl Copy for StrCursor<'_> {}
+
+ impl<'a> Clone for StrCursor<'a> {
+ fn clone(&self) -> StrCursor<'a> {
+ *self
+ }
+ }
+
+ impl std::fmt::Debug for StrCursor<'_> {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(fmt, "StrCursor({:?} | {:?})", self.slice_before(), self.slice_after())
+ }
+ }
+
+ fn str_eq_literal(a: &str, b: &str) -> bool {
+ a.as_bytes().as_ptr() == b.as_bytes().as_ptr() && a.len() == b.len()
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs b/compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs
new file mode 100644
index 000000000..fc7442470
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs
@@ -0,0 +1,145 @@
+use super::{iter_subs, parse_next_substitution as pns, Format as F, Num as N, Substitution as S};
+
+macro_rules! assert_eq_pnsat {
+ ($lhs:expr, $rhs:expr) => {
+ assert_eq!(
+ pns($lhs).and_then(|(s, _)| s.translate().ok()),
+ $rhs.map(<String as From<&str>>::from)
+ )
+ };
+}
+
+#[test]
+fn test_escape() {
+ assert_eq!(pns("has no escapes"), None);
+ assert_eq!(pns("has no escapes, either %"), None);
+ assert_eq!(pns("*so* has a %% escape"), Some((S::Escape((11, 13)), " escape")));
+ assert_eq!(pns("%% leading escape"), Some((S::Escape((0, 2)), " leading escape")));
+ assert_eq!(pns("trailing escape %%"), Some((S::Escape((16, 18)), "")));
+}
+
+#[test]
+fn test_parse() {
+ macro_rules! assert_pns_eq_sub {
+ ($in_:expr, {
+ $param:expr, $flags:expr,
+ $width:expr, $prec:expr, $len:expr, $type_:expr,
+ $pos:expr,
+ }) => {
+ assert_eq!(
+ pns(concat!($in_, "!")),
+ Some((
+ S::Format(F {
+ span: $in_,
+ parameter: $param,
+ flags: $flags,
+ width: $width,
+ precision: $prec,
+ length: $len,
+ type_: $type_,
+ position: rustc_span::InnerSpan::new($pos.0, $pos.1),
+ }),
+ "!"
+ ))
+ )
+ };
+ }
+
+ assert_pns_eq_sub!("%!",
+ { None, "", None, None, None, "!", (0, 2), });
+ assert_pns_eq_sub!("%c",
+ { None, "", None, None, None, "c", (0, 2), });
+ assert_pns_eq_sub!("%s",
+ { None, "", None, None, None, "s", (0, 2), });
+ assert_pns_eq_sub!("%06d",
+ { None, "0", Some(N::Num(6)), None, None, "d", (0, 4), });
+ assert_pns_eq_sub!("%4.2f",
+ { None, "", Some(N::Num(4)), Some(N::Num(2)), None, "f", (0, 5), });
+ assert_pns_eq_sub!("%#x",
+ { None, "#", None, None, None, "x", (0, 3), });
+ assert_pns_eq_sub!("%-10s",
+ { None, "-", Some(N::Num(10)), None, None, "s", (0, 5), });
+ assert_pns_eq_sub!("%*s",
+ { None, "", Some(N::Next), None, None, "s", (0, 3), });
+ assert_pns_eq_sub!("%-10.*s",
+ { None, "-", Some(N::Num(10)), Some(N::Next), None, "s", (0, 7), });
+ assert_pns_eq_sub!("%-*.*s",
+ { None, "-", Some(N::Next), Some(N::Next), None, "s", (0, 6), });
+ assert_pns_eq_sub!("%.6i",
+ { None, "", None, Some(N::Num(6)), None, "i", (0, 4), });
+ assert_pns_eq_sub!("%+i",
+ { None, "+", None, None, None, "i", (0, 3), });
+ assert_pns_eq_sub!("%08X",
+ { None, "0", Some(N::Num(8)), None, None, "X", (0, 4), });
+ assert_pns_eq_sub!("%lu",
+ { None, "", None, None, Some("l"), "u", (0, 3), });
+ assert_pns_eq_sub!("%Iu",
+ { None, "", None, None, Some("I"), "u", (0, 3), });
+ assert_pns_eq_sub!("%I32u",
+ { None, "", None, None, Some("I32"), "u", (0, 5), });
+ assert_pns_eq_sub!("%I64u",
+ { None, "", None, None, Some("I64"), "u", (0, 5), });
+ assert_pns_eq_sub!("%'d",
+ { None, "'", None, None, None, "d", (0, 3), });
+ assert_pns_eq_sub!("%10s",
+ { None, "", Some(N::Num(10)), None, None, "s", (0, 4), });
+ assert_pns_eq_sub!("%-10.10s",
+ { None, "-", Some(N::Num(10)), Some(N::Num(10)), None, "s", (0, 8), });
+ assert_pns_eq_sub!("%1$d",
+ { Some(1), "", None, None, None, "d", (0, 4), });
+ assert_pns_eq_sub!("%2$.*3$d",
+ { Some(2), "", None, Some(N::Arg(3)), None, "d", (0, 8), });
+ assert_pns_eq_sub!("%1$*2$.*3$d",
+ { Some(1), "", Some(N::Arg(2)), Some(N::Arg(3)), None, "d", (0, 11), });
+ assert_pns_eq_sub!("%-8ld",
+ { None, "-", Some(N::Num(8)), None, Some("l"), "d", (0, 5), });
+}
+
+#[test]
+fn test_iter() {
+ let s = "The %d'th word %% is: `%.*s` %!\n";
+ let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate().ok()).collect();
+ assert_eq!(
+ subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
+ vec![Some("{}"), None, Some("{:.*}"), None]
+ );
+}
+
+/// Checks that the translations are what we expect.
+#[test]
+fn test_translation() {
+ assert_eq_pnsat!("%c", Some("{}"));
+ assert_eq_pnsat!("%d", Some("{}"));
+ assert_eq_pnsat!("%u", Some("{}"));
+ assert_eq_pnsat!("%x", Some("{:x}"));
+ assert_eq_pnsat!("%X", Some("{:X}"));
+ assert_eq_pnsat!("%e", Some("{:e}"));
+ assert_eq_pnsat!("%E", Some("{:E}"));
+ assert_eq_pnsat!("%f", Some("{}"));
+ assert_eq_pnsat!("%g", Some("{:e}"));
+ assert_eq_pnsat!("%G", Some("{:E}"));
+ assert_eq_pnsat!("%s", Some("{}"));
+ assert_eq_pnsat!("%p", Some("{:p}"));
+
+ assert_eq_pnsat!("%06d", Some("{:06}"));
+ assert_eq_pnsat!("%4.2f", Some("{:4.2}"));
+ assert_eq_pnsat!("%#x", Some("{:#x}"));
+ assert_eq_pnsat!("%-10s", Some("{:<10}"));
+ assert_eq_pnsat!("%*s", None);
+ assert_eq_pnsat!("%-10.*s", Some("{:<10.*}"));
+ assert_eq_pnsat!("%-*.*s", None);
+ assert_eq_pnsat!("%.6i", Some("{:06}"));
+ assert_eq_pnsat!("%+i", Some("{:+}"));
+ assert_eq_pnsat!("%08X", Some("{:08X}"));
+ assert_eq_pnsat!("%lu", Some("{}"));
+ assert_eq_pnsat!("%Iu", Some("{}"));
+ assert_eq_pnsat!("%I32u", Some("{}"));
+ assert_eq_pnsat!("%I64u", Some("{}"));
+ assert_eq_pnsat!("%'d", None);
+ assert_eq_pnsat!("%10s", Some("{:>10}"));
+ assert_eq_pnsat!("%-10.10s", Some("{:<10.10}"));
+ assert_eq_pnsat!("%1$d", Some("{0}"));
+ assert_eq_pnsat!("%2$.*3$d", Some("{1:02$}"));
+ assert_eq_pnsat!("%1$*2$.*3$s", Some("{0:>1$.2$}"));
+ assert_eq_pnsat!("%-8ld", Some("{:<8}"));
+}
diff --git a/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs b/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs
new file mode 100644
index 000000000..f5f82732f
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs
@@ -0,0 +1,56 @@
+use super::{parse_next_substitution as pns, Substitution as S};
+
+macro_rules! assert_eq_pnsat {
+ ($lhs:expr, $rhs:expr) => {
+ assert_eq!(
+ pns($lhs).and_then(|(f, _)| f.translate().ok()),
+ $rhs.map(<String as From<&str>>::from)
+ )
+ };
+}
+
+#[test]
+fn test_escape() {
+ assert_eq!(pns("has no escapes"), None);
+ assert_eq!(pns("has no escapes, either $"), None);
+ assert_eq!(pns("*so* has a $$ escape"), Some((S::Escape((11, 13)), " escape")));
+ assert_eq!(pns("$$ leading escape"), Some((S::Escape((0, 2)), " leading escape")));
+ assert_eq!(pns("trailing escape $$"), Some((S::Escape((16, 18)), "")));
+}
+
+#[test]
+fn test_parse() {
+ macro_rules! assert_pns_eq_sub {
+ ($in_:expr, $kind:ident($arg:expr, $pos:expr)) => {
+ assert_eq!(pns(concat!($in_, "!")), Some((S::$kind($arg.into(), $pos), "!")))
+ };
+ }
+
+ assert_pns_eq_sub!("$0", Ordinal(0, (0, 2)));
+ assert_pns_eq_sub!("$1", Ordinal(1, (0, 2)));
+ assert_pns_eq_sub!("$9", Ordinal(9, (0, 2)));
+ assert_pns_eq_sub!("$N", Name("N", (0, 2)));
+ assert_pns_eq_sub!("$NAME", Name("NAME", (0, 5)));
+}
+
+#[test]
+fn test_iter() {
+ use super::iter_subs;
+ let s = "The $0'th word $$ is: `$WORD` $!\n";
+ let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate().ok()).collect();
+ assert_eq!(
+ subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
+ vec![Some("{0}"), None, Some("{WORD}")]
+ );
+}
+
+#[test]
+fn test_translation() {
+ assert_eq_pnsat!("$0", Some("{0}"));
+ assert_eq_pnsat!("$9", Some("{9}"));
+ assert_eq_pnsat!("$1", Some("{1}"));
+ assert_eq_pnsat!("$10", Some("{1}"));
+ assert_eq_pnsat!("$stuff", Some("{stuff}"));
+ assert_eq_pnsat!("$NAME", Some("{NAME}"));
+ assert_eq_pnsat!("$PREFIX/bin", Some("{PREFIX}"));
+}
diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs
new file mode 100644
index 000000000..36cfbba45
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/global_allocator.rs
@@ -0,0 +1,194 @@
+use crate::util::check_builtin_macro_attribute;
+
+use rustc_ast::expand::allocator::{
+ AllocatorKind, AllocatorMethod, AllocatorTy, ALLOCATOR_METHODS,
+};
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Attribute, Expr, FnHeader, FnSig, Generics, Param, StmtKind};
+use rustc_ast::{Fn, ItemKind, Mutability, Stmt, Ty, TyKind, Unsafe};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::Span;
+
+pub fn expand(
+ ecx: &mut ExtCtxt<'_>,
+ _span: Span,
+ meta_item: &ast::MetaItem,
+ item: Annotatable,
+) -> Vec<Annotatable> {
+ check_builtin_macro_attribute(ecx, meta_item, sym::global_allocator);
+
+ let orig_item = item.clone();
+ let not_static = || {
+ ecx.sess.parse_sess.span_diagnostic.span_err(item.span(), "allocators must be statics");
+ vec![orig_item.clone()]
+ };
+
+ // Allow using `#[global_allocator]` on an item statement
+ // FIXME - if we get deref patterns, use them to reduce duplication here
+ let (item, is_stmt, ty_span) = match &item {
+ Annotatable::Item(item) => match item.kind {
+ ItemKind::Static(ref ty, ..) => (item, false, ecx.with_def_site_ctxt(ty.span)),
+ _ => return not_static(),
+ },
+ Annotatable::Stmt(stmt) => match &stmt.kind {
+ StmtKind::Item(item_) => match item_.kind {
+ ItemKind::Static(ref ty, ..) => (item_, true, ecx.with_def_site_ctxt(ty.span)),
+ _ => return not_static(),
+ },
+ _ => return not_static(),
+ },
+ _ => return not_static(),
+ };
+
+ // Generate a bunch of new items using the AllocFnFactory
+ let span = ecx.with_def_site_ctxt(item.span);
+ let f =
+ AllocFnFactory { span, ty_span, kind: AllocatorKind::Global, global: item.ident, cx: ecx };
+
+ // Generate item statements for the allocator methods.
+ let stmts = ALLOCATOR_METHODS.iter().map(|method| f.allocator_fn(method)).collect();
+
+ // Generate anonymous constant serving as container for the allocator methods.
+ let const_ty = ecx.ty(ty_span, TyKind::Tup(Vec::new()));
+ let const_body = ecx.expr_block(ecx.block(span, stmts));
+ let const_item = ecx.item_const(span, Ident::new(kw::Underscore, span), const_ty, const_body);
+ let const_item = if is_stmt {
+ Annotatable::Stmt(P(ecx.stmt_item(span, const_item)))
+ } else {
+ Annotatable::Item(const_item)
+ };
+
+ // Return the original item and the new methods.
+ vec![orig_item, const_item]
+}
+
+struct AllocFnFactory<'a, 'b> {
+ span: Span,
+ ty_span: Span,
+ kind: AllocatorKind,
+ global: Ident,
+ cx: &'b ExtCtxt<'a>,
+}
+
+impl AllocFnFactory<'_, '_> {
+ fn allocator_fn(&self, method: &AllocatorMethod) -> Stmt {
+ let mut abi_args = Vec::new();
+ let mut i = 0;
+ let mut mk = || {
+ let name = Ident::from_str_and_span(&format!("arg{}", i), self.span);
+ i += 1;
+ name
+ };
+ let args = method.inputs.iter().map(|ty| self.arg_ty(ty, &mut abi_args, &mut mk)).collect();
+ let result = self.call_allocator(method.name, args);
+ let (output_ty, output_expr) = self.ret_ty(&method.output, result);
+ let decl = self.cx.fn_decl(abi_args, ast::FnRetTy::Ty(output_ty));
+ let header = FnHeader { unsafety: Unsafe::Yes(self.span), ..FnHeader::default() };
+ let sig = FnSig { decl, header, span: self.span };
+ let body = Some(self.cx.block_expr(output_expr));
+ let kind = ItemKind::Fn(Box::new(Fn {
+ defaultness: ast::Defaultness::Final,
+ sig,
+ generics: Generics::default(),
+ body,
+ }));
+ let item = self.cx.item(
+ self.span,
+ Ident::from_str_and_span(&self.kind.fn_name(method.name), self.span),
+ self.attrs(),
+ kind,
+ );
+ self.cx.stmt_item(self.ty_span, item)
+ }
+
+ fn call_allocator(&self, method: Symbol, mut args: Vec<P<Expr>>) -> P<Expr> {
+ let method = self.cx.std_path(&[sym::alloc, sym::GlobalAlloc, method]);
+ let method = self.cx.expr_path(self.cx.path(self.ty_span, method));
+ let allocator = self.cx.path_ident(self.ty_span, self.global);
+ let allocator = self.cx.expr_path(allocator);
+ let allocator = self.cx.expr_addr_of(self.ty_span, allocator);
+ args.insert(0, allocator);
+
+ self.cx.expr_call(self.ty_span, method, args)
+ }
+
+ fn attrs(&self) -> Vec<Attribute> {
+ let special = sym::rustc_std_internal_symbol;
+ let special = self.cx.meta_word(self.span, special);
+ vec![self.cx.attribute(special)]
+ }
+
+ fn arg_ty(
+ &self,
+ ty: &AllocatorTy,
+ args: &mut Vec<Param>,
+ ident: &mut dyn FnMut() -> Ident,
+ ) -> P<Expr> {
+ match *ty {
+ AllocatorTy::Layout => {
+ let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span));
+ let ty_usize = self.cx.ty_path(usize);
+ let size = ident();
+ let align = ident();
+ args.push(self.cx.param(self.span, size, ty_usize.clone()));
+ args.push(self.cx.param(self.span, align, ty_usize));
+
+ let layout_new =
+ self.cx.std_path(&[sym::alloc, sym::Layout, sym::from_size_align_unchecked]);
+ let layout_new = self.cx.expr_path(self.cx.path(self.span, layout_new));
+ let size = self.cx.expr_ident(self.span, size);
+ let align = self.cx.expr_ident(self.span, align);
+ let layout = self.cx.expr_call(self.span, layout_new, vec![size, align]);
+ layout
+ }
+
+ AllocatorTy::Ptr => {
+ let ident = ident();
+ args.push(self.cx.param(self.span, ident, self.ptr_u8()));
+ let arg = self.cx.expr_ident(self.span, ident);
+ self.cx.expr_cast(self.span, arg, self.ptr_u8())
+ }
+
+ AllocatorTy::Usize => {
+ let ident = ident();
+ args.push(self.cx.param(self.span, ident, self.usize()));
+ self.cx.expr_ident(self.span, ident)
+ }
+
+ AllocatorTy::ResultPtr | AllocatorTy::Unit => {
+ panic!("can't convert AllocatorTy to an argument")
+ }
+ }
+ }
+
+ fn ret_ty(&self, ty: &AllocatorTy, expr: P<Expr>) -> (P<Ty>, P<Expr>) {
+ match *ty {
+ AllocatorTy::ResultPtr => {
+ // We're creating:
+ //
+ // #expr as *mut u8
+
+ let expr = self.cx.expr_cast(self.span, expr, self.ptr_u8());
+ (self.ptr_u8(), expr)
+ }
+
+ AllocatorTy::Unit => (self.cx.ty(self.span, TyKind::Tup(Vec::new())), expr),
+
+ AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => {
+ panic!("can't convert `AllocatorTy` to an output")
+ }
+ }
+ }
+
+ fn usize(&self) -> P<Ty> {
+ let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span));
+ self.cx.ty_path(usize)
+ }
+
+ fn ptr_u8(&self) -> P<Ty> {
+ let u8 = self.cx.path_ident(self.span, Ident::new(sym::u8, self.span));
+ let ty_u8 = self.cx.ty_path(u8);
+ self.cx.ty_ptr(self.span, ty_u8, Mutability::Mut)
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
new file mode 100644
index 000000000..11565ba72
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -0,0 +1,119 @@
+//! This crate contains implementations of built-in macros and other code generating facilities
+//! injecting code into the crate before it is lowered to HIR.
+
+#![allow(rustc::potential_query_instability)]
+#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(array_windows)]
+#![feature(box_patterns)]
+#![feature(decl_macro)]
+#![feature(if_let_guard)]
+#![feature(is_sorted)]
+#![feature(let_chains)]
+#![feature(let_else)]
+#![feature(proc_macro_internals)]
+#![feature(proc_macro_quote)]
+#![recursion_limit = "256"]
+
+extern crate proc_macro;
+
+use crate::deriving::*;
+
+use rustc_expand::base::{MacroExpanderFn, ResolverExpand, SyntaxExtensionKind};
+use rustc_expand::proc_macro::BangProcMacro;
+use rustc_span::symbol::sym;
+
+mod assert;
+mod cfg;
+mod cfg_accessible;
+mod cfg_eval;
+mod compile_error;
+mod concat;
+mod concat_bytes;
+mod concat_idents;
+mod derive;
+mod deriving;
+mod edition_panic;
+mod env;
+mod format;
+mod format_foreign;
+mod global_allocator;
+mod log_syntax;
+mod source_util;
+mod test;
+mod trace_macros;
+mod util;
+
+pub mod asm;
+pub mod cmdline_attrs;
+pub mod proc_macro_harness;
+pub mod standard_library_imports;
+pub mod test_harness;
+
+pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
+ let mut register = |name, kind| resolver.register_builtin_macro(name, kind);
+ macro register_bang($($name:ident: $f:expr,)*) {
+ $(register(sym::$name, SyntaxExtensionKind::LegacyBang(Box::new($f as MacroExpanderFn)));)*
+ }
+ macro register_attr($($name:ident: $f:expr,)*) {
+ $(register(sym::$name, SyntaxExtensionKind::LegacyAttr(Box::new($f)));)*
+ }
+ macro register_derive($($name:ident: $f:expr,)*) {
+ $(register(sym::$name, SyntaxExtensionKind::LegacyDerive(Box::new(BuiltinDerive($f))));)*
+ }
+
+ register_bang! {
+ asm: asm::expand_asm,
+ assert: assert::expand_assert,
+ cfg: cfg::expand_cfg,
+ column: source_util::expand_column,
+ compile_error: compile_error::expand_compile_error,
+ concat_bytes: concat_bytes::expand_concat_bytes,
+ concat_idents: concat_idents::expand_concat_idents,
+ concat: concat::expand_concat,
+ env: env::expand_env,
+ file: source_util::expand_file,
+ format_args_nl: format::expand_format_args_nl,
+ format_args: format::expand_format_args,
+ const_format_args: format::expand_format_args,
+ global_asm: asm::expand_global_asm,
+ include_bytes: source_util::expand_include_bytes,
+ include_str: source_util::expand_include_str,
+ include: source_util::expand_include,
+ line: source_util::expand_line,
+ log_syntax: log_syntax::expand_log_syntax,
+ module_path: source_util::expand_mod,
+ option_env: env::expand_option_env,
+ core_panic: edition_panic::expand_panic,
+ std_panic: edition_panic::expand_panic,
+ unreachable: edition_panic::expand_unreachable,
+ stringify: source_util::expand_stringify,
+ trace_macros: trace_macros::expand_trace_macros,
+ }
+
+ register_attr! {
+ bench: test::expand_bench,
+ cfg_accessible: cfg_accessible::Expander,
+ cfg_eval: cfg_eval::expand,
+ derive: derive::Expander,
+ global_allocator: global_allocator::expand,
+ test: test::expand_test,
+ test_case: test::expand_test_case,
+ }
+
+ register_derive! {
+ Clone: clone::expand_deriving_clone,
+ Copy: bounds::expand_deriving_copy,
+ Debug: debug::expand_deriving_debug,
+ Default: default::expand_deriving_default,
+ Eq: eq::expand_deriving_eq,
+ Hash: hash::expand_deriving_hash,
+ Ord: ord::expand_deriving_ord,
+ PartialEq: partial_eq::expand_deriving_partial_eq,
+ PartialOrd: partial_ord::expand_deriving_partial_ord,
+ RustcDecodable: decodable::expand_deriving_rustc_decodable,
+ RustcEncodable: encodable::expand_deriving_rustc_encodable,
+ }
+
+ let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
+ register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })));
+}
diff --git a/compiler/rustc_builtin_macros/src/log_syntax.rs b/compiler/rustc_builtin_macros/src/log_syntax.rs
new file mode 100644
index 000000000..ede34a761
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/log_syntax.rs
@@ -0,0 +1,14 @@
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast_pretty::pprust;
+use rustc_expand::base;
+
+pub fn expand_log_syntax<'cx>(
+ _cx: &'cx mut base::ExtCtxt<'_>,
+ sp: rustc_span::Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ println!("{}", pprust::tts_to_string(&tts));
+
+ // any so that `log_syntax` can be invoked as an expression and item.
+ base::DummyResult::any_valid(sp)
+}
diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
new file mode 100644
index 000000000..5cfda3349
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
@@ -0,0 +1,393 @@
+use std::mem;
+
+use rustc_ast::attr;
+use rustc_ast::ptr::P;
+use rustc_ast::visit::{self, Visitor};
+use rustc_ast::{self as ast, NodeId};
+use rustc_ast_pretty::pprust;
+use rustc_expand::base::{parse_macro_name_and_helper_attrs, ExtCtxt, ResolverExpand};
+use rustc_expand::expand::{AstFragment, ExpansionConfig};
+use rustc_session::Session;
+use rustc_span::hygiene::AstPass;
+use rustc_span::source_map::SourceMap;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use smallvec::smallvec;
+
+struct ProcMacroDerive {
+ id: NodeId,
+ trait_name: Symbol,
+ function_name: Ident,
+ span: Span,
+ attrs: Vec<Symbol>,
+}
+
+struct ProcMacroDef {
+ id: NodeId,
+ function_name: Ident,
+ span: Span,
+}
+
+enum ProcMacro {
+ Derive(ProcMacroDerive),
+ Attr(ProcMacroDef),
+ Bang(ProcMacroDef),
+}
+
+struct CollectProcMacros<'a> {
+ sess: &'a Session,
+ macros: Vec<ProcMacro>,
+ in_root: bool,
+ handler: &'a rustc_errors::Handler,
+ source_map: &'a SourceMap,
+ is_proc_macro_crate: bool,
+ is_test_crate: bool,
+}
+
+pub fn inject(
+ 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,
+ source_map: sess.source_map(),
+ is_proc_macro_crate,
+ is_test_crate,
+ };
+
+ if has_proc_macro_decls || is_proc_macro_crate {
+ visit::walk_crate(&mut collect, &krate);
+ }
+ let macros = collect.macros;
+
+ if !is_proc_macro_crate {
+ return krate;
+ }
+
+ if is_test_crate {
+ return krate;
+ }
+
+ let decls = mk_decls(&mut cx, &macros);
+ krate.items.push(decls);
+
+ krate
+}
+
+impl<'a> CollectProcMacros<'a> {
+ fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
+ if self.is_proc_macro_crate && self.in_root && vis.kind.is_pub() {
+ self.handler.span_err(
+ sp,
+ "`proc-macro` crate types currently cannot export any items other \
+ than functions tagged with `#[proc_macro]`, `#[proc_macro_derive]`, \
+ or `#[proc_macro_attribute]`",
+ );
+ }
+ }
+
+ fn collect_custom_derive(&mut self, item: &'a ast::Item, attr: &'a ast::Attribute) {
+ let Some((trait_name, proc_attrs)) = parse_macro_name_and_helper_attrs(self.handler, attr, "derive") else {
+ return;
+ };
+
+ if self.in_root && item.vis.kind.is_pub() {
+ self.macros.push(ProcMacro::Derive(ProcMacroDerive {
+ id: item.id,
+ span: item.span,
+ trait_name,
+ function_name: item.ident,
+ attrs: proc_attrs,
+ }));
+ } else {
+ let msg = if !self.in_root {
+ "functions tagged with `#[proc_macro_derive]` must \
+ currently reside in the root of the crate"
+ } else {
+ "functions tagged with `#[proc_macro_derive]` must be `pub`"
+ };
+ self.handler.span_err(self.source_map.guess_head_span(item.span), msg);
+ }
+ }
+
+ fn collect_attr_proc_macro(&mut self, item: &'a ast::Item) {
+ if self.in_root && item.vis.kind.is_pub() {
+ self.macros.push(ProcMacro::Attr(ProcMacroDef {
+ id: item.id,
+ span: item.span,
+ function_name: item.ident,
+ }));
+ } else {
+ let msg = if !self.in_root {
+ "functions tagged with `#[proc_macro_attribute]` must \
+ currently reside in the root of the crate"
+ } else {
+ "functions tagged with `#[proc_macro_attribute]` must be `pub`"
+ };
+ self.handler.span_err(self.source_map.guess_head_span(item.span), msg);
+ }
+ }
+
+ fn collect_bang_proc_macro(&mut self, item: &'a ast::Item) {
+ if self.in_root && item.vis.kind.is_pub() {
+ self.macros.push(ProcMacro::Bang(ProcMacroDef {
+ id: item.id,
+ span: item.span,
+ function_name: item.ident,
+ }));
+ } else {
+ let msg = if !self.in_root {
+ "functions tagged with `#[proc_macro]` must \
+ currently reside in the root of the crate"
+ } else {
+ "functions tagged with `#[proc_macro]` must be `pub`"
+ };
+ self.handler.span_err(self.source_map.guess_head_span(item.span), msg);
+ }
+ }
+}
+
+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) {
+ 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);
+ }
+ }
+
+ // First up, make sure we're checking a bare function. If we're not then
+ // we're just not interested in this item.
+ //
+ // If we find one, try to locate a `#[proc_macro_derive]` attribute on it.
+ let is_fn = matches!(item.kind, ast::ItemKind::Fn(..));
+
+ let mut found_attr: Option<&'a ast::Attribute> = None;
+
+ for attr in &item.attrs {
+ if self.sess.is_proc_macro_attr(&attr) {
+ if let Some(prev_attr) = found_attr {
+ let prev_item = prev_attr.get_normal_item();
+ let item = attr.get_normal_item();
+ let path_str = pprust::path_to_string(&item.path);
+ let msg = if item.path.segments[0].ident.name
+ == prev_item.path.segments[0].ident.name
+ {
+ format!(
+ "only one `#[{}]` attribute is allowed on any given function",
+ path_str,
+ )
+ } else {
+ format!(
+ "`#[{}]` and `#[{}]` attributes cannot both be applied
+ to the same function",
+ path_str,
+ pprust::path_to_string(&prev_item.path),
+ )
+ };
+
+ self.handler
+ .struct_span_err(attr.span, &msg)
+ .span_label(prev_attr.span, "previous attribute here")
+ .emit();
+
+ return;
+ }
+
+ found_attr = Some(attr);
+ }
+ }
+
+ let Some(attr) = found_attr else {
+ self.check_not_pub_in_root(&item.vis, self.source_map.guess_head_span(item.span));
+ let prev_in_root = mem::replace(&mut self.in_root, false);
+ visit::walk_item(self, item);
+ self.in_root = prev_in_root;
+ return;
+ };
+
+ if !is_fn {
+ let msg = format!(
+ "the `#[{}]` attribute may only be used on bare functions",
+ pprust::path_to_string(&attr.get_normal_item().path),
+ );
+
+ self.handler.span_err(attr.span, &msg);
+ return;
+ }
+
+ if self.is_test_crate {
+ return;
+ }
+
+ if !self.is_proc_macro_crate {
+ let msg = format!(
+ "the `#[{}]` attribute is only usable with crates of the `proc-macro` crate type",
+ pprust::path_to_string(&attr.get_normal_item().path),
+ );
+
+ self.handler.span_err(attr.span, &msg);
+ return;
+ }
+
+ if attr.has_name(sym::proc_macro_derive) {
+ self.collect_custom_derive(item, attr);
+ } else if attr.has_name(sym::proc_macro_attribute) {
+ self.collect_attr_proc_macro(item);
+ } else if attr.has_name(sym::proc_macro) {
+ self.collect_bang_proc_macro(item);
+ };
+
+ let prev_in_root = mem::replace(&mut self.in_root, false);
+ visit::walk_item(self, item);
+ self.in_root = prev_in_root;
+ }
+}
+
+// Creates a new module which looks like:
+//
+// const _: () = {
+// extern crate proc_macro;
+//
+// use proc_macro::bridge::client::ProcMacro;
+//
+// #[rustc_proc_macro_decls]
+// #[allow(deprecated)]
+// static DECLS: &[ProcMacro] = &[
+// ProcMacro::custom_derive($name_trait1, &[], ::$name1);
+// ProcMacro::custom_derive($name_trait2, &["attribute_name"], ::$name2);
+// // ...
+// ];
+// }
+fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
+ let expn_id = cx.resolver.expansion_for_ast_pass(
+ DUMMY_SP,
+ AstPass::ProcMacroHarness,
+ &[sym::rustc_attrs, sym::proc_macro_internals],
+ None,
+ );
+ let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
+
+ let proc_macro = Ident::new(sym::proc_macro, span);
+ let krate = cx.item(span, proc_macro, Vec::new(), ast::ItemKind::ExternCrate(None));
+
+ let bridge = Ident::new(sym::bridge, span);
+ let client = Ident::new(sym::client, span);
+ let proc_macro_ty = Ident::new(sym::ProcMacro, span);
+ let custom_derive = Ident::new(sym::custom_derive, span);
+ let attr = Ident::new(sym::attr, span);
+ let bang = Ident::new(sym::bang, span);
+
+ // We add NodeIds to 'resolver.proc_macros' in the order
+ // that we generate expressions. The position of each NodeId
+ // in the 'proc_macros' Vec corresponds to its position
+ // in the static array that will be generated
+ let decls = macros
+ .iter()
+ .map(|m| {
+ let harness_span = span;
+ let span = match m {
+ ProcMacro::Derive(m) => m.span,
+ ProcMacro::Attr(m) | ProcMacro::Bang(m) => m.span,
+ };
+ let local_path = |cx: &ExtCtxt<'_>, name| cx.expr_path(cx.path(span, vec![name]));
+ let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
+ cx.expr_path(cx.path(
+ span.with_ctxt(harness_span.ctxt()),
+ vec![proc_macro, bridge, client, proc_macro_ty, method],
+ ))
+ };
+ match m {
+ ProcMacro::Derive(cd) => {
+ cx.resolver.declare_proc_macro(cd.id);
+ cx.expr_call(
+ span,
+ proc_macro_ty_method_path(cx, custom_derive),
+ vec![
+ cx.expr_str(span, cd.trait_name),
+ cx.expr_array_ref(
+ span,
+ cd.attrs.iter().map(|&s| cx.expr_str(span, s)).collect::<Vec<_>>(),
+ ),
+ local_path(cx, cd.function_name),
+ ],
+ )
+ }
+ ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
+ cx.resolver.declare_proc_macro(ca.id);
+ let ident = match m {
+ ProcMacro::Attr(_) => attr,
+ ProcMacro::Bang(_) => bang,
+ ProcMacro::Derive(_) => unreachable!(),
+ };
+
+ cx.expr_call(
+ span,
+ proc_macro_ty_method_path(cx, ident),
+ vec![
+ cx.expr_str(span, ca.function_name.name),
+ local_path(cx, ca.function_name),
+ ],
+ )
+ }
+ }
+ })
+ .collect();
+
+ let decls_static = cx
+ .item_static(
+ span,
+ Ident::new(sym::_DECLS, span),
+ cx.ty_rptr(
+ span,
+ cx.ty(
+ span,
+ ast::TyKind::Slice(
+ cx.ty_path(cx.path(span, vec![proc_macro, bridge, client, proc_macro_ty])),
+ ),
+ ),
+ None,
+ ast::Mutability::Not,
+ ),
+ ast::Mutability::Not,
+ cx.expr_array_ref(span, decls),
+ )
+ .map(|mut i| {
+ let attr = cx.meta_word(span, sym::rustc_proc_macro_decls);
+ i.attrs.push(cx.attribute(attr));
+
+ let deprecated_attr = attr::mk_nested_word_item(Ident::new(sym::deprecated, span));
+ let allow_deprecated_attr =
+ attr::mk_list_item(Ident::new(sym::allow, span), vec![deprecated_attr]);
+ i.attrs.push(cx.attribute(allow_deprecated_attr));
+
+ i
+ });
+
+ let block = cx.expr_block(
+ cx.block(span, vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]),
+ );
+
+ let anon_constant = cx.item_const(
+ span,
+ Ident::new(kw::Underscore, span),
+ cx.ty(span, ast::TyKind::Tup(Vec::new())),
+ block,
+ );
+
+ // Integrate the new item into existing module structures.
+ let items = AstFragment::Items(smallvec![anon_constant]);
+ cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
+}
diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs
new file mode 100644
index 000000000..8bf3a0799
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/source_util.rs
@@ -0,0 +1,225 @@
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::token;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast_pretty::pprust;
+use rustc_expand::base::{self, *};
+use rustc_expand::module::DirOwnership;
+use rustc_parse::parser::{ForceCollect, Parser};
+use rustc_parse::{self, new_parser_from_file};
+use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
+use rustc_span::symbol::Symbol;
+use rustc_span::{self, Pos, Span};
+
+use smallvec::SmallVec;
+use std::rc::Rc;
+
+// These macros all relate to the file system; they either return
+// the column/row/filename of the expression, or they include
+// a given file into the current one.
+
+/// line!(): expands to the current line number
+pub fn expand_line(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+ base::check_zero_tts(cx, sp, tts, "line!");
+
+ let topmost = cx.expansion_cause().unwrap_or(sp);
+ let loc = cx.source_map().lookup_char_pos(topmost.lo());
+
+ base::MacEager::expr(cx.expr_u32(topmost, loc.line as u32))
+}
+
+/* column!(): expands to the current column number */
+pub fn expand_column(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+ base::check_zero_tts(cx, sp, tts, "column!");
+
+ let topmost = cx.expansion_cause().unwrap_or(sp);
+ let loc = cx.source_map().lookup_char_pos(topmost.lo());
+
+ base::MacEager::expr(cx.expr_u32(topmost, loc.col.to_usize() as u32 + 1))
+}
+
+/// file!(): expands to the current filename */
+/// The source_file (`loc.file`) contains a bunch more information we could spit
+/// out if we wanted.
+pub fn expand_file(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+ base::check_zero_tts(cx, sp, tts, "file!");
+
+ let topmost = cx.expansion_cause().unwrap_or(sp);
+ let loc = cx.source_map().lookup_char_pos(topmost.lo());
+ base::MacEager::expr(
+ cx.expr_str(topmost, Symbol::intern(&loc.file.name.prefer_remapped().to_string_lossy())),
+ )
+}
+
+pub fn expand_stringify(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+ let s = pprust::tts_to_string(&tts);
+ base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&s)))
+}
+
+pub fn expand_mod(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+ base::check_zero_tts(cx, sp, tts, "module_path!");
+ let mod_path = &cx.current_expansion.module.mod_path;
+ let string = mod_path.iter().map(|x| x.to_string()).collect::<Vec<String>>().join("::");
+
+ base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&string)))
+}
+
+/// include! : parse the given file as an expr
+/// This is generally a bad idea because it's going to behave
+/// unhygienically.
+pub fn expand_include<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+ let sp = cx.with_def_site_ctxt(sp);
+ let Some(file) = get_single_str_from_tts(cx, sp, tts, "include!") else {
+ return DummyResult::any(sp);
+ };
+ // The file will be added to the code map by the parser
+ let file = match resolve_path(&cx.sess.parse_sess, file.as_str(), sp) {
+ Ok(f) => f,
+ Err(mut err) => {
+ err.emit();
+ return DummyResult::any(sp);
+ }
+ };
+ let p = new_parser_from_file(cx.parse_sess(), &file, Some(sp));
+
+ // If in the included file we have e.g., `mod bar;`,
+ // then the path of `bar.rs` should be relative to the directory of `file`.
+ // See https://github.com/rust-lang/rust/pull/69838/files#r395217057 for a discussion.
+ // `MacroExpander::fully_expand_fragment` later restores, so "stack discipline" is maintained.
+ let dir_path = file.parent().unwrap_or(&file).to_owned();
+ cx.current_expansion.module = Rc::new(cx.current_expansion.module.with_dir_path(dir_path));
+ cx.current_expansion.dir_ownership = DirOwnership::Owned { relative: None };
+
+ struct ExpandResult<'a> {
+ p: Parser<'a>,
+ node_id: ast::NodeId,
+ }
+ impl<'a> base::MacResult for ExpandResult<'a> {
+ fn make_expr(mut self: Box<ExpandResult<'a>>) -> Option<P<ast::Expr>> {
+ let r = base::parse_expr(&mut self.p)?;
+ if self.p.token != token::Eof {
+ self.p.sess.buffer_lint(
+ &INCOMPLETE_INCLUDE,
+ self.p.token.span,
+ self.node_id,
+ "include macro expected single expression in source",
+ );
+ }
+ Some(r)
+ }
+
+ fn make_items(mut self: Box<ExpandResult<'a>>) -> Option<SmallVec<[P<ast::Item>; 1]>> {
+ let mut ret = SmallVec::new();
+ loop {
+ match self.p.parse_item(ForceCollect::No) {
+ Err(mut err) => {
+ err.emit();
+ break;
+ }
+ Ok(Some(item)) => ret.push(item),
+ Ok(None) => {
+ if self.p.token != token::Eof {
+ let token = pprust::token_to_string(&self.p.token);
+ let msg = format!("expected item, found `{}`", token);
+ self.p.struct_span_err(self.p.token.span, &msg).emit();
+ }
+
+ break;
+ }
+ }
+ }
+ Some(ret)
+ }
+ }
+
+ Box::new(ExpandResult { p, node_id: cx.current_expansion.lint_node_id })
+}
+
+// include_str! : read the given file, insert it as a literal string expr
+pub fn expand_include_str(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+ let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_str!") else {
+ return DummyResult::any(sp);
+ };
+ let file = match resolve_path(&cx.sess.parse_sess, file.as_str(), sp) {
+ Ok(f) => f,
+ Err(mut err) => {
+ err.emit();
+ return DummyResult::any(sp);
+ }
+ };
+ match cx.source_map().load_binary_file(&file) {
+ Ok(bytes) => match std::str::from_utf8(&bytes) {
+ Ok(src) => {
+ let interned_src = Symbol::intern(&src);
+ base::MacEager::expr(cx.expr_str(sp, interned_src))
+ }
+ Err(_) => {
+ cx.span_err(sp, &format!("{} wasn't a utf-8 file", file.display()));
+ DummyResult::any(sp)
+ }
+ },
+ Err(e) => {
+ cx.span_err(sp, &format!("couldn't read {}: {}", file.display(), e));
+ DummyResult::any(sp)
+ }
+ }
+}
+
+pub fn expand_include_bytes(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let sp = cx.with_def_site_ctxt(sp);
+ let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_bytes!") else {
+ return DummyResult::any(sp);
+ };
+ let file = match resolve_path(&cx.sess.parse_sess, file.as_str(), sp) {
+ Ok(f) => f,
+ Err(mut err) => {
+ err.emit();
+ return DummyResult::any(sp);
+ }
+ };
+ match cx.source_map().load_binary_file(&file) {
+ Ok(bytes) => base::MacEager::expr(cx.expr_lit(sp, ast::LitKind::ByteStr(bytes.into()))),
+ Err(e) => {
+ cx.span_err(sp, &format!("couldn't read {}: {}", file.display(), e));
+ DummyResult::any(sp)
+ }
+ }
+}
diff --git a/compiler/rustc_builtin_macros/src/standard_library_imports.rs b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
new file mode 100644
index 000000000..09ad5f9b3
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
@@ -0,0 +1,92 @@
+use rustc_ast as ast;
+use rustc_expand::base::{ExtCtxt, ResolverExpand};
+use rustc_expand::expand::ExpansionConfig;
+use rustc_session::Session;
+use rustc_span::edition::Edition::*;
+use rustc_span::hygiene::AstPass;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::DUMMY_SP;
+
+pub fn inject(
+ mut krate: ast::Crate,
+ resolver: &mut dyn ResolverExpand,
+ sess: &Session,
+) -> ast::Crate {
+ 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) {
+ &[sym::core]
+ } else {
+ &[sym::core, sym::compiler_builtins]
+ }
+ } else {
+ &[sym::std]
+ };
+
+ let expn_id = resolver.expansion_for_ast_pass(
+ DUMMY_SP,
+ AstPass::StdImports,
+ &[sym::prelude_import],
+ None,
+ );
+ let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
+ let call_site = DUMMY_SP.with_call_site_ctxt(expn_id.to_expn_id());
+
+ let ecfg = ExpansionConfig::default("std_lib_injection".to_string());
+ let cx = ExtCtxt::new(sess, ecfg, resolver, None);
+
+ // .rev() to preserve ordering above in combination with insert(0, ...)
+ for &name in names.iter().rev() {
+ let ident = if edition >= Edition2018 {
+ Ident::new(name, span)
+ } else {
+ Ident::new(name, call_site)
+ };
+ krate.items.insert(
+ 0,
+ cx.item(
+ span,
+ ident,
+ vec![cx.attribute(cx.meta_word(span, sym::macro_use))],
+ ast::ItemKind::ExternCrate(None),
+ ),
+ );
+ }
+
+ // The crates have been injected, the assumption is that the first one is
+ // the one with the prelude.
+ let name = names[0];
+
+ let root = (edition == Edition2015).then(|| kw::PathRoot);
+
+ let import_path = root
+ .iter()
+ .chain(&[name, sym::prelude])
+ .chain(&[match edition {
+ Edition2015 => sym::rust_2015,
+ Edition2018 => sym::rust_2018,
+ Edition2021 => sym::rust_2021,
+ Edition2024 => sym::rust_2024,
+ }])
+ .map(|&symbol| Ident::new(symbol, span))
+ .collect();
+
+ let use_item = cx.item(
+ span,
+ Ident::empty(),
+ vec![cx.attribute(cx.meta_word(span, sym::prelude_import))],
+ ast::ItemKind::Use(ast::UseTree {
+ prefix: cx.path(span, import_path),
+ kind: ast::UseTreeKind::Glob,
+ span,
+ }),
+ );
+
+ krate.items.insert(0, use_item);
+
+ krate
+}
diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs
new file mode 100644
index 000000000..e20375689
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/test.rs
@@ -0,0 +1,529 @@
+/// 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::attr;
+use rustc_ast::ptr::P;
+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 std::iter;
+
+// #[test_case] is used by custom test authors to mark tests
+// When building for test, it needs to make the item public and gensym the name
+// Otherwise, we'll omit the item. This behavior means that any item annotated
+// with #[test_case] is never addressable.
+//
+// We mark item with an inert attribute "rustc_test_marker" which the test generation
+// logic will pick up on.
+pub fn expand_test_case(
+ ecx: &mut ExtCtxt<'_>,
+ attr_sp: Span,
+ meta_item: &ast::MetaItem,
+ anno_item: Annotatable,
+) -> Vec<Annotatable> {
+ check_builtin_macro_attribute(ecx, meta_item, sym::test_case);
+ warn_on_duplicate_attribute(&ecx, &anno_item, sym::test_case);
+
+ if !ecx.ecfg.should_test {
+ return vec![];
+ }
+
+ let sp = ecx.with_def_site_ctxt(attr_sp);
+ let mut item = anno_item.expect_item();
+ item = item.map(|mut item| {
+ item.vis = ast::Visibility {
+ span: item.vis.span,
+ kind: ast::VisibilityKind::Public,
+ tokens: None,
+ };
+ item.ident.span = item.ident.span.with_ctxt(sp.ctxt());
+ item.attrs.push(ecx.attribute(ecx.meta_word(sp, sym::rustc_test_marker)));
+ item
+ });
+
+ return vec![Annotatable::Item(item)];
+}
+
+pub fn expand_test(
+ cx: &mut ExtCtxt<'_>,
+ attr_sp: Span,
+ meta_item: &ast::MetaItem,
+ item: Annotatable,
+) -> Vec<Annotatable> {
+ check_builtin_macro_attribute(cx, meta_item, sym::test);
+ warn_on_duplicate_attribute(&cx, &item, sym::test);
+ expand_test_or_bench(cx, attr_sp, item, false)
+}
+
+pub fn expand_bench(
+ cx: &mut ExtCtxt<'_>,
+ attr_sp: Span,
+ meta_item: &ast::MetaItem,
+ item: Annotatable,
+) -> Vec<Annotatable> {
+ check_builtin_macro_attribute(cx, meta_item, sym::bench);
+ warn_on_duplicate_attribute(&cx, &item, sym::bench);
+ expand_test_or_bench(cx, attr_sp, item, true)
+}
+
+pub fn expand_test_or_bench(
+ cx: &mut ExtCtxt<'_>,
+ attr_sp: Span,
+ item: Annotatable,
+ is_bench: bool,
+) -> Vec<Annotatable> {
+ // If we're not in test configuration, remove the annotated item
+ if !cx.ecfg.should_test {
+ return vec![];
+ }
+
+ let (item, is_stmt) = match item {
+ Annotatable::Item(i) => (i, false),
+ Annotatable::Stmt(stmt) if matches!(stmt.kind, ast::StmtKind::Item(_)) => {
+ // FIXME: Use an 'if let' guard once they are implemented
+ if let ast::StmtKind::Item(i) = stmt.into_inner().kind {
+ (i, true)
+ } else {
+ unreachable!()
+ }
+ }
+ other => {
+ cx.struct_span_err(
+ other.span(),
+ "`#[test]` attribute is only allowed on non associated functions",
+ )
+ .emit();
+ return vec![other];
+ }
+ };
+
+ // Note: non-associated fn items are already handled by `expand_test_or_bench`
+ if !matches!(item.kind, ast::ItemKind::Fn(_)) {
+ 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(),
+ };
+ err.span_label(attr_sp, "the `#[test]` macro causes a 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
+ // will fail. We shouldn't try to expand in this case because the errors
+ // would be spurious.
+ if (!is_bench && !has_test_signature(cx, &item))
+ || (is_bench && !has_bench_signature(cx, &item))
+ {
+ return vec![Annotatable::Item(item)];
+ }
+
+ let (sp, attr_sp) = (cx.with_def_site_ctxt(item.span), cx.with_def_site_ctxt(attr_sp));
+
+ let test_id = Ident::new(sym::test, attr_sp);
+
+ // creates test::$name
+ let test_path = |name| cx.path(sp, vec![test_id, Ident::from_str_and_span(name, sp)]);
+
+ // creates test::ShouldPanic::$name
+ let should_panic_path = |name| {
+ cx.path(
+ sp,
+ vec![
+ test_id,
+ Ident::from_str_and_span("ShouldPanic", sp),
+ Ident::from_str_and_span(name, sp),
+ ],
+ )
+ };
+
+ // creates test::TestType::$name
+ let test_type_path = |name| {
+ cx.path(
+ sp,
+ vec![
+ test_id,
+ Ident::from_str_and_span("TestType", sp),
+ Ident::from_str_and_span(name, sp),
+ ],
+ )
+ };
+
+ // creates $name: $expr
+ let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr);
+
+ let test_fn = if is_bench {
+ // A simple ident for a lambda
+ let b = Ident::from_str_and_span("b", attr_sp);
+
+ cx.expr_call(
+ sp,
+ cx.expr_path(test_path("StaticBenchFn")),
+ vec![
+ // |b| self::test::assert_test_result(
+ cx.lambda1(
+ sp,
+ cx.expr_call(
+ sp,
+ cx.expr_path(test_path("assert_test_result")),
+ vec![
+ // super::$test_fn(b)
+ cx.expr_call(
+ sp,
+ cx.expr_path(cx.path(sp, vec![item.ident])),
+ vec![cx.expr_ident(sp, b)],
+ ),
+ ],
+ ),
+ b,
+ ), // )
+ ],
+ )
+ } else {
+ cx.expr_call(
+ sp,
+ cx.expr_path(test_path("StaticTestFn")),
+ vec![
+ // || {
+ cx.lambda0(
+ sp,
+ // test::assert_test_result(
+ cx.expr_call(
+ sp,
+ cx.expr_path(test_path("assert_test_result")),
+ vec![
+ // $test_fn()
+ cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![]), // )
+ ],
+ ), // }
+ ), // )
+ ],
+ )
+ };
+
+ let mut test_const = cx.item(
+ sp,
+ Ident::new(item.ident.name, sp),
+ vec![
+ // #[cfg(test)]
+ cx.attribute(attr::mk_list_item(
+ Ident::new(sym::cfg, attr_sp),
+ vec![attr::mk_nested_word_item(Ident::new(sym::test, attr_sp))],
+ )),
+ // #[rustc_test_marker]
+ cx.attribute(cx.meta_word(attr_sp, sym::rustc_test_marker)),
+ ],
+ // 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"),
+ vec![
+ // desc: test::TestDesc {
+ field(
+ "desc",
+ cx.expr_struct(
+ sp,
+ test_path("TestDesc"),
+ vec![
+ // name: "path::to::test"
+ field(
+ "name",
+ cx.expr_call(
+ sp,
+ cx.expr_path(test_path("StaticTestName")),
+ vec![cx.expr_str(
+ sp,
+ Symbol::intern(&item_path(
+ // skip the name of the root module
+ &cx.current_expansion.module.mod_path[1..],
+ &item.ident,
+ )),
+ )],
+ ),
+ ),
+ // ignore: true | false
+ field(
+ "ignore",
+ cx.expr_bool(sp, should_ignore(&cx.sess, &item)),
+ ),
+ // ignore_message: Some("...") | None
+ field(
+ "ignore_message",
+ if let Some(msg) = should_ignore_message(cx, &item) {
+ cx.expr_some(sp, cx.expr_str(sp, msg))
+ } else {
+ cx.expr_none(sp)
+ },
+ ),
+ // compile_fail: true | false
+ field("compile_fail", cx.expr_bool(sp, false)),
+ // no_run: true | false
+ field("no_run", cx.expr_bool(sp, false)),
+ // should_panic: ...
+ field(
+ "should_panic",
+ match should_panic(cx, &item) {
+ // test::ShouldPanic::No
+ ShouldPanic::No => {
+ cx.expr_path(should_panic_path("No"))
+ }
+ // test::ShouldPanic::Yes
+ ShouldPanic::Yes(None) => {
+ cx.expr_path(should_panic_path("Yes"))
+ }
+ // test::ShouldPanic::YesWithMessage("...")
+ ShouldPanic::Yes(Some(sym)) => cx.expr_call(
+ sp,
+ cx.expr_path(should_panic_path("YesWithMessage")),
+ vec![cx.expr_str(sp, sym)],
+ ),
+ },
+ ),
+ // test_type: ...
+ field(
+ "test_type",
+ match test_type(cx) {
+ // test::TestType::UnitTest
+ TestType::UnitTest => {
+ cx.expr_path(test_type_path("UnitTest"))
+ }
+ // test::TestType::IntegrationTest
+ TestType::IntegrationTest => {
+ cx.expr_path(test_type_path("IntegrationTest"))
+ }
+ // test::TestPath::Unknown
+ TestType::Unknown => {
+ cx.expr_path(test_type_path("Unknown"))
+ }
+ },
+ ),
+ // },
+ ],
+ ),
+ ),
+ // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
+ field("testfn", test_fn), // }
+ ],
+ ), // }
+ ),
+ ),
+ );
+ test_const = test_const.map(|mut tc| {
+ tc.vis.kind = ast::VisibilityKind::Public;
+ tc
+ });
+
+ // extern crate test
+ let test_extern = cx.item(sp, test_id, vec![], ast::ItemKind::ExternCrate(None));
+
+ tracing::debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
+
+ if is_stmt {
+ vec![
+ // Access to libtest under a hygienic name
+ Annotatable::Stmt(P(cx.stmt_item(sp, test_extern))),
+ // The generated test case
+ Annotatable::Stmt(P(cx.stmt_item(sp, test_const))),
+ // The original item
+ Annotatable::Stmt(P(cx.stmt_item(sp, item))),
+ ]
+ } else {
+ vec![
+ // Access to libtest under a hygienic name
+ Annotatable::Item(test_extern),
+ // The generated test case
+ Annotatable::Item(test_const),
+ // The original item
+ Annotatable::Item(item),
+ ]
+ }
+}
+
+fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
+ mod_path
+ .iter()
+ .chain(iter::once(item_ident))
+ .map(|x| x.to_string())
+ .collect::<Vec<String>>()
+ .join("::")
+}
+
+enum ShouldPanic {
+ No,
+ Yes(Option<Symbol>),
+}
+
+fn should_ignore(sess: &Session, i: &ast::Item) -> bool {
+ sess.contains_name(&i.attrs, sym::ignore)
+}
+
+fn should_ignore_message(cx: &ExtCtxt<'_>, i: &ast::Item) -> Option<Symbol> {
+ match cx.sess.find_by_name(&i.attrs, sym::ignore) {
+ Some(attr) => {
+ match attr.meta_item_list() {
+ // Handle #[ignore(bar = "foo")]
+ Some(_) => None,
+ // Handle #[ignore] and #[ignore = "message"]
+ None => attr.value_str(),
+ }
+ }
+ None => None,
+ }
+}
+
+fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
+ match cx.sess.find_by_name(&i.attrs, sym::should_panic) {
+ Some(attr) => {
+ let sd = &cx.sess.parse_sess.span_diagnostic;
+
+ match attr.meta_item_list() {
+ // Handle #[should_panic(expected = "foo")]
+ Some(list) => {
+ let msg = list
+ .iter()
+ .find(|mi| mi.has_name(sym::expected))
+ .and_then(|mi| mi.meta_item())
+ .and_then(|mi| mi.value_str());
+ if list.len() != 1 || msg.is_none() {
+ sd.struct_span_warn(
+ attr.span,
+ "argument must be of the form: \
+ `expected = \"error message\"`",
+ )
+ .note(
+ "errors in this attribute were erroneously \
+ allowed and will become a hard error in a \
+ future release",
+ )
+ .emit();
+ ShouldPanic::Yes(None)
+ } else {
+ ShouldPanic::Yes(msg)
+ }
+ }
+ // Handle #[should_panic] and #[should_panic = "expected"]
+ None => ShouldPanic::Yes(attr.value_str()),
+ }
+ }
+ None => ShouldPanic::No,
+ }
+}
+
+enum TestType {
+ UnitTest,
+ IntegrationTest,
+ Unknown,
+}
+
+/// Attempts to determine the type of test.
+/// Since doctests are created without macro expanding, only possible variants here
+/// are `UnitTest`, `IntegrationTest` or `Unknown`.
+fn test_type(cx: &ExtCtxt<'_>) -> TestType {
+ // Root path from context contains the topmost sources directory of the crate.
+ // I.e., for `project` with sources in `src` and tests in `tests` folders
+ // (no matter how many nested folders lie inside),
+ // there will be two different root paths: `/project/src` and `/project/tests`.
+ let crate_path = cx.root_path.as_path();
+
+ if crate_path.ends_with("src") {
+ // `/src` folder contains unit-tests.
+ TestType::UnitTest
+ } else if crate_path.ends_with("tests") {
+ // `/tests` folder contains integration tests.
+ TestType::IntegrationTest
+ } else {
+ // Crate layout doesn't match expected one, test type is unknown.
+ TestType::Unknown
+ }
+}
+
+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 sd = &cx.sess.parse_sess.span_diagnostic;
+ if let ast::ItemKind::Fn(box ast::Fn { ref sig, ref generics, .. }) = i.kind {
+ if let ast::Unsafe::Yes(span) = sig.header.unsafety {
+ sd.struct_span_err(i.span, "unsafe functions cannot be used for tests")
+ .span_label(span, "`unsafe` because of this")
+ .emit();
+ return false;
+ }
+ if let ast::Async::Yes { span, .. } = sig.header.asyncness {
+ sd.struct_span_err(i.span, "async functions cannot be used for tests")
+ .span_label(span, "`async` because of this")
+ .emit();
+ return false;
+ }
+
+ // If the termination trait is active, the compiler will check that the output
+ // type implements the `Termination` trait as `libtest` enforces that.
+ let has_output = match sig.decl.output {
+ ast::FnRetTy::Default(..) => false,
+ ast::FnRetTy::Ty(ref t) if t.kind.is_unit() => false,
+ _ => true,
+ };
+
+ if !sig.decl.inputs.is_empty() {
+ sd.span_err(i.span, "functions used as tests can not have any arguments");
+ return false;
+ }
+
+ match (has_output, has_should_panic_attr) {
+ (true, true) => {
+ sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
+ false
+ }
+ (true, false) => {
+ if !generics.params.is_empty() {
+ sd.span_err(i.span, "functions used as tests must have signature fn() -> ()");
+ false
+ } else {
+ true
+ }
+ }
+ (false, _) => true,
+ }
+ } else {
+ // should be unreachable because `is_test_fn_item` should catch all non-fn items
+ false
+ }
+}
+
+fn has_bench_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
+ let has_sig = if let ast::ItemKind::Fn(box ast::Fn { ref sig, .. }) = i.kind {
+ // N.B., inadequate check, but we're running
+ // well before resolve, can't get too deep.
+ sig.decl.inputs.len() == 1
+ } else {
+ false
+ };
+
+ if !has_sig {
+ cx.sess.parse_sess.span_diagnostic.span_err(
+ i.span,
+ "functions used as benches must have \
+ signature `fn(&mut Bencher) -> impl Termination`",
+ );
+ }
+
+ has_sig
+}
diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs
new file mode 100644
index 000000000..0ebe29df9
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/test_harness.rs
@@ -0,0 +1,390 @@
+// Code that generates a test runner to run all the tests in a crate
+
+use rustc_ast as ast;
+use rustc_ast::entry::EntryPointType;
+use rustc_ast::mut_visit::{ExpectOne, *};
+use rustc_ast::ptr::P;
+use rustc_ast::{attr, ModKind};
+use rustc_expand::base::{ExtCtxt, ResolverExpand};
+use rustc_expand::expand::{AstFragment, ExpansionConfig};
+use rustc_feature::Features;
+use rustc_session::Session;
+use rustc_span::hygiene::{AstPass, SyntaxContext, Transparency};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use rustc_target::spec::PanicStrategy;
+use smallvec::{smallvec, SmallVec};
+use tracing::debug;
+
+use std::{iter, mem};
+
+struct Test {
+ span: Span,
+ ident: Ident,
+}
+
+struct TestCtxt<'a> {
+ ext_cx: ExtCtxt<'a>,
+ panic_strategy: PanicStrategy,
+ def_site: Span,
+ test_cases: Vec<Test>,
+ reexport_test_harness_main: Option<Symbol>,
+ test_runner: Option<ast::Path>,
+}
+
+// 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) {
+ let span_diagnostic = sess.diagnostic();
+ let panic_strategy = sess.panic_strategy();
+ let platform_panic_strategy = sess.target.panic_strategy;
+
+ // Check for #![reexport_test_harness_main = "some_name"] which gives the
+ // main test function the name `some_name` without hygiene. This needs to be
+ // 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);
+
+ // 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);
+
+ if sess.opts.test {
+ let panic_strategy = match (panic_strategy, sess.opts.unstable_opts.panic_abort_tests) {
+ (PanicStrategy::Abort, true) => PanicStrategy::Abort,
+ (PanicStrategy::Abort, false) => {
+ if panic_strategy == platform_panic_strategy {
+ // Silently allow compiling with panic=abort on these platforms,
+ // but with old behavior (abort if a test fails).
+ } else {
+ span_diagnostic.err(
+ "building tests with panic=abort is not supported \
+ without `-Zpanic_abort_tests`",
+ );
+ }
+ PanicStrategy::Unwind
+ }
+ (PanicStrategy::Unwind, _) => PanicStrategy::Unwind,
+ };
+ generate_test_harness(
+ sess,
+ resolver,
+ reexport_test_harness_main,
+ krate,
+ &sess.features_untracked(),
+ panic_strategy,
+ test_runner,
+ )
+ }
+}
+
+struct TestHarnessGenerator<'a> {
+ cx: TestCtxt<'a>,
+ tests: Vec<Test>,
+}
+
+impl TestHarnessGenerator<'_> {
+ fn add_test_cases(&mut self, node_id: ast::NodeId, span: Span, prev_tests: Vec<Test>) {
+ let mut tests = mem::replace(&mut self.tests, prev_tests);
+
+ if !tests.is_empty() {
+ // Create an identifier that will hygienically resolve the test
+ // case name, even in another module.
+ let expn_id = self.cx.ext_cx.resolver.expansion_for_ast_pass(
+ span,
+ AstPass::TestHarness,
+ &[],
+ Some(node_id),
+ );
+ for test in &mut tests {
+ // See the comment on `mk_main` for why we're using
+ // `apply_mark` directly.
+ test.ident.span =
+ test.ident.span.apply_mark(expn_id.to_expn_id(), Transparency::Opaque);
+ }
+ self.cx.test_cases.extend(tests);
+ }
+ }
+}
+
+impl<'a> MutVisitor for TestHarnessGenerator<'a> {
+ fn visit_crate(&mut self, c: &mut ast::Crate) {
+ let prev_tests = mem::take(&mut self.tests);
+ noop_visit_crate(c, self);
+ self.add_test_cases(ast::CRATE_NODE_ID, c.spans.inner_span, prev_tests);
+
+ // Create a main function to run our tests
+ c.items.push(mk_main(&mut self.cx));
+ }
+
+ fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
+ let mut item = i.into_inner();
+ if is_test_case(&self.cx.ext_cx.sess, &item) {
+ debug!("this is a test item");
+
+ let test = Test { span: item.span, ident: item.ident };
+ self.tests.push(test);
+ }
+
+ // We don't want to recurse into anything other than mods, since
+ // mods or tests inside of functions will break things
+ if let ast::ItemKind::Mod(_, ModKind::Loaded(.., ref spans)) = item.kind {
+ let ast::ModSpans { inner_span: span, inject_use_span: _ } = *spans;
+ let prev_tests = mem::take(&mut self.tests);
+ noop_visit_item_kind(&mut item.kind, self);
+ self.add_test_cases(item.id, span, prev_tests);
+ }
+ smallvec![P(item)]
+ }
+}
+
+// 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 {
+ match item.kind {
+ ast::ItemKind::Fn(..) => {
+ if sess.contains_name(&item.attrs, sym::start) {
+ EntryPointType::Start
+ } else if sess.contains_name(&item.attrs, sym::rustc_main) {
+ EntryPointType::RustcMainAttr
+ } else if item.ident.name == sym::main {
+ if depth == 0 {
+ // This is a top-level function so can be 'main'
+ EntryPointType::MainNamed
+ } else {
+ EntryPointType::OtherMain
+ }
+ } else {
+ EntryPointType::None
+ }
+ }
+ _ => EntryPointType::None,
+ }
+}
+/// A folder used to remove any entry points (like fn main) because the harness
+/// generator will provide its own
+struct EntryPointCleaner<'a> {
+ // Current depth in the ast
+ sess: &'a Session,
+ depth: usize,
+ def_site: Span,
+}
+
+impl<'a> MutVisitor for EntryPointCleaner<'a> {
+ fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
+ self.depth += 1;
+ let item = noop_flat_map_item(i, self).expect_one("noop did something");
+ self.depth -= 1;
+
+ // 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) {
+ EntryPointType::MainNamed | EntryPointType::RustcMainAttr | EntryPointType::Start => {
+ item.map(|ast::Item { id, ident, attrs, kind, vis, span, tokens }| {
+ let allow_ident = Ident::new(sym::allow, self.def_site);
+ let dc_nested =
+ attr::mk_nested_word_item(Ident::new(sym::dead_code, self.def_site));
+ let allow_dead_code_item = attr::mk_list_item(allow_ident, vec![dc_nested]);
+ let allow_dead_code = attr::mk_attr_outer(allow_dead_code_item);
+ let attrs = attrs
+ .into_iter()
+ .filter(|attr| {
+ !attr.has_name(sym::rustc_main) && !attr.has_name(sym::start)
+ })
+ .chain(iter::once(allow_dead_code))
+ .collect();
+
+ ast::Item { id, ident, attrs, kind, vis, span, tokens }
+ })
+ }
+ EntryPointType::None | EntryPointType::OtherMain => item,
+ };
+
+ smallvec![item]
+ }
+}
+
+/// Crawl over the crate, inserting test reexports and the test main function
+fn generate_test_harness(
+ sess: &Session,
+ resolver: &mut dyn ResolverExpand,
+ reexport_test_harness_main: Option<Symbol>,
+ krate: &mut ast::Crate,
+ features: &Features,
+ panic_strategy: PanicStrategy,
+ test_runner: Option<ast::Path>,
+) {
+ let mut econfig = ExpansionConfig::default("test".to_string());
+ econfig.features = Some(features);
+
+ let ext_cx = ExtCtxt::new(sess, econfig, resolver, None);
+
+ let expn_id = ext_cx.resolver.expansion_for_ast_pass(
+ DUMMY_SP,
+ AstPass::TestHarness,
+ &[sym::test, sym::rustc_attrs],
+ None,
+ );
+ let def_site = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
+
+ // Remove the entry points
+ let mut cleaner = EntryPointCleaner { sess, depth: 0, def_site };
+ cleaner.visit_crate(krate);
+
+ let cx = TestCtxt {
+ ext_cx,
+ panic_strategy,
+ def_site,
+ test_cases: Vec::new(),
+ reexport_test_harness_main,
+ test_runner,
+ };
+
+ TestHarnessGenerator { cx, tests: Vec::new() }.visit_crate(krate);
+}
+
+/// Creates a function item for use as the main function of a test build.
+/// This function will call the `test_runner` as specified by the crate attribute
+///
+/// By default this expands to
+///
+/// ```ignore UNSOLVED (I think I still need guidance for this one. Is it correct? Do we try to make it run? How do we nicely fill it out?)
+/// #[rustc_main]
+/// pub fn main() {
+/// extern crate test;
+/// test::test_main_static(&[
+/// &test_const1,
+/// &test_const2,
+/// &test_const3,
+/// ]);
+/// }
+/// ```
+///
+/// Most of the Ident have the usual def-site hygiene for the AST pass. The
+/// exception is the `test_const`s. These have a syntax context that has two
+/// opaque marks: one from the expansion of `test` or `test_case`, and one
+/// generated in `TestHarnessGenerator::flat_map_item`. When resolving this
+/// identifier after failing to find a matching identifier in the root module
+/// we remove the outer mark, and try resolving at its def-site, which will
+/// then resolve to `test_const`.
+///
+/// The expansion here can be controlled by two attributes:
+///
+/// [`TestCtxt::reexport_test_harness_main`] provides a different name for the `main`
+/// function and [`TestCtxt::test_runner`] provides a path that replaces
+/// `test::test_main_static`.
+fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
+ let sp = cx.def_site;
+ let ecx = &cx.ext_cx;
+ let test_id = Ident::new(sym::test, sp);
+
+ let runner_name = match cx.panic_strategy {
+ PanicStrategy::Unwind => "test_main_static",
+ PanicStrategy::Abort => "test_main_static_abort",
+ };
+
+ // test::test_main_static(...)
+ let mut test_runner = cx
+ .test_runner
+ .clone()
+ .unwrap_or_else(|| ecx.path(sp, vec![test_id, Ident::from_str_and_span(runner_name, sp)]));
+
+ test_runner.span = sp;
+
+ let test_main_path_expr = ecx.expr_path(test_runner);
+ let call_test_main = ecx.expr_call(sp, test_main_path_expr, vec![mk_tests_slice(cx, sp)]);
+ let call_test_main = ecx.stmt_expr(call_test_main);
+
+ // extern crate test
+ let test_extern_stmt =
+ ecx.stmt_item(sp, ecx.item(sp, test_id, vec![], ast::ItemKind::ExternCrate(None)));
+
+ // #[rustc_main]
+ let main_meta = ecx.meta_word(sp, sym::rustc_main);
+ let main_attr = ecx.attribute(main_meta);
+
+ // pub fn main() { ... }
+ let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![]));
+
+ // If no test runner is provided we need to import the test crate
+ let main_body = if cx.test_runner.is_none() {
+ ecx.block(sp, vec![test_extern_stmt, call_test_main])
+ } else {
+ ecx.block(sp, vec![call_test_main])
+ };
+
+ let decl = ecx.fn_decl(vec![], ast::FnRetTy::Ty(main_ret_ty));
+ let sig = ast::FnSig { decl, header: ast::FnHeader::default(), span: sp };
+ let defaultness = ast::Defaultness::Final;
+ let main = ast::ItemKind::Fn(Box::new(ast::Fn {
+ defaultness,
+ sig,
+ generics: ast::Generics::default(),
+ body: Some(main_body),
+ }));
+
+ // Honor the reexport_test_harness_main attribute
+ let main_id = match cx.reexport_test_harness_main {
+ Some(sym) => Ident::new(sym, sp.with_ctxt(SyntaxContext::root())),
+ None => Ident::new(sym::main, sp),
+ };
+
+ let main = P(ast::Item {
+ ident: main_id,
+ attrs: vec![main_attr],
+ id: ast::DUMMY_NODE_ID,
+ kind: main,
+ vis: ast::Visibility { span: sp, kind: ast::VisibilityKind::Public, tokens: None },
+ span: sp,
+ tokens: None,
+ });
+
+ // Integrate the new item into existing module structures.
+ let main = AstFragment::Items(smallvec![main]);
+ cx.ext_cx.monotonic_expander().fully_expand_fragment(main).make_items().pop().unwrap()
+}
+
+/// Creates a slice containing every test like so:
+/// &[&test1, &test2]
+fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> {
+ debug!("building test vector from {} tests", cx.test_cases.len());
+ let ecx = &cx.ext_cx;
+
+ ecx.expr_array_ref(
+ sp,
+ cx.test_cases
+ .iter()
+ .map(|test| {
+ ecx.expr_addr_of(test.span, ecx.expr_path(ecx.path(test.span, vec![test.ident])))
+ })
+ .collect(),
+ )
+}
+
+fn is_test_case(sess: &Session, i: &ast::Item) -> bool {
+ sess.contains_name(&i.attrs, sym::rustc_test_marker)
+}
+
+fn get_test_runner(
+ sess: &Session,
+ sd: &rustc_errors::Handler,
+ krate: &ast::Crate,
+) -> Option<ast::Path> {
+ let test_attr = sess.find_by_name(&krate.attrs, sym::test_runner)?;
+ let meta_list = test_attr.meta_item_list()?;
+ let span = test_attr.span;
+ match &*meta_list {
+ [single] => match single.meta_item() {
+ Some(meta_item) if meta_item.is_word() => return Some(meta_item.path.clone()),
+ _ => {
+ sd.struct_span_err(span, "`test_runner` argument must be a path").emit();
+ }
+ },
+ _ => {
+ sd.struct_span_err(span, "`#![test_runner(..)]` accepts exactly 1 argument").emit();
+ }
+ }
+ None
+}
diff --git a/compiler/rustc_builtin_macros/src/trace_macros.rs b/compiler/rustc_builtin_macros/src/trace_macros.rs
new file mode 100644
index 000000000..cc5ae6894
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/trace_macros.rs
@@ -0,0 +1,29 @@
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_expand::base::{self, ExtCtxt};
+use rustc_span::symbol::kw;
+use rustc_span::Span;
+
+pub fn expand_trace_macros(
+ cx: &mut ExtCtxt<'_>,
+ sp: Span,
+ tt: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+ let mut cursor = tt.into_trees();
+ let mut err = false;
+ let value = match &cursor.next() {
+ Some(TokenTree::Token(token, _)) if token.is_keyword(kw::True) => true,
+ Some(TokenTree::Token(token, _)) if token.is_keyword(kw::False) => false,
+ _ => {
+ err = true;
+ false
+ }
+ };
+ err |= cursor.next().is_some();
+ if err {
+ cx.span_err(sp, "trace_macros! accepts only `true` or `false`")
+ } else {
+ cx.set_trace_macros(value);
+ }
+
+ base::DummyResult::any_valid(sp)
+}
diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs
new file mode 100644
index 000000000..527fe50ef
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/util.rs
@@ -0,0 +1,43 @@
+use rustc_ast::{Attribute, MetaItem};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_feature::AttributeTemplate;
+use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES;
+use rustc_parse::validate_attr;
+use rustc_span::Symbol;
+
+pub 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() };
+ let attr = ecx.attribute(meta_item.clone());
+ validate_attr::check_builtin_attribute(&ecx.sess.parse_sess, &attr, name, template);
+}
+
+/// 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 fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) {
+ let attrs: Option<&[Attribute]> = match item {
+ Annotatable::Item(item) => Some(&item.attrs),
+ Annotatable::TraitItem(item) => Some(&item.attrs),
+ Annotatable::ImplItem(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(&param.attrs),
+ Annotatable::Param(param) => Some(&param.attrs),
+ Annotatable::FieldDef(def) => Some(&def.attrs),
+ Annotatable::Variant(variant) => Some(&variant.attrs),
+ _ => None,
+ };
+ if let Some(attrs) = attrs {
+ if let Some(attr) = ecx.sess.find_by_name(attrs, name) {
+ ecx.parse_sess().buffer_lint(
+ DUPLICATE_MACRO_ATTRIBUTES,
+ attr.span,
+ ecx.current_expansion.lint_node_id,
+ "duplicated attribute",
+ );
+ }
+ }
+}