diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_builtin_macros | |
parent | Initial commit. (diff) | |
download | rustc-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')
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(¬e); + } + 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(¶m), + Annotatable::Param(param) => finder.visit_param(¶m), + 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 ¶m.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(¬e); + } + 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, ¯os); + 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(¶m.attrs), + Annotatable::Param(param) => Some(¶m.attrs), + Annotatable::FieldDef(def) => Some(&def.attrs), + Annotatable::Variant(variant) => Some(&variant.attrs), + _ => None, + }; + if let Some(attrs) = attrs { + if let Some(attr) = 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", + ); + } + } +} |