diff options
Diffstat (limited to 'compiler/rustc_builtin_macros/src')
46 files changed, 12163 insertions, 0 deletions
diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs new file mode 100644 index 00000000000..4721e74b955 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs @@ -0,0 +1,96 @@ +use crate::errors; +use crate::util::check_builtin_macro_attribute; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, FnHeader, FnSig, Generics, StmtKind}; +use rustc_ast::{Fn, ItemKind, Safety, Stmt, TyKind}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{kw, sym, Ident}; +use rustc_span::Span; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) fn expand( + ecx: &mut ExtCtxt<'_>, + _span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec<Annotatable> { + check_builtin_macro_attribute(ecx, meta_item, sym::alloc_error_handler); + + let orig_item = item.clone(); + + // Allow using `#[alloc_error_handler]` on an item statement + // FIXME - if we get deref patterns, use them to reduce duplication here + let (item, is_stmt, sig_span) = if let Annotatable::Item(item) = &item + && let ItemKind::Fn(fn_kind) = &item.kind + { + (item, false, ecx.with_def_site_ctxt(fn_kind.sig.span)) + } else if let Annotatable::Stmt(stmt) = &item + && let StmtKind::Item(item) = &stmt.kind + && let ItemKind::Fn(fn_kind) = &item.kind + { + (item, true, ecx.with_def_site_ctxt(fn_kind.sig.span)) + } else { + ecx.dcx().emit_err(errors::AllocErrorMustBeFn { span: item.span() }); + return vec![orig_item]; + }; + + // Generate a bunch of new items using the AllocFnFactory + let span = ecx.with_def_site_ctxt(item.span); + + // Generate item statements for the allocator methods. + let stmts = thin_vec![generate_handler(ecx, item.ident, span, sig_span)]; + + // Generate anonymous constant serving as container for the allocator methods. + let const_ty = ecx.ty(sig_span, TyKind::Tup(ThinVec::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] +} + +// #[rustc_std_internal_symbol] +// unsafe fn __rg_oom(size: usize, align: usize) -> ! { +// handler(core::alloc::Layout::from_size_align_unchecked(size, align)) +// } +fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span) -> Stmt { + let usize = cx.path_ident(span, Ident::new(sym::usize, span)); + let ty_usize = cx.ty_path(usize); + let size = Ident::from_str_and_span("size", span); + let align = Ident::from_str_and_span("align", span); + + let layout_new = cx.std_path(&[sym::alloc, sym::Layout, sym::from_size_align_unchecked]); + let layout_new = cx.expr_path(cx.path(span, layout_new)); + let layout = cx.expr_call( + span, + layout_new, + thin_vec![cx.expr_ident(span, size), cx.expr_ident(span, align)], + ); + + let call = cx.expr_call_ident(sig_span, handler, thin_vec![layout]); + + let never = ast::FnRetTy::Ty(cx.ty(span, TyKind::Never)); + let params = thin_vec![cx.param(span, size, ty_usize.clone()), cx.param(span, align, ty_usize)]; + let decl = cx.fn_decl(params, never); + let header = FnHeader { safety: Safety::Unsafe(span), ..FnHeader::default() }; + let sig = FnSig { decl, header, span: span }; + + let body = Some(cx.block_expr(call)); + let kind = ItemKind::Fn(Box::new(Fn { + defaultness: ast::Defaultness::Final, + sig, + generics: Generics::default(), + body, + })); + + let attrs = thin_vec![cx.attr_word(sym::rustc_std_internal_symbol, span)]; + + let item = cx.item(span, Ident::from_str_and_span("__rg_oom", span), attrs, kind); + cx.stmt_item(sig_span, item) +} diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs new file mode 100644 index 00000000000..64238e81b26 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -0,0 +1,808 @@ +use crate::errors; +use crate::util::expr_to_spanned_string; +use ast::token::IdentIsRaw; +use lint::BuiltinLintDiag; +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, FxIndexMap}; +use rustc_errors::PResult; +use rustc_expand::base::*; +use rustc_index::bit_set::GrowableBitSet; +use rustc_parse::parser::Parser; +use rustc_parse_format as parse; +use rustc_session::lint; +use rustc_span::symbol::Ident; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::{ErrorGuaranteed, 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: FxIndexMap<Symbol, usize>, + reg_args: GrowableBitSet<usize>, + pub clobber_abis: Vec<(Symbol, Span)>, + options: ast::InlineAsmOptions, + pub options_spans: Vec<Span>, +} + +fn parse_args<'a>( + ecx: &ExtCtxt<'a>, + sp: Span, + tts: TokenStream, + is_global_asm: bool, +) -> PResult<'a, AsmArgs> { + let mut p = ecx.new_parser_from_tts(tts); + parse_asm_args(&mut p, 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>, + sp: Span, + is_global_asm: bool, +) -> PResult<'a, AsmArgs> { + let dcx = p.dcx(); + + if p.token == token::Eof { + return Err(dcx.create_err(errors::AsmRequiresTemplate { span: sp })); + } + + let first_template = p.parse_expr()?; + let mut args = AsmArgs { + templates: vec![first_template], + operands: vec![], + named_args: Default::default(), + reg_args: Default::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... + return Err(dcx.create_err(errors::AsmExpectedComma { span: p.token.span })); + } 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 = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span }); + 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 = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span }); + 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 = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span }); + 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_expr_anon_const()?; + 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 = dcx.create_err(errors::AsmSymNoPath { span: expr.span }); + return Err(err); + }; + let sym = ast::InlineAsmSym { + id: ast::DUMMY_NODE_ID, + qself: qself.clone(), + path: path.clone(), + }; + ast::InlineAsmOperand::Sym { sym } + } else if !is_global_asm && p.eat_keyword(sym::label) { + let block = p.parse_block()?; + ast::InlineAsmOperand::Label { block } + } 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(token_lit) + if matches!( + token_lit.kind, + token::LitKind::Str | token::LitKind::StrRaw(_) + ) => {} + ast::ExprKind::MacCall(..) => {} + _ => { + let err = dcx.create_err(errors::AsmExpectedOther { + span: template.span, + is_global_asm, + }); + return Err(err); + } + } + args.templates.push(template); + continue; + } else { + p.unexpected_any()? + }; + + 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 explicit_reg { + if name.is_some() { + dcx.emit_err(errors::AsmExplicitRegisterName { span }); + } + args.reg_args.insert(slot); + } else if let Some(name) = name { + if let Some(&prev) = args.named_args.get(&name) { + dcx.emit_err(errors::AsmDuplicateArg { span, name, prev: args.operands[prev].1 }); + continue; + } + args.named_args.insert(name, slot); + } else { + if !args.named_args.is_empty() || !args.reg_args.is_empty() { + let named = args.named_args.values().map(|p| args.operands[*p].1).collect(); + let explicit = args.reg_args.iter().map(|p| args.operands[p].1).collect(); + + dcx.emit_err(errors::AsmPositionalAfter { span, named, explicit }); + } + } + } + + if args.options.contains(ast::InlineAsmOptions::NOMEM) + && args.options.contains(ast::InlineAsmOptions::READONLY) + { + let spans = args.options_spans.clone(); + dcx.emit_err(errors::AsmMutuallyExclusive { spans, opt1: "nomem", opt2: "readonly" }); + } + if args.options.contains(ast::InlineAsmOptions::PURE) + && args.options.contains(ast::InlineAsmOptions::NORETURN) + { + let spans = args.options_spans.clone(); + dcx.emit_err(errors::AsmMutuallyExclusive { spans, opt1: "pure", opt2: "noreturn" }); + } + if args.options.contains(ast::InlineAsmOptions::PURE) + && !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY) + { + let spans = args.options_spans.clone(); + dcx.emit_err(errors::AsmPureCombine { spans }); + } + + let mut have_real_output = false; + let mut outputs_sp = vec![]; + let mut regclass_outputs = vec![]; + let mut labels_sp = 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); + } + } + ast::InlineAsmOperand::Label { .. } => { + labels_sp.push(*op_sp); + } + _ => {} + } + } + if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output { + dcx.emit_err(errors::AsmPureNoOutput { spans: args.options_spans.clone() }); + } + if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() { + let err = dcx.create_err(errors::AsmNoReturn { outputs_sp }); + // Bail out now since this is likely to confuse MIR + return Err(err); + } + if args.options.contains(ast::InlineAsmOptions::MAY_UNWIND) && !labels_sp.is_empty() { + dcx.emit_err(errors::AsmMayUnwind { labels_sp }); + } + + if args.clobber_abis.len() > 0 { + if is_global_asm { + let err = dcx.create_err(errors::GlobalAsmClobberAbi { + spans: args.clobber_abis.iter().map(|(_, span)| *span).collect(), + }); + + // Bail out now since this is likely to confuse later stages + return Err(err); + } + if !regclass_outputs.is_empty() { + dcx.emit_err(errors::AsmClobberNoReg { + spans: regclass_outputs, + clobbers: args.clobber_abis.iter().map(|(_, span)| *span).collect(), + }); + } + } + + 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(p: &Parser<'_>, symbol: Symbol, span: Span) { + // Tool-only output + let full_span = if p.token.kind == token::Comma { span.to(p.token.span) } else { span }; + p.dcx().emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); +} + +/// 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: &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)) { + return Err(p.dcx().create_err(errors::NonABI { span: p.token.span })); + } + + let mut new_abis = Vec::new(); + while !p.eat(&token::CloseDelim(Delimiter::Parenthesis)) { + match p.parse_str_lit() { + Ok(str_lit) => { + new_abis.push((str_lit.symbol_unescaped, str_lit.span)); + } + Err(opt_lit) => { + let span = opt_lit.map_or(p.token.span, |lit| lit.span); + let mut err = p.dcx().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); + + 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, IdentIsRaw::No) => ast::InlineAsmRegOrRegClass::RegClass(name), + token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => { + *explicit_reg = true; + ast::InlineAsmRegOrRegClass::Reg(symbol) + } + _ => { + return Err(p.dcx().create_err(errors::ExpectedRegisterClassOrExplicitRegister { + span: p.token.span, + })); + } + }; + p.bump(); + p.expect(&token::CloseDelim(Delimiter::Parenthesis))?; + Ok(result) +} + +fn expand_preparsed_asm( + ecx: &mut ExtCtxt<'_>, + args: AsmArgs, +) -> ExpandResult<Result<ast::InlineAsm, ErrorGuaranteed>, ()> { + 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.iter() { + 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) = { + let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, template_expr, msg) else { + return ExpandResult::Retry(()); + }; + match mac { + Ok(template_part) => template_part, + Err(err) => { + return ExpandResult::Ready(Err(match err { + Ok((err, _)) => err.emit(), + Err(guar) => guar, + })); + } + } + }; + + 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_deref().map(Symbol::intern), + 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.psess().buffer_lint( + lint::builtin::BAD_ASM_STYLE, + find_span(".intel_syntax"), + ecx.current_expansion.lint_node_id, + BuiltinLintDiag::AvoidUsingIntelSyntax, + ); + } + if template_str.contains(".att_syntax") { + ecx.psess().buffer_lint( + lint::builtin::BAD_ASM_STYLE, + find_span(".att_syntax"), + ecx.current_expansion.lint_node_id, + BuiltinLintDiag::AvoidUsingAttSyntax, + ); + } + } + + // 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.dcx().struct_span_err(err_sp, msg); + e.span_label(err_sp, err.label + " in asm template string"); + if let Some(note) = err.note { + e.note(note); + } + if let Some((label, span)) = err.secondary_label { + let err_sp = template_span.from_inner(InnerSpan::new(span.start, span.end)); + e.span_label(err_sp, label); + } + let guar = e.emit(); + return ExpandResult::Ready(Err(guar)); + } + + 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.dcx().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 {positional}arguments were given"), + 1 => format!("there is 1 {positional}argument"), + x => format!("there are {x} {positional}arguments"), + }; + 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.span_help( + args.operands[idx].1, + "use the register name directly in the assembly code", + ); + } + 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.dcx() + .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.dcx().emit_err(errors::AsmModifierInvalid { span }); + 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(); + ecx.dcx() + .struct_span_err(sp, msg) + .with_span_label(sp, msg) + .with_help(format!( + "if this argument is intentionally unused, \ + consider using it in an asm comment: `\"/*{help_str} */\"`" + )) + .emit(); + } + _ => { + let mut err = ecx.dcx().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(); + } + } + + ExpandResult::Ready(Ok(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, +) -> MacroExpanderResult<'cx> { + ExpandResult::Ready(match parse_args(ecx, sp, tts, false) { + Ok(args) => { + let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, args) else { + return ExpandResult::Retry(()); + }; + let expr = match mac { + Ok(inline_asm) => P(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind: ast::ExprKind::InlineAsm(P(inline_asm)), + span: sp, + attrs: ast::AttrVec::new(), + tokens: None, + }), + Err(guar) => DummyResult::raw_expr(sp, Some(guar)), + }; + MacEager::expr(expr) + } + Err(err) => { + let guar = err.emit(); + DummyResult::any(sp, guar) + } + }) +} + +pub(super) fn expand_global_asm<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + ExpandResult::Ready(match parse_args(ecx, sp, tts, true) { + Ok(args) => { + let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, args) else { + return ExpandResult::Retry(()); + }; + match mac { + Ok(inline_asm) => MacEager::items(smallvec![P(ast::Item { + ident: Ident::empty(), + attrs: ast::AttrVec::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: sp, + tokens: None, + })]), + Err(guar) => DummyResult::any(sp, guar), + } + } + Err(err) => { + let guar = err.emit(); + DummyResult::any(sp, guar) + } + }) +} diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs new file mode 100644 index 00000000000..c75050f2701 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -0,0 +1,163 @@ +mod context; + +use crate::edition_panic::use_panic_2021; +use crate::errors; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::token::Delimiter; +use rustc_ast::tokenstream::{DelimSpan, TokenStream}; +use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp}; +use rustc_ast_pretty::pprust; +use rustc_errors::PResult; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; +use rustc_parse::parser::Parser; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use thin_vec::thin_vec; + +pub(crate) fn expand_assert<'cx>( + cx: &'cx mut ExtCtxt<'_>, + span: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) { + Ok(assert) => assert, + Err(err) => { + let guar = err.emit(); + return ExpandResult::Ready(DummyResult::any(span, guar)); + } + }; + + // `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(P(MacCall { + path: panic_path(), + args: P(DelimArgs { + dspan: DelimSpan::from_single(call_site_span), + delim: Delimiter::Parenthesis, + tokens, + }), + })), + ); + 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 cx.ecfg.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]), + thin_vec![cx.expr_str( + DUMMY_SP, + Symbol::intern(&format!( + "assertion failed: {}", + pprust::expr_to_string(&cond_expr) + )), + )], + ); + expr_if_not(cx, call_site_span, cond_expr, then, None) + }; + + ExpandResult::Ready(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: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> { + let mut parser = cx.new_parser_from_tts(stream); + + if parser.token == token::Eof { + return Err(cx.dcx().create_err(errors::AssertRequiresBoolean { span: sp })); + } + + 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 { + cx.dcx().emit_err(errors::AssertRequiresExpression { span: sp, token: parser.token.span }); + 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 comma = parser.prev_token.span.shrink_to_hi(); + cx.dcx().emit_err(errors::AssertMissingComma { span: parser.token.span, comma }); + + parse_custom_message(&mut parser) + } else if parser.eat(&token::Comma) { + parse_custom_message(&mut parser) + } else { + None + }; + + if parser.token != token::Eof { + 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 00000000000..a98cb6f0f76 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -0,0 +1,462 @@ +use rustc_ast::{ + ptr::P, + token::{self, Delimiter, IdentIsRaw}, + tokenstream::{DelimSpan, TokenStream, TokenTree}, + BinOpKind, BorrowKind, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MethodCall, 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, +}; +use thin_vec::{thin_vec, ThinVec}; + +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 = ThinVec::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 = ThinVec::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), + span: this.span, + }, + DUMMY_NODE_ID, + ) + }; + self.cx.stmt_item( + self.span, + self.cx.item( + self.span, + Ident::empty(), + thin_vec![self.cx.attr_nested_word(sym::allow, sym::unused_imports, self.span)], + ItemKind::Use(UseTree { + prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])), + kind: UseTreeKind::Nested { + items: thin_vec![ + nested_tree(self, sym::TryCaptureGeneric), + nested_tree(self, sym::TryCapturePrintable), + ], + span: self.span, + }, + 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)), + thin_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_joint( + 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_joint( + token::Ident(cap.ident.name, IdentIsRaw::No), + cap.ident.span, + ), + TokenTree::token_alone(token::Comma, self.span), + ] + }); + self.cx.expr( + self.span, + ExprKind::MacCall(P(MacCall { + path: panic_path, + args: P(DelimArgs { + dspan: DelimSpan::from_single(self.span), + delim: Delimiter::Parenthesis, + tokens: initial.into_iter().chain(captures).collect::<TokenStream>(), + }), + })), + ) + } + + /// 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 &mut expr.kind { + ExprKind::AddrOf(_, mutability, local_expr) => { + self.with_is_consumed_management(matches!(mutability, Mutability::Mut), |this| { + this.manage_cond_expr(local_expr) + }); + } + ExprKind::Array(local_exprs) => { + for local_expr in local_exprs { + self.manage_cond_expr(local_expr); + } + } + ExprKind::Binary(op, lhs, 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(_, local_exprs) => { + for local_expr in local_exprs { + self.manage_cond_expr(local_expr); + } + } + ExprKind::Cast(local_expr, _) => { + self.manage_cond_expr(local_expr); + } + ExprKind::If(local_expr, _, _) => { + self.manage_cond_expr(local_expr); + } + ExprKind::Index(prefix, suffix, _) => { + self.manage_cond_expr(prefix); + self.manage_cond_expr(suffix); + } + ExprKind::Let(_, local_expr, _, _) => { + self.manage_cond_expr(local_expr); + } + ExprKind::Match(local_expr, ..) => { + self.manage_cond_expr(local_expr); + } + ExprKind::MethodCall(call) => { + for arg in &mut call.args { + self.manage_cond_expr(arg); + } + } + ExprKind::Path(_, Path { segments, .. }) if let [path_segment] = &segments[..] => { + let path_ident = path_segment.ident; + self.manage_initial_capture(expr, path_ident); + } + ExprKind::Paren(local_expr) => { + self.manage_cond_expr(local_expr); + } + ExprKind::Range(prefix, suffix, _) => { + if let Some(elem) = prefix { + self.manage_cond_expr(elem); + } + if let Some(elem) = suffix { + self.manage_cond_expr(elem); + } + } + ExprKind::Repeat(local_expr, elem) => { + self.manage_cond_expr(local_expr); + self.manage_cond_expr(&mut elem.value); + } + ExprKind::Struct(elem) => { + for field in &mut elem.fields { + self.manage_cond_expr(&mut field.expr); + } + if let StructRest::Base(local_expr) = &mut elem.rest { + self.manage_cond_expr(local_expr); + } + } + ExprKind::Tup(local_exprs) => { + for local_expr in local_exprs { + self.manage_cond_expr(local_expr); + } + } + ExprKind::Unary(un_op, 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::Gen(_, _, _) + | ExprKind::Await(_, _) + | ExprKind::Block(_, _) + | ExprKind::Break(_, _) + | ExprKind::Closure(_) + | ExprKind::ConstBlock(_) + | ExprKind::Continue(_) + | ExprKind::Dummy + | ExprKind::Err(_) + | ExprKind::Field(_, _) + | ExprKind::ForLoop { .. } + | ExprKind::FormatArgs(_) + | ExprKind::IncludedBytes(..) + | ExprKind::InlineAsm(_) + | ExprKind::Lit(_) + | ExprKind::Loop(_, _, _) + | ExprKind::MacCall(_) + | ExprKind::OffsetOf(_, _) + | ExprKind::Path(_, _) + | ExprKind::Ret(_) + | ExprKind::Try(_) + | ExprKind::TryBlock(_) + | ExprKind::Type(_, _) + | ExprKind::Underscore + | ExprKind::While(_, _, _) + | ExprKind::Yeet(_) + | ExprKind::Become(_) + | 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)), + ThinVec::new(), + ); + 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])), + ), + thin_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), + }, + expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)), + thin_vec![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, thin_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<'_>, + seg: PathSegment, + receiver: P<Expr>, + args: ThinVec<P<Expr>>, + span: Span, +) -> P<Expr> { + cx.expr(span, ExprKind::MethodCall(Box::new(MethodCall { seg, receiver, 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 00000000000..827719d7944 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -0,0 +1,54 @@ +//! 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 crate::errors; +use rustc_ast as ast; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_attr as attr; +use rustc_errors::PResult; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; +use rustc_span::Span; + +pub(crate) fn expand_cfg( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + + ExpandResult::Ready(match parse_cfg(cx, sp, tts) { + Ok(cfg) => { + let matches_cfg = attr::cfg_matches( + &cfg, + &cx.sess, + cx.current_expansion.lint_node_id, + Some(cx.ecfg.features), + ); + MacEager::expr(cx.expr_bool(sp, matches_cfg)) + } + Err(err) => { + let guar = err.emit(); + DummyResult::any(sp, guar) + } + }) +} + +fn parse_cfg<'a>(cx: &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.dcx().create_err(errors::RequiresCfgPattern { span })); + } + + let cfg = p.parse_meta_item()?; + + let _ = p.eat(&token::Comma); + + if !p.eat(&token::Eof) { + return Err(cx.dcx().create_err(errors::OneCfgPattern { span })); + } + + Ok(cfg) +} diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs new file mode 100644 index 00000000000..98c0ca3a526 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs @@ -0,0 +1,70 @@ +//! Implementation of the `#[cfg_accessible(path)]` attribute macro. + +use crate::errors; +use rustc_ast as ast; +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier}; +use rustc_feature::AttributeTemplate; +use rustc_parse::validate_attr; +use rustc_span::symbol::sym; +use rustc_span::Span; + +pub(crate) struct Expander; + +fn validate_input<'a>(ecx: &ExtCtxt<'_>, mi: &'a ast::MetaItem) -> Option<&'a ast::Path> { + use errors::CfgAccessibleInvalid::*; + match mi.meta_item_list() { + None => {} + Some([]) => { + ecx.dcx().emit_err(UnspecifiedPath(mi.span)); + } + Some([_, .., l]) => { + ecx.dcx().emit_err(MultiplePaths(l.span())); + } + Some([nmi]) => match nmi.meta_item() { + None => { + ecx.dcx().emit_err(LiteralPath(nmi.span())); + } + Some(mi) => { + if !mi.is_word() { + ecx.dcx().emit_err(HasArguments(mi.span)); + } + return Some(&mi.path); + } + }, + } + None +} + +impl MultiItemModifier for Expander { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + _is_derive_const: bool, + ) -> ExpandResult<Vec<Annotatable>, Annotatable> { + let template = AttributeTemplate { list: Some("path"), ..Default::default() }; + validate_attr::check_builtin_meta_item( + &ecx.sess.psess, + meta_item, + ast::AttrStyle::Outer, + 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.dcx().emit_err(errors::CfgAccessibleIndeterminate { span }); + 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 00000000000..03aff6f9633 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -0,0 +1,287 @@ +use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; + +use core::ops::ControlFlow; +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_errors::PResult; +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; +use tracing::instrument; + +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: &Features, + annotatable: Annotatable, + lint_node_id: NodeId, +) -> Annotatable { + let features = Some(features); + 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)) + } + } +} + +fn has_cfg_or_cfg_attr(annotatable: &Annotatable) -> bool { + struct CfgFinder; + + impl<'ast> visit::Visitor<'ast> for CfgFinder { + type Result = ControlFlow<()>; + fn visit_attribute(&mut self, attr: &'ast Attribute) -> ControlFlow<()> { + if attr + .ident() + .is_some_and(|ident| ident.name == sym::cfg || ident.name == sym::cfg_attr) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + } + } + + let res = match annotatable { + Annotatable::Item(item) => CfgFinder.visit_item(item), + Annotatable::TraitItem(item) => CfgFinder.visit_assoc_item(item, visit::AssocCtxt::Trait), + Annotatable::ImplItem(item) => CfgFinder.visit_assoc_item(item, visit::AssocCtxt::Impl), + Annotatable::ForeignItem(item) => CfgFinder.visit_foreign_item(item), + Annotatable::Stmt(stmt) => CfgFinder.visit_stmt(stmt), + Annotatable::Expr(expr) => CfgFinder.visit_expr(expr), + Annotatable::Arm(arm) => CfgFinder.visit_arm(arm), + Annotatable::ExprField(field) => CfgFinder.visit_expr_field(field), + Annotatable::PatField(field) => CfgFinder.visit_pat_field(field), + Annotatable::GenericParam(param) => CfgFinder.visit_generic_param(param), + Annotatable::Param(param) => CfgFinder.visit_param(param), + Annotatable::FieldDef(field) => CfgFinder.visit_field_def(field), + Annotatable::Variant(variant) => CfgFinder.visit_variant(variant), + Annotatable::Crate(krate) => CfgFinder.visit_crate(krate), + }; + res.is_break() +} + +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 !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: for<'a> fn(&mut Parser<'a>) -> PResult<'a, _> = + match annotatable { + Annotatable::Item(_) => { + |parser| Ok(Annotatable::Item(parser.parse_item(ForceCollect::Yes)?.unwrap())) + } + Annotatable::TraitItem(_) => |parser| { + Ok(Annotatable::TraitItem( + parser.parse_trait_item(ForceCollect::Yes)?.unwrap().unwrap(), + )) + }, + Annotatable::ImplItem(_) => |parser| { + Ok(Annotatable::ImplItem( + parser.parse_impl_item(ForceCollect::Yes)?.unwrap().unwrap(), + )) + }, + Annotatable::ForeignItem(_) => |parser| { + Ok(Annotatable::ForeignItem( + parser.parse_foreign_item(ForceCollect::Yes)?.unwrap().unwrap(), + )) + }, + Annotatable::Stmt(_) => |parser| { + Ok(Annotatable::Stmt(P(parser + .parse_stmt_without_recovery(false, ForceCollect::Yes)? + .unwrap()))) + }, + Annotatable::Expr(_) => { + |parser| Ok(Annotatable::Expr(parser.parse_expr_force_collect()?)) + } + _ => 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 `AttrTokenStream` (specifically, we capture + // `AttrTokenTree::AttributesData` for all occurrences of `#[cfg]` and `#[cfg_attr]`) + let mut parser = Parser::new(&self.cfg.sess.psess, orig_tokens, None); + parser.capture_cfg = true; + match parse_annotatable_with(&mut parser) { + Ok(a) => annotatable = a, + Err(err) => { + err.emit(); + return Some(annotatable); + } + } + + // Now that we have our re-parsed `AttrTokenStream`, recursively configuring + // our attribute target will correctly the tokens as well. + flat_map_annotatable(self, annotatable) + } +} + +impl MutVisitor for CfgEval<'_, '_> { + #[instrument(level = "trace", skip(self))] + fn visit_expr(&mut self, expr: &mut P<ast::Expr>) { + self.cfg.configure_expr(expr, false); + mut_visit::noop_visit_expr(expr, self); + } + + #[instrument(level = "trace", skip(self))] + fn visit_method_receiver_expr(&mut self, expr: &mut P<ast::Expr>) { + self.cfg.configure_expr(expr, true); + 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_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_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_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 00000000000..58928815e89 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs @@ -0,0 +1,42 @@ +//! Attributes injected into the crate root from command line using `-Z crate-attr`. + +use crate::errors; +use rustc_ast::attr::mk_attr; +use rustc_ast::token; +use rustc_ast::{self as ast, AttrItem, AttrStyle}; +use rustc_parse::{new_parser_from_source_str, unwrap_or_emit_fatal}; +use rustc_session::parse::ParseSess; +use rustc_span::FileName; + +pub fn inject(krate: &mut ast::Crate, psess: &ParseSess, attrs: &[String]) { + for raw_attr in attrs { + let mut parser = unwrap_or_emit_fatal(new_parser_from_source_str( + psess, + FileName::cli_crate_attr_source_code(raw_attr), + raw_attr.clone(), + )); + + let start_span = parser.token.span; + let AttrItem { unsafety, path, args, tokens: _ } = match parser.parse_attr_item(false) { + Ok(ai) => ai, + Err(err) => { + err.emit(); + continue; + } + }; + let end_span = parser.token.span; + if parser.token != token::Eof { + psess.dcx().emit_err(errors::InvalidCrateAttr { span: start_span.to(end_span) }); + continue; + } + + krate.attrs.push(mk_attr( + &psess.attr_id_generator, + AttrStyle::Inner, + unsafety, + path, + args, + start_span.to(end_span), + )); + } +} 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 00000000000..a08e8b2819b --- /dev/null +++ b/compiler/rustc_builtin_macros/src/compile_error.rs @@ -0,0 +1,26 @@ +// The compiler code necessary to support the compile_error! extension. + +use crate::util::get_single_str_from_tts; +use rustc_ast::tokenstream::TokenStream; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; +use rustc_span::Span; + +pub(crate) fn expand_compile_error<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "compile_error!") else { + return ExpandResult::Retry(()); + }; + let var = match mac { + Ok(var) => var, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + + #[expect(rustc::diagnostic_outside_of_impl, reason = "diagnostic message is specified by user")] + #[expect(rustc::untranslatable_diagnostic, reason = "diagnostic message is specified by user")] + let guar = cx.dcx().span_err(sp, var.to_string()); + + ExpandResult::Ready(DummyResult::any(sp, guar)) +} diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs new file mode 100644 index 00000000000..15af79ef67d --- /dev/null +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -0,0 +1,85 @@ +use crate::errors; +use crate::util::get_exprs_from_tts; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{ExprKind, LitKind, UnOp}; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; +use rustc_session::errors::report_lit_error; +use rustc_span::symbol::Symbol; + +pub(crate) fn expand_concat( + cx: &mut ExtCtxt<'_>, + sp: rustc_span::Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else { + return ExpandResult::Retry(()); + }; + let es = match mac { + Ok(es) => es, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + let mut accumulator = String::new(); + let mut missing_literal = vec![]; + let mut guar = None; + for e in es { + match e.kind { + ExprKind::Lit(token_lit) => match LitKind::from_token_lit(token_lit) { + Ok(LitKind::Str(s, _) | LitKind::Float(s, _)) => { + accumulator.push_str(s.as_str()); + } + Ok(LitKind::Char(c)) => { + accumulator.push(c); + } + Ok(LitKind::Int(i, _)) => { + accumulator.push_str(&i.to_string()); + } + Ok(LitKind::Bool(b)) => { + accumulator.push_str(&b.to_string()); + } + Ok(LitKind::CStr(..)) => { + guar = Some(cx.dcx().emit_err(errors::ConcatCStrLit { span: e.span })); + } + Ok(LitKind::Byte(..) | LitKind::ByteStr(..)) => { + guar = Some(cx.dcx().emit_err(errors::ConcatBytestr { span: e.span })); + } + Ok(LitKind::Err(guarantee)) => { + guar = Some(guarantee); + } + Err(err) => { + guar = Some(report_lit_error(&cx.sess.psess, err, token_lit, e.span)); + } + }, + // We also want to allow negative numeric literals. + ExprKind::Unary(UnOp::Neg, ref expr) if let ExprKind::Lit(token_lit) = expr.kind => { + match LitKind::from_token_lit(token_lit) { + Ok(LitKind::Int(i, _)) => accumulator.push_str(&format!("-{i}")), + Ok(LitKind::Float(f, _)) => accumulator.push_str(&format!("-{f}")), + Err(err) => { + guar = Some(report_lit_error(&cx.sess.psess, err, token_lit, e.span)); + } + _ => missing_literal.push(e.span), + } + } + ExprKind::IncludedBytes(..) => { + cx.dcx().emit_err(errors::ConcatBytestr { span: e.span }); + } + ExprKind::Err(guarantee) => { + guar = Some(guarantee); + } + ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"), + _ => { + missing_literal.push(e.span); + } + } + } + + ExpandResult::Ready(if !missing_literal.is_empty() { + let guar = cx.dcx().emit_err(errors::ConcatMissingLiteral { spans: missing_literal }); + DummyResult::any(sp, guar) + } else if let Some(guar) = guar { + DummyResult::any(sp, guar) + } else { + let sp = cx.with_def_site_ctxt(sp); + 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 00000000000..3130870df41 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -0,0 +1,185 @@ +use crate::errors; +use crate::util::get_exprs_from_tts; +use rustc_ast::{ptr::P, token, tokenstream::TokenStream, ExprKind, LitIntType, LitKind, UintTy}; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; +use rustc_session::errors::report_lit_error; +use rustc_span::{ErrorGuaranteed, Span}; + +/// Emits errors for literal expressions that are invalid inside and outside of an array. +fn invalid_type_err( + cx: &ExtCtxt<'_>, + token_lit: token::Lit, + span: Span, + is_nested: bool, +) -> ErrorGuaranteed { + use errors::{ + ConcatBytesInvalid, ConcatBytesInvalidSuggestion, ConcatBytesNonU8, ConcatBytesOob, + }; + let snippet = cx.sess.source_map().span_to_snippet(span).ok(); + let dcx = cx.dcx(); + match LitKind::from_token_lit(token_lit) { + Ok(LitKind::CStr(_, _)) => { + // Avoid ambiguity in handling of terminal `NUL` by refusing to + // concatenate C string literals as bytes. + dcx.emit_err(errors::ConcatCStrLit { span }) + } + Ok(LitKind::Char(_)) => { + let sugg = + snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet }); + dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg }) + } + Ok(LitKind::Str(_, _)) => { + // suggestion would be invalid if we are nested + let sugg = if !is_nested { + snippet.map(|snippet| ConcatBytesInvalidSuggestion::StrLit { span, snippet }) + } else { + None + }; + dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg }) + } + Ok(LitKind::Float(_, _)) => { + dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None }) + } + Ok(LitKind::Bool(_)) => { + dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "boolean", sugg: None }) + } + Ok(LitKind::Int(_, _)) if !is_nested => { + let sugg = + snippet.map(|snippet| ConcatBytesInvalidSuggestion::IntLit { span, snippet }); + dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg }) + } + Ok(LitKind::Int(val, LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8))) => { + assert!(val.get() > u8::MAX.into()); // must be an error + dcx.emit_err(ConcatBytesOob { span }) + } + Ok(LitKind::Int(_, _)) => dcx.emit_err(ConcatBytesNonU8 { span }), + Ok(LitKind::ByteStr(..) | LitKind::Byte(_)) => unreachable!(), + Ok(LitKind::Err(guar)) => guar, + Err(err) => report_lit_error(&cx.sess.psess, err, token_lit, span), + } +} + +/// Returns `expr` as a *single* byte literal if applicable. +/// +/// Otherwise, returns `None`, and either pushes the `expr`'s span to `missing_literals` or +/// updates `guar` accordingly. +fn handle_array_element( + cx: &ExtCtxt<'_>, + guar: &mut Option<ErrorGuaranteed>, + missing_literals: &mut Vec<rustc_span::Span>, + expr: &P<rustc_ast::Expr>, +) -> Option<u8> { + let dcx = cx.dcx(); + + match expr.kind { + ExprKind::Lit(token_lit) => { + match LitKind::from_token_lit(token_lit) { + Ok(LitKind::Int( + val, + LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8), + )) if let Ok(val) = u8::try_from(val.get()) => { + return Some(val); + } + Ok(LitKind::Byte(val)) => return Some(val), + Ok(LitKind::ByteStr(..)) => { + guar.get_or_insert_with(|| { + dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: true }) + }); + } + _ => { + guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, expr.span, true)); + } + }; + } + ExprKind::Array(_) | ExprKind::Repeat(_, _) => { + guar.get_or_insert_with(|| { + dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false }) + }); + } + ExprKind::IncludedBytes(..) => { + guar.get_or_insert_with(|| { + dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false }) + }); + } + _ => missing_literals.push(expr.span), + } + + None +} + +pub(crate) fn expand_concat_bytes( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else { + return ExpandResult::Retry(()); + }; + let es = match mac { + Ok(es) => es, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + let mut accumulator = Vec::new(); + let mut missing_literals = vec![]; + let mut guar = None; + for e in es { + match &e.kind { + ExprKind::Array(exprs) => { + for expr in exprs { + if let Some(elem) = + handle_array_element(cx, &mut guar, &mut missing_literals, expr) + { + accumulator.push(elem); + } + } + } + ExprKind::Repeat(expr, count) => { + if let ExprKind::Lit(token_lit) = count.value.kind + && let Ok(LitKind::Int(count_val, _)) = LitKind::from_token_lit(token_lit) + { + if let Some(elem) = + handle_array_element(cx, &mut guar, &mut missing_literals, expr) + { + for _ in 0..count_val.get() { + accumulator.push(elem); + } + } + } else { + guar = Some( + cx.dcx().emit_err(errors::ConcatBytesBadRepeat { span: count.value.span }), + ); + } + } + &ExprKind::Lit(token_lit) => match LitKind::from_token_lit(token_lit) { + Ok(LitKind::Byte(val)) => { + accumulator.push(val); + } + Ok(LitKind::ByteStr(ref bytes, _)) => { + accumulator.extend_from_slice(bytes); + } + _ => { + guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, e.span, false)); + } + }, + ExprKind::IncludedBytes(bytes) => { + accumulator.extend_from_slice(bytes); + } + ExprKind::Err(guarantee) => { + guar = Some(*guarantee); + } + ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"), + _ => { + missing_literals.push(e.span); + } + } + } + ExpandResult::Ready(if !missing_literals.is_empty() { + let guar = cx.dcx().emit_err(errors::ConcatBytesMissingLiteral { spans: missing_literals }); + MacEager::expr(DummyResult::raw_expr(sp, Some(guar))) + } else if let Some(guar) = guar { + MacEager::expr(DummyResult::raw_expr(sp, Some(guar))) + } else { + let sp = cx.with_def_site_ctxt(sp); + MacEager::expr(cx.expr_byte_str(sp, 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 00000000000..13729a9d250 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/concat_idents.rs @@ -0,0 +1,72 @@ +use rustc_ast::ptr::P; +use rustc_ast::token::{self, Token}; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast::{AttrVec, Expr, ExprKind, Path, Ty, TyKind, DUMMY_NODE_ID}; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult}; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::Span; + +use crate::errors; + +pub(crate) fn expand_concat_idents<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + if tts.is_empty() { + let guar = cx.dcx().emit_err(errors::ConcatIdentsMissingArgs { span: sp }); + return ExpandResult::Ready(DummyResult::any(sp, guar)); + } + + let mut res_str = String::new(); + for (i, e) in tts.trees().enumerate() { + if i & 1 == 1 { + match e { + TokenTree::Token(Token { kind: token::Comma, .. }, _) => {} + _ => { + let guar = cx.dcx().emit_err(errors::ConcatIdentsMissingComma { span: sp }); + return ExpandResult::Ready(DummyResult::any(sp, guar)); + } + } + } else { + if let TokenTree::Token(token, _) = e { + if let Some((ident, _)) = token.ident() { + res_str.push_str(ident.name.as_str()); + continue; + } + } + + let guar = cx.dcx().emit_err(errors::ConcatIdentsIdentArgs { span: sp }); + return ExpandResult::Ready(DummyResult::any(sp, guar)); + } + } + + let ident = Ident::new(Symbol::intern(&res_str), cx.with_call_site_ctxt(sp)); + + struct ConcatIdentsResult { + ident: Ident, + } + + impl MacResult for ConcatIdentsResult { + fn make_expr(self: Box<Self>) -> Option<P<Expr>> { + Some(P(Expr { + id: DUMMY_NODE_ID, + kind: ExprKind::Path(None, Path::from_ident(self.ident)), + span: self.ident.span, + attrs: AttrVec::new(), + tokens: None, + })) + } + + fn make_ty(self: Box<Self>) -> Option<P<Ty>> { + Some(P(Ty { + id: DUMMY_NODE_ID, + kind: TyKind::Path(None, Path::from_ident(self.ident)), + span: self.ident.span, + tokens: None, + })) + } + } + + ExpandResult::Ready(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 00000000000..b5cbfdf0ec6 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -0,0 +1,172 @@ +use crate::cfg_eval::cfg_eval; +use crate::errors; + +use rustc_ast as ast; +use rustc_ast::{GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, Safety, StmtKind}; +use rustc_expand::base::{ + Annotatable, DeriveResolution, 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::{ErrorGuaranteed, Span}; + +pub(crate) struct Expander { + pub is_const: bool, +} + +impl MultiItemModifier for Expander { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + _: bool, + ) -> ExpandResult<Vec<Annotatable>, Annotatable> { + let sess = ecx.sess; + if report_bad_target(sess, &item, span).is_err() { + // 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() }; + validate_attr::check_builtin_meta_item( + &sess.psess, + meta_item, + ast::AttrStyle::Outer, + sym::derive, + template, + ); + + let mut resolutions = match &meta_item.kind { + MetaItemKind::List(list) => { + list.iter() + .filter_map(|nested_meta| match nested_meta { + NestedMetaItem::MetaItem(meta) => Some(meta), + NestedMetaItem::Lit(lit) => { + // Reject `#[derive("Debug")]`. + report_unexpected_meta_item_lit(sess, lit); + None + } + }) + .map(|meta| { + // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the + // paths. + report_path_args(sess, meta); + report_unsafe_args(sess, meta); + meta.path.clone() + }) + .map(|path| DeriveResolution { + path, + item: dummy_annotatable(), + exts: None, + is_const: self.is_const, + }) + .collect() + } + _ => vec![], + }; + + // Do not configure or clone items unless necessary. + match &mut resolutions[..] { + [] => {} + [first, others @ ..] => { + first.item = cfg_eval( + sess, + features, + item.clone(), + ecx.current_expansion.lint_node_id, + ); + for other in others { + other.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, +) -> Result<(), ErrorGuaranteed> { + 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 { + return Err(sess.dcx().emit_err(errors::BadDeriveTarget { span, item: item.span() })); + } + Ok(()) +} + +fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) { + let help = match lit.kind { + ast::LitKind::Str(_, ast::StrStyle::Cooked) + if rustc_lexer::is_ident(lit.symbol.as_str()) => + { + errors::BadDeriveLitHelp::StrLit { sym: lit.symbol } + } + _ => errors::BadDeriveLitHelp::Other, + }; + sess.dcx().emit_err(errors::BadDeriveLit { span: lit.span, help }); +} + +fn report_path_args(sess: &Session, meta: &ast::MetaItem) { + let span = meta.span.with_lo(meta.path.span.hi()); + + match meta.kind { + MetaItemKind::Word => {} + MetaItemKind::List(..) => { + sess.dcx().emit_err(errors::DerivePathArgsList { span }); + } + MetaItemKind::NameValue(..) => { + sess.dcx().emit_err(errors::DerivePathArgsValue { span }); + } + } +} + +fn report_unsafe_args(sess: &Session, meta: &ast::MetaItem) { + match meta.unsafety { + Safety::Unsafe(span) => { + sess.dcx().emit_err(errors::DeriveUnsafePath { span }); + } + Safety::Default => {} + Safety::Safe(_) => unreachable!(), + } +} 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 00000000000..97e2344ff30 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/bounds.rs @@ -0,0 +1,52 @@ +use crate::deriving::generic::*; +use crate::deriving::path_std; + +use rustc_ast::MetaItem; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::Span; + +pub(crate) fn expand_deriving_copy( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + let trait_def = TraitDef { + span, + path: path_std!(marker::Copy), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: false, + additional_bounds: Vec::new(), + supports_unions: true, + methods: Vec::new(), + associated_types: Vec::new(), + is_const, + }; + + trait_def.expand(cx, mitem, item, push); +} + +pub(crate) fn expand_deriving_const_param_ty( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + let trait_def = TraitDef { + span, + path: path_std!(marker::ConstParamTy), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: false, + additional_bounds: vec![ty::Ty::Path(path_std!(cmp::Eq))], + supports_unions: false, + methods: Vec::new(), + associated_types: Vec::new(), + is_const, + }; + + 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 00000000000..abcb402a46f --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs @@ -0,0 +1,218 @@ +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; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) fn expand_deriving_clone( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + // 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(annitem) => match &annitem.kind { + ItemKind::Struct(_, Generics { params, .. }) + | ItemKind::Enum(_, Generics { 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.dcx().span_bug(span, "`#[derive(Clone)]` on wrong item kind"), + }, + + _ => cx.dcx().span_bug(span, "`#[derive(Clone)]` on trait item or impl item"), + } + + let trait_def = TraitDef { + span, + path: path_std!(clone::Clone), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: bounds, + supports_unions: true, + methods: vec![MethodDef { + name: sym::clone, + generics: Bounds::empty(), + explicit_self: true, + nonself_args: Vec::new(), + ret_ty: Self_, + attributes: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: FieldlessVariantsStrategy::Default, + combine_substructure: substructure, + }], + associated_types: Vec::new(), + is_const, + }; + + trait_def.expand_ext(cx, mitem, item, push, is_simple) +} + +fn cs_clone_simple( + name: &str, + cx: &ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, + is_union: bool, +) -> BlockOrExpr { + let mut stmts = ThinVec::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. + // Anonymous structs or unions must be eliminated as they cannot be + // type parameters. + } else if !field.ty.kind.is_anon_adt() { + // 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.dcx().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: &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: &ExtCtxt<'_>, field: &FieldInfo| { + let args = thin_vec![field.self_expr.clone()]; + cx.expr_call_global(field.span, fn_path.clone(), args) + }; + + let vdata; + match substr.fields { + Struct(vdata_, af) => { + ctor_path = cx.path(trait_span, vec![substr.type_ident]); + all_fields = af; + vdata = *vdata_; + } + EnumMatching(.., variant, af) => { + ctor_path = cx.path(trait_span, vec![substr.type_ident, variant.ident]); + all_fields = af; + vdata = &variant.data; + } + EnumDiscr(..) | AllFieldlessEnum(..) => { + cx.dcx().span_bug(trait_span, format!("enum discriminants in `derive({name})`",)) + } + StaticEnum(..) | StaticStruct(..) => { + cx.dcx().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.dcx().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::<ThinVec<_>>(); + + 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 00000000000..53a15131605 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -0,0 +1,92 @@ +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; +use rustc_span::Span; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) fn expand_deriving_eq( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + let span = cx.with_def_site_ctxt(span); + + let trait_def = TraitDef { + span, + path: path_std!(cmp::Eq), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: Vec::new(), + 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: thin_vec![ + cx.attr_word(sym::inline, span), + cx.attr_nested_word(sym::doc, sym::hidden, span), + cx.attr_nested_word(sym::coverage, sym::off, span) + ], + fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + cs_total_eq_assert(a, b, c) + })), + }], + associated_types: Vec::new(), + is_const, + }; + trait_def.expand_ext(cx, mitem, item, push, true) +} + +fn cs_total_eq_assert( + cx: &ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, +) -> BlockOrExpr { + let mut stmts = ThinVec::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.dcx().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 00000000000..8470d466a23 --- /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; +use thin_vec::thin_vec; + +pub(crate) fn expand_deriving_ord( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + let trait_def = TraitDef { + span, + path: path_std!(cmp::Ord), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: Vec::new(), + 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: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, + combine_substructure: combine_substructure(Box::new(|a, b, c| cs_cmp(a, b, c))), + }], + associated_types: Vec::new(), + is_const, + }; + + trait_def.expand(cx, mitem, item, push) +} + +pub(crate) fn cs_cmp(cx: &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.dcx().span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`"); + }; + let args = thin_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, thin_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 00000000000..a6457f4a433 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs @@ -0,0 +1,115 @@ +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; +use thin_vec::thin_vec; + +pub(crate) fn expand_deriving_partial_eq( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + fn cs_eq(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr { + let base = true; + 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.dcx() + .span_bug(field.span, "not exactly 2 arguments in `derive(PartialEq)`"); + }; + + // We received arguments of type `&T`. Convert them to type `T` by stripping + // any leading `&`. This isn't necessary for type checking, but + // it results in better error messages if something goes wrong. + // + // Note: for arguments that look like `&{ x }`, which occur with packed + // structs, this would cause expressions like `{ self.x } == { other.x }`, + // which isn't valid Rust syntax. This wouldn't break compilation because these + // AST nodes are constructed within the compiler. But it would mean that code + // printed by `-Zunpretty=expanded` (or `cargo expand`) would have invalid + // syntax, which would be suboptimal. So we wrap these in parens, giving + // `({ self.x }) == ({ other.x })`, which is valid syntax. + let convert = |expr: &P<Expr>| { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = + &expr.kind + { + if let ExprKind::Block(..) = &inner.kind { + // `&{ x }` form: remove the `&`, add parens. + cx.expr_paren(field.span, inner.clone()) + } else { + // `&x` form: remove the `&`. + inner.clone() + } + } else { + expr.clone() + } + }; + cx.expr_binary( + field.span, + BinOpKind::Eq, + convert(&field.self_expr), + convert(other_expr), + ) + } + CsFold::Combine(span, expr1, expr2) => { + cx.expr_binary(span, BinOpKind::And, expr1, expr2) + } + CsFold::Fieldless => cx.expr_bool(span, base), + }, + ); + BlockOrExpr::new_expr(expr) + } + + let structural_trait_def = TraitDef { + span, + path: path_std!(marker::StructuralPartialEq), + skip_path_as_bound: true, // crucial! + needs_copy_as_bound_if_packed: false, + additional_bounds: Vec::new(), + // We really don't support unions, but that's already checked by the impl generated below; + // a second check here would lead to redundant error messages. + supports_unions: true, + methods: Vec::new(), + associated_types: Vec::new(), + is_const: false, + }; + structural_trait_def.expand(cx, mitem, item, push); + + // No need to generate `ne`, the default suffices, and not generating it is + // faster. + let methods = vec![MethodDef { + name: sym::eq, + generics: Bounds::empty(), + explicit_self: true, + nonself_args: vec![(self_ref(), sym::other)], + ret_ty: Path(path_local!(bool)), + attributes: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, + combine_substructure: combine_substructure(Box::new(|a, b, c| cs_eq(a, b, c))), + }]; + + let trait_def = TraitDef { + span, + path: path_std!(cmp::PartialEq), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: Vec::new(), + supports_unions: false, + methods, + associated_types: Vec::new(), + is_const, + }; + 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 00000000000..006e5a3d268 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs @@ -0,0 +1,156 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::{path_std, pathvec_std}; +use rustc_ast::{ExprKind, ItemKind, MetaItem, PatKind}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::Span; +use thin_vec::thin_vec; + +pub(crate) fn expand_deriving_partial_ord( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + 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)); + + // Order in which to perform matching + let discr_then_data = if let Annotatable::Item(item) = item + && let ItemKind::Enum(def, _) = &item.kind + { + let dataful: Vec<bool> = def.variants.iter().map(|v| !v.data.fields().is_empty()).collect(); + match dataful.iter().filter(|&&b| b).count() { + // No data, placing the discriminant check first makes codegen simpler + 0 => true, + 1..=2 => false, + _ => (0..dataful.len() - 1).any(|i| { + if dataful[i] + && let Some(idx) = dataful[i + 1..].iter().position(|v| *v) + { + idx >= 2 + } else { + false + } + }), + } + } else { + true + }; + 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: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, + combine_substructure: combine_substructure(Box::new(|cx, span, substr| { + cs_partial_cmp(cx, span, substr, discr_then_data) + })), + }; + + let trait_def = TraitDef { + span, + path: path_std!(cmp::PartialOrd), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: vec![], + supports_unions: false, + methods: vec![partial_cmp_def], + associated_types: Vec::new(), + is_const, + }; + trait_def.expand(cx, mitem, item, push) +} + +fn cs_partial_cmp( + cx: &ExtCtxt<'_>, + span: Span, + substr: &Substructure<'_>, + discr_then_data: bool, +) -> 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.dcx().span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`"); + }; + let args = thin_vec![field.self_expr.clone(), other_expr.clone()]; + cx.expr_call_global(field.span, partial_cmp_path.clone(), args) + } + CsFold::Combine(span, mut expr1, expr2) => { + // When the item is an enum, this expands to + // ``` + // match (expr2) { + // Some(Ordering::Equal) => expr1, + // cmp => cmp + // } + // ``` + // where `expr2` is `partial_cmp(self_discr, other_discr)`, and `expr1` is a `match` + // against the enum variants. This means that we begin by comparing the enum discriminants, + // before either inspecting their contents (if they match), or returning + // the `cmp::Ordering` of comparing the enum discriminants. + // ``` + // match partial_cmp(self_discr, other_discr) { + // Some(Ordering::Equal) => match (self, other) { + // (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0), + // (Self::B(self_0), Self::B(other_0)) => partial_cmp(self_0, other_0), + // _ => Some(Ordering::Equal) + // } + // cmp => cmp + // } + // ``` + // If we have any certain enum layouts, flipping this results in better codegen + // ``` + // match (self, other) { + // (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0), + // _ => partial_cmp(self_discr, other_discr) + // } + // ``` + // Reference: https://github.com/rust-lang/rust/pull/103659#issuecomment-1328126354 + + if !discr_then_data + && let ExprKind::Match(_, arms, _) = &mut expr1.kind + && let Some(last) = arms.last_mut() + && let PatKind::Wild = last.pat.kind + { + last.body = Some(expr2); + expr1 + } else { + 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, thin_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 00000000000..57ec0435e3e --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -0,0 +1,240 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::path_std; + +use rustc_ast::{self as ast, EnumDef, MetaItem}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) fn expand_deriving_debug( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + // &mut ::std::fmt::Formatter + let fmtr = Ref(Box::new(Path(path_std!(fmt::Formatter))), ast::Mutability::Mut); + + let trait_def = TraitDef { + span, + path: path_std!(fmt::Debug), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: Vec::new(), + 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: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: + FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + show_substructure(a, b, c) + })), + }], + associated_types: Vec::new(), + is_const, + }; + trait_def.expand(cx, mitem, item, push) +} + +fn show_substructure(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr { + // 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 (ident, vdata, fields) = match substr.fields { + Struct(vdata, fields) => (substr.type_ident, *vdata, fields), + EnumMatching(_, v, fields) => (v.ident, &v.data, fields), + AllFieldlessEnum(enum_def) => return show_fieldless_enum(cx, span, enum_def, substr), + EnumDiscr(..) | StaticStruct(..) | StaticEnum(..) => { + cx.dcx().span_bug(span, "nonsensical .fields in `#[derive(Debug)]`") + } + }; + + let name = cx.expr_str(span, ident.name); + 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; + + fn expr_for_field( + cx: &ExtCtxt<'_>, + field: &FieldInfo, + index: usize, + len: usize, + ) -> ast::ptr::P<ast::Expr> { + if index < len - 1 { + field.self_expr.clone() + } else { + // Unsized types need an extra indirection, but only the last field + // may be unsized. + cx.expr_addr_of(field.span, field.self_expr.clone()) + } + } + + 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, thin_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 = ThinVec::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_str(field.span, field.name.unwrap().name); + args.push(name); + } + + let field = expr_for_field(cx, field, i, fields.len()); + 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 = ThinVec::with_capacity(fields.len()); + let mut value_exprs = ThinVec::with_capacity(fields.len()); + + for i in 0..fields.len() { + let field = &fields[i]; + if is_struct { + name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name)); + } + + let field = expr_for_field(cx, field, i, fields.len()); + value_exprs.push(field); + } + + // `let names: &'static _ = &["field1", "field2"];` + let names_let = is_struct.then(|| { + let lt_static = Some(cx.lifetime_static(span)); + let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not); + cx.stmt_let_ty( + span, + false, + Ident::new(sym::names, span), + Some(ty_static_ref), + cx.expr_array_ref(span, name_exprs), + ) + }); + + // `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, false)], + ast::TraitObjectSyntax::Dyn, + ), + ); + let ty_slice = cx.ty( + span, + ast::TyKind::Slice(cx.ty_ref(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_ref(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 = ThinVec::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 = ThinVec::with_capacity(2); + if is_struct { + stmts.push(names_let.unwrap()); + } + stmts.push(values_let); + BlockOrExpr::new_mixed(stmts, Some(expr)) + } +} + +/// Special case for enums with no fields. Builds: +/// ```text +/// impl ::core::fmt::Debug for A { +/// fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { +/// ::core::fmt::Formatter::write_str(f, +/// match self { +/// A::A => "A", +/// A::B() => "B", +/// A::C {} => "C", +/// }) +/// } +/// } +/// ``` +fn show_fieldless_enum( + cx: &ExtCtxt<'_>, + span: Span, + def: &EnumDef, + substr: &Substructure<'_>, +) -> BlockOrExpr { + let fmt = substr.nonselflike_args[0].clone(); + let arms = def + .variants + .iter() + .map(|v| { + let variant_path = cx.path(span, vec![substr.type_ident, v.ident]); + let pat = match &v.data { + ast::VariantData::Tuple(fields, _) => { + debug_assert!(fields.is_empty()); + cx.pat_tuple_struct(span, variant_path, ThinVec::new()) + } + ast::VariantData::Struct { fields, .. } => { + debug_assert!(fields.is_empty()); + cx.pat_struct(span, variant_path, ThinVec::new()) + } + ast::VariantData::Unit(_) => cx.pat_path(span, variant_path), + }; + cx.arm(span, pat, cx.expr_str(span, v.ident.name)) + }) + .collect::<ThinVec<_>>(); + let name = cx.expr_match(span, cx.expr_self(span), arms); + let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]); + BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name])) +} 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 00000000000..e9851c87aea --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs @@ -0,0 +1,226 @@ +//! 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; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) fn expand_deriving_rustc_decodable( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + let krate = sym::rustc_serialize; + let typaram = sym::__D; + + let trait_def = TraitDef { + span, + path: Path::new_(vec![krate, sym::Decodable], vec![], PathKind::Global), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: Vec::new(), + 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: ast::AttrVec::new(), + fieldless_variants_strategy: FieldlessVariantsStrategy::Default, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + decodable_substructure(a, b, c, krate) + })), + }], + associated_types: Vec::new(), + is_const, + }; + + trait_def.expand(cx, mitem, item, push) +} + +fn decodable_substructure( + cx: &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(_, summary) => { + let nfields = match summary { + Unnamed(fields, _) => fields.len(), + Named(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(), + thin_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, + thin_vec![ + decoder, + cx.expr_str(trait_span, substr.type_ident.name), + cx.expr_usize(trait_span, nfields), + cx.lambda1(trait_span, result, blkarg), + ], + ) + } + StaticEnum(_, fields) => { + let variant = Ident::new(sym::i, trait_span); + + let mut arms = ThinVec::with_capacity(fields.len() + 1); + let mut variants = ThinVec::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(), + thin_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, + thin_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, + thin_vec![ + decoder, + cx.expr_str(trait_span, substr.type_ident.name), + cx.lambda1(trait_span, result, blkarg), + ], + ) + } + _ => cx.dcx().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: &ExtCtxt<'_>, + trait_span: Span, + outer_pat_path: ast::Path, + fields: &StaticFields, + mut getarg: F, +) -> P<Expr> +where + F: FnMut(&ExtCtxt<'_>, Span, Symbol, usize) -> P<Expr>, +{ + match fields { + Unnamed(fields, is_tuple) => { + let path_expr = cx.expr_path(outer_pat_path); + if matches!(is_tuple, IsTuple::No) { + 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(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 00000000000..577523a1d5a --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/default.rs @@ -0,0 +1,250 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::errors; +use core::ops::ControlFlow; +use rustc_ast as ast; +use rustc_ast::visit::visit_opt; +use rustc_ast::{attr, EnumDef, VariantData}; +use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt}; +use rustc_span::symbol::Ident; +use rustc_span::symbol::{kw, sym}; +use rustc_span::{ErrorGuaranteed, Span}; +use smallvec::SmallVec; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) fn expand_deriving_default( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &ast::MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + item.visit_with(&mut DetectNonVariantDefaultAttr { cx }); + + let trait_def = TraitDef { + span, + path: Path::new(vec![kw::Default, sym::Default]), + skip_path_as_bound: has_a_default_variant(item), + needs_copy_as_bound_if_packed: false, + additional_bounds: Vec::new(), + supports_unions: false, + methods: vec![MethodDef { + name: kw::Default, + generics: Bounds::empty(), + explicit_self: false, + nonself_args: Vec::new(), + ret_ty: Self_, + attributes: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: FieldlessVariantsStrategy::Default, + 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.dcx().span_bug(trait_span, "method in `derive(Default)`"), + } + })), + }], + associated_types: Vec::new(), + is_const, + }; + trait_def.expand(cx, mitem, item, push) +} + +fn default_struct_substructure( + cx: &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(), ThinVec::new()); + + let expr = match summary { + Unnamed(_, IsTuple::No) => cx.expr_ident(trait_span, substr.type_ident), + Unnamed(fields, IsTuple::Yes) => { + let exprs = fields.iter().map(|sp| default_call(*sp)).collect(); + cx.expr_call_ident(trait_span, substr.type_ident, exprs) + } + Named(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: &ExtCtxt<'_>, + trait_span: Span, + enum_def: &EnumDef, +) -> BlockOrExpr { + let expr = match try { + let default_variant = extract_default_variant(cx, enum_def, trait_span)?; + validate_default_attribute(cx, default_variant)?; + default_variant + } { + Ok(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], + )) + } + Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)), + }; + BlockOrExpr::new_expr(expr) +} + +fn extract_default_variant<'a>( + cx: &ExtCtxt<'_>, + enum_def: &'a EnumDef, + trait_span: Span, +) -> Result<&'a rustc_ast::Variant, ErrorGuaranteed> { + let default_variants: SmallVec<[_; 1]> = enum_def + .variants + .iter() + .filter(|variant| attr::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| !attr::contains_name(&variant.attrs, sym::non_exhaustive)); + + let suggs = possible_defaults + .map(|v| errors::NoDefaultVariantSugg { span: v.span, ident: v.ident }) + .collect(); + let guar = cx.dcx().emit_err(errors::NoDefaultVariant { span: trait_span, suggs }); + + return Err(guar); + } + [first, rest @ ..] => { + let suggs = default_variants + .iter() + .filter_map(|variant| { + let keep = attr::find_by_name(&variant.attrs, kw::Default)?.span; + let spans: Vec<Span> = default_variants + .iter() + .flat_map(|v| { + attr::filter_by_name(&v.attrs, kw::Default) + .filter_map(|attr| (attr.span != keep).then_some(attr.span)) + }) + .collect(); + (!spans.is_empty()) + .then_some(errors::MultipleDefaultsSugg { spans, ident: variant.ident }) + }) + .collect(); + let guar = cx.dcx().emit_err(errors::MultipleDefaults { + span: trait_span, + first: first.span, + additional: rest.iter().map(|v| v.span).collect(), + suggs, + }); + return Err(guar); + } + }; + + if !matches!(variant.data, VariantData::Unit(..)) { + let guar = cx.dcx().emit_err(errors::NonUnitDefault { span: variant.ident.span }); + return Err(guar); + } + + if let Some(non_exhaustive_attr) = attr::find_by_name(&variant.attrs, sym::non_exhaustive) { + let guar = cx.dcx().emit_err(errors::NonExhaustiveDefault { + span: variant.ident.span, + non_exhaustive: non_exhaustive_attr.span, + }); + + return Err(guar); + } + + Ok(variant) +} + +fn validate_default_attribute( + cx: &ExtCtxt<'_>, + default_variant: &rustc_ast::Variant, +) -> Result<(), ErrorGuaranteed> { + let attrs: SmallVec<[_; 1]> = + attr::filter_by_name(&default_variant.attrs, kw::Default).collect(); + + let attr = match attrs.as_slice() { + [attr] => attr, + [] => cx.dcx().bug( + "this method must only be called with a variant that has a `#[default]` attribute", + ), + [first, rest @ ..] => { + let sugg = errors::MultipleDefaultAttrsSugg { + spans: rest.iter().map(|attr| attr.span).collect(), + }; + let guar = cx.dcx().emit_err(errors::MultipleDefaultAttrs { + span: default_variant.ident.span, + first: first.span, + first_rest: rest[0].span, + rest: rest.iter().map(|attr| attr.span).collect::<Vec<_>>().into(), + only_one: rest.len() == 1, + sugg, + }); + + return Err(guar); + } + }; + if !attr.is_word() { + let guar = cx.dcx().emit_err(errors::DefaultHasArg { span: attr.span }); + + return Err(guar); + } + 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.dcx().emit_err(errors::NonUnitDefault { span: attr.span }); + } + + 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); + visit_opt!(self, visit_anon_const, &v.disr_expr); + for attr in &v.attrs { + rustc_ast::visit::walk_attribute(self, attr); + } + } +} + +fn has_a_default_variant(item: &Annotatable) -> bool { + struct HasDefaultAttrOnVariant; + + impl<'ast> rustc_ast::visit::Visitor<'ast> for HasDefaultAttrOnVariant { + type Result = ControlFlow<()>; + fn visit_variant(&mut self, v: &'ast rustc_ast::Variant) -> ControlFlow<()> { + if v.attrs.iter().any(|attr| attr.has_name(kw::Default)) { + ControlFlow::Break(()) + } else { + // no need to subrecurse. + ControlFlow::Continue(()) + } + } + } + + item.visit_with(&mut HasDefaultAttrOnVariant).is_break() +} 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 00000000000..3bd74d8d019 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/encodable.rs @@ -0,0 +1,301 @@ +//! 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::{AttrVec, ExprKind, MetaItem, Mutability}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) fn expand_deriving_rustc_encodable( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + let krate = sym::rustc_serialize; + let typaram = sym::__S; + + let trait_def = TraitDef { + span, + path: Path::new_(vec![krate, sym::Encodable], vec![], PathKind::Global), + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: Vec::new(), + 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: AttrVec::new(), + fieldless_variants_strategy: FieldlessVariantsStrategy::Default, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + encodable_substructure(a, b, c, krate) + })), + }], + associated_types: Vec::new(), + is_const, + }; + + trait_def.expand(cx, mitem, item, push) +} + +fn encodable_substructure( + cx: &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(_, fields) => { + let fn_emit_struct_field_path = + cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_struct_field]); + let mut stmts = ThinVec::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(), thin_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(), + thin_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, ThinVec::new())); + 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, + thin_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, 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 = ThinVec::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(), + thin_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(), + thin_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, ThinVec::new())); + 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, + thin_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, + thin_vec![encoder, cx.expr_str(trait_span, substr.type_ident.name), blk], + ); + BlockOrExpr::new_mixed(thin_vec![me], Some(expr)) + } + + _ => cx.dcx().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 00000000000..ba289f9552e --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -0,0 +1,1726 @@ +//! 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)`) +//! - `EnumDiscr` when `Self` is an enum, for comparing the enum discriminants. +//! - `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 +//! 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 discriminants, +//! +//! ```text +//! EnumDiscr( +//! &[<ident of self discriminant>, <ident of other discriminant>], +//! <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(crate) use StaticFields::*; +pub(crate) use SubstructureFields::*; + +use crate::{deriving, errors}; +use rustc_ast::ptr::P; +use rustc_ast::{ + self as ast, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind, Generics, + Mutability, PatKind, TyKind, VariantData, +}; +use rustc_attr as attr; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_session::lint::builtin::BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use std::cell::RefCell; +use std::iter; +use std::ops::Not; +use std::vec; +use thin_vec::{thin_vec, ThinVec}; +use ty::{Bounds, Path, Ref, Self_, Ty}; + +pub(crate) mod ty; + +pub(crate) struct TraitDef<'a> { + /// The span for the current #[derive(Foo)] header. + pub span: Span, + + /// Path of the trait, including any type parameters + pub path: Path, + + /// Whether to skip adding the current trait as a bound to the type parameters of the type. + pub skip_path_as_bound: bool, + + /// Whether `Copy` is needed as an additional bound on type parameters in a packed struct. + pub needs_copy_as_bound_if_packed: bool, + + /// Additional bounds required of any type parameters of the type, + /// other than the current trait + pub additional_bounds: Vec<Ty>, + + /// Can this trait be derived for unions? + pub supports_unions: bool, + + pub methods: Vec<MethodDef<'a>>, + + pub associated_types: Vec<(Ident, Ty)>, + + pub is_const: bool, +} + +pub(crate) 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: ast::AttrVec, + + pub fieldless_variants_strategy: FieldlessVariantsStrategy, + + pub combine_substructure: RefCell<CombineSubstructureFunc<'a>>, +} + +/// How to handle fieldless enum variants. +#[derive(PartialEq)] +pub(crate) enum FieldlessVariantsStrategy { + /// Combine fieldless variants into a single match arm. + /// This assumes that relevant information has been handled + /// by looking at the enum's discriminant. + Unify, + /// Don't do anything special about fieldless variants. They are + /// handled like any other variant. + Default, + /// If all variants of the enum are fieldless, expand the special + /// `AllFieldLessEnum` substructure, so that the entire enum can be handled + /// at once. + SpecializeIfAllVariantsFieldless, +} + +/// All the data about the data structure/method being derived upon. +pub(crate) 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(crate) 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>>, +} + +#[derive(Copy, Clone)] +pub(crate) enum IsTuple { + No, + Yes, +} + +/// Fields for a static method +pub(crate) enum StaticFields { + /// Tuple and unit structs/enum variants like this. + Unnamed(Vec<Span>, IsTuple), + /// Normal structs/struct variants. + Named(Vec<(Ident, Span)>), +} + +/// A summary of the possible sets of fields. +pub(crate) enum SubstructureFields<'a> { + /// A non-static method where `Self` is a struct. + Struct(&'a ast::VariantData, Vec<FieldInfo>), + + /// A non-static method handling the entire enum at once + /// (after it has been determined that none of the enum + /// variants has any fields). + AllFieldlessEnum(&'a ast::EnumDef), + + /// Matching variants of the enum: variant index, ast::Variant, + /// fields: the field name is only non-`None` in the case of a struct + /// variant. + EnumMatching(usize, &'a ast::Variant, Vec<FieldInfo>), + + /// The discriminant of an enum. The first field is a `FieldInfo` for the discriminants, as + /// if they were fields. The second field is the expression to combine the + /// discriminant expression with; it will be `None` if no match is necessary. + EnumDiscr(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(crate) type CombineSubstructureFunc<'a> = + Box<dyn FnMut(&ExtCtxt<'_>, Span, &Substructure<'_>) -> BlockOrExpr + 'a>; + +pub(crate) fn combine_substructure( + f: CombineSubstructureFunc<'_>, +) -> RefCell<CombineSubstructureFunc<'_>> { + RefCell::new(f) +} + +struct TypeParameter { + bound_generic_params: ThinVec<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(crate) struct BlockOrExpr(ThinVec<ast::Stmt>, Option<P<Expr>>); + +impl BlockOrExpr { + pub fn new_stmts(stmts: ThinVec<ast::Stmt>) -> BlockOrExpr { + BlockOrExpr(stmts, None) + } + + pub fn new_expr(expr: P<Expr>) -> BlockOrExpr { + BlockOrExpr(ThinVec::new(), Some(expr)) + } + + pub fn new_mixed(stmts: ThinVec<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, ThinVec::new())), + 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: ThinVec<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) { + let stack_len = self.bound_generic_params_stack.len(); + if let ast::TyKind::BareFn(bare_fn) = &ty.kind + && !bare_fn.generic_params.is_empty() + { + // Given a field `x: for<'a> fn(T::SomeType<'a>)`, we wan't to account for `'a` so + // that we generate `where for<'a> T::SomeType<'a>: ::core::clone::Clone`. #122622 + self.bound_generic_params_stack.extend(bare_fn.generic_params.iter().cloned()); + } + + if let ast::TyKind::Path(_, path) = &ty.kind + && let Some(segment) = path.segments.first() + && 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); + self.bound_generic_params_stack.truncate(stack_len); + } + + // 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) { + let stack_len = self.bound_generic_params_stack.len(); + self.bound_generic_params_stack.extend(trait_ref.bound_generic_params.iter().cloned()); + + visit::walk_poly_trait_ref(self, trait_ref); + + self.bound_generic_params_stack.truncate(stack_len); + } + + fn visit_mac_call(&mut self, mac: &ast::MacCall) { + self.cx.dcx().emit_err(errors::DeriveMacroCall { span: mac.span() }); + } + } + + let mut visitor = Visitor { + cx, + ty_param_names, + bound_generic_params_stack: ThinVec::new(), + type_params: Vec::new(), + }; + visit::Visitor::visit_ty(&mut visitor, ty); + + visitor.type_params +} + +impl<'a> TraitDef<'a> { + pub fn expand( + self, + cx: &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: &ExtCtxt<'_>, + mitem: &ast::MetaItem, + item: &'a Annotatable, + push: &mut dyn FnMut(Annotatable), + from_scratch: bool, + ) { + match item { + Annotatable::Item(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 newitem = match &item.kind { + ast::ItemKind::Struct(struct_def, generics) => self.expand_struct_def( + cx, + struct_def, + item.ident, + generics, + from_scratch, + is_packed, + ), + ast::ItemKind::Enum(enum_def, generics) => { + // We ignore `is_packed` 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(struct_def, generics) => { + if self.supports_unions { + self.expand_struct_def( + cx, + struct_def, + item.ident, + generics, + from_scratch, + is_packed, + ) + } else { + cx.dcx().emit_err(errors::DeriveUnion { span: mitem.span }); + 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: &ExtCtxt<'_>, + type_ident: Ident, + generics: &Generics, + field_tys: Vec<P<ast::Ty>>, + methods: Vec<P<ast::AssocItem>>, + is_packed: bool, + ) -> 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: ast::AttrVec::new(), + kind: ast::AssocItemKind::Type(Box::new(ast::TyAlias { + defaultness: ast::Defaultness::Final, + generics: Generics::default(), + where_clauses: ast::TyAliasWhereClauses::default(), + bounds: Vec::new(), + ty: Some(type_def.to_ty(cx, self.span, type_ident, generics)), + })), + tokens: None, + }) + }); + + let mut where_clause = ast::WhereClause::default(); + where_clause.span = generics.where_clause.span; + let ctxt = self.span.ctxt(); + let span = generics.span.with_ctxt(ctxt); + + // Create the generic parameters + let params: ThinVec<_> = generics + .params + .iter() + .map(|param| match ¶m.kind { + GenericParamKind::Lifetime { .. } => param.clone(), + GenericParamKind::Type { .. } => { + // Extra restrictions on the generics parameters to the + // type being derived upon. + let bounds: Vec<_> = self + .additional_bounds + .iter() + .map(|p| { + cx.trait_bound( + p.to_path(cx, self.span, type_ident, generics), + self.is_const, + ) + }) + .chain( + // Add a bound for the current trait. + self.skip_path_as_bound + .not() + .then(|| cx.trait_bound(trait_path.clone(), self.is_const)), + ) + .chain({ + // Add a `Copy` bound if required. + if is_packed && self.needs_copy_as_bound_if_packed { + let p = deriving::path_std!(marker::Copy); + Some(cx.trait_bound( + p.to_path(cx, self.span, type_ident, generics), + self.is_const, + )) + } else { + None + } + }) + .chain( + // Also add in any bounds from the declaration. + param.bounds.iter().cloned(), + ) + .collect(); + + cx.typaram(param.ident.span.with_ctxt(ctxt), param.ident, 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 + } + }) + .collect(); + + // 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 { span, ..we.clone() }) + } + } + })); + + let ty_param_names: Vec<Symbol> = params + .iter() + .filter(|param| matches!(param.kind, ast::GenericParamKind::Type { .. })) + .map(|ty_param| ty_param.ident.name) + .collect(); + + if !ty_param_names.is_empty() { + 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(_, p) = &field_ty_param.ty.kind + && let [sole_segment] = &*p.segments + && ty_param_names.contains(&sole_segment.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), + self.is_const, + ) + }) + .collect(); + + // Require the current trait. + if !self.skip_path_as_bound { + bounds.push(cx.trait_bound(trait_path.clone(), self.is_const)); + } + + // Add a `Copy` bound if required. + if is_packed && self.needs_copy_as_bound_if_packed { + let p = deriving::path_std!(marker::Copy); + bounds.push(cx.trait_bound( + p.to_path(cx, self.span, type_ident, generics), + self.is_const, + )); + } + + if !bounds.is_empty() { + 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 attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span),]; + let opt_trait_ref = Some(trait_ref); + + cx.item( + self.span, + Ident::empty(), + attrs, + ast::ItemKind::Impl(Box::new(ast::Impl { + safety: ast::Safety::Default, + polarity: ast::ImplPolarity::Positive, + defaultness: ast::Defaultness::Final, + constness: if self.is_const { ast::Const::Yes(DUMMY_SP) } else { 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: &ExtCtxt<'_>, + struct_def: &'a VariantData, + type_ident: Ident, + generics: &Generics, + from_scratch: bool, + is_packed: 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, + ) + }; + + 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, is_packed) + } + + fn expand_enum_def( + &self, + cx: &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(); + + let is_packed = false; // enums are never packed + self.create_derived_impl(cx, type_ident, generics, field_tys, methods, is_packed) + } +} + +impl<'a> MethodDef<'a> { + fn call_substructure_method( + &self, + cx: &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: &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: &ExtCtxt<'_>, + trait_: &TraitDef<'_>, + type_ident: Ident, + generics: &Generics, + ) -> (Option<ast::ExplicitSelf>, ThinVec<P<Expr>>, Vec<P<Expr>>, Vec<(Ident, P<ast::Ty>)>) { + let mut selflike_args = ThinVec::new(); + let mut nonselflike_args = Vec::new(); + let mut nonself_arg_tys = Vec::new(); + let span = trait_.span; + + let explicit_self = self.explicit_self.then(|| { + let (self_expr, explicit_self) = ty::get_explicit_self(cx, span); + selflike_args.push(self_expr); + explicit_self + }); + + 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.dcx().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: &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, we use a local block to force a copy. + /// This requires that the field impl `Copy`. + /// + /// ```rust,ignore (example) + /// # 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); + /// } + /// } + /// ``` + fn expand_struct_method_body<'b>( + &self, + cx: &ExtCtxt<'_>, + trait_: &TraitDef<'b>, + struct_def: &'b VariantData, + type_ident: Ident, + selflike_args: &[P<Expr>], + nonselflike_args: &[P<Expr>], + is_packed: bool, + ) -> BlockOrExpr { + assert!(selflike_args.len() == 1 || selflike_args.len() == 2); + + let selflike_fields = + trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, is_packed); + self.call_substructure_method( + cx, + trait_, + type_ident, + nonselflike_args, + &Struct(struct_def, selflike_fields), + ) + } + + fn expand_static_struct_method_body( + &self, + cx: &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: + /// + /// ``` + /// #![feature(core_intrinsics)] + /// enum A { + /// A1, + /// A2(i32) + /// } + /// impl ::core::cmp::PartialEq for A { + /// #[inline] + /// fn eq(&self, other: &A) -> bool { + /// let __self_discr = ::core::intrinsics::discriminant_value(self); + /// let __arg1_discr = ::core::intrinsics::discriminant_value(other); + /// __self_discr == __arg1_discr + /// && match (self, other) { + /// (A::A2(__self_0), A::A2(__arg1_0)) => *__self_0 == *__arg1_0, + /// _ => true, + /// } + /// } + /// } + /// ``` + /// + /// Creates a discriminant 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 + /// `Unify`), and possibly a default arm. + fn expand_enum_method_body<'b>( + &self, + cx: &ExtCtxt<'_>, + trait_: &TraitDef<'b>, + enum_def: &'b EnumDef, + type_ident: Ident, + mut selflike_args: ThinVec<P<Expr>>, + nonselflike_args: &[P<Expr>], + ) -> BlockOrExpr { + assert!( + !selflike_args.is_empty(), + "static methods must use `expand_static_enum_method_body`", + ); + + let span = trait_.span; + let variants = &enum_def.variants; + + // Traits that unify fieldless variants always use the discriminant(s). + let unify_fieldless_variants = + self.fieldless_variants_strategy == FieldlessVariantsStrategy::Unify; + + // For zero-variant enum, this function body is unreachable. Generate + // `match *self {}`. This produces machine code identical to `unsafe { + // core::intrinsics::unreachable() }` while being safe and stable. + if variants.is_empty() { + selflike_args.truncate(1); + let match_arg = cx.expr_deref(span, selflike_args.pop().unwrap()); + let match_arms = ThinVec::new(); + let expr = cx.expr_match(span, match_arg, match_arms); + return BlockOrExpr(ThinVec::new(), Some(expr)); + } + + 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_discr = ::core::intrinsics::discriminant_value(self); + // let __arg1_discr = ::core::intrinsics::discriminant_value(other); + // ``` + let get_discr_pieces = |cx: &ExtCtxt<'_>| { + let discr_idents: Vec<_> = prefixes + .iter() + .map(|name| Ident::from_str_and_span(&format!("{name}_discr"), span)) + .collect(); + + let mut discr_exprs: Vec<_> = discr_idents + .iter() + .map(|&ident| cx.expr_addr_of(span, cx.expr_ident(span, ident))) + .collect(); + + let self_expr = discr_exprs.remove(0); + let other_selflike_exprs = discr_exprs; + let discr_field = FieldInfo { span, name: None, self_expr, other_selflike_exprs }; + + let discr_let_stmts: ThinVec<_> = iter::zip(&discr_idents, &selflike_args) + .map(|(&ident, selflike_arg)| { + let variant_value = deriving::call_intrinsic( + cx, + span, + sym::discriminant_value, + thin_vec![selflike_arg.clone()], + ); + cx.stmt_let(span, false, ident, variant_value) + }) + .collect(); + + (discr_field, discr_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 variants.len() > 1 { + match self.fieldless_variants_strategy { + FieldlessVariantsStrategy::Unify => { + // If the type is fieldless and the trait uses the discriminant and + // there are multiple variants, we need just an operation on + // the discriminant(s). + let (discr_field, mut discr_let_stmts) = get_discr_pieces(cx); + let mut discr_check = self.call_substructure_method( + cx, + trait_, + type_ident, + nonselflike_args, + &EnumDiscr(discr_field, None), + ); + discr_let_stmts.append(&mut discr_check.0); + return BlockOrExpr(discr_let_stmts, discr_check.1); + } + FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless => { + return self.call_substructure_method( + cx, + trait_, + type_ident, + nonselflike_args, + &AllFieldlessEnum(enum_def), + ); + } + FieldlessVariantsStrategy::Default => (), + } + } else if variants.len() == 1 { + // If there is a single variant, we don't need an operation on + // the discriminant(s). Just use the most degenerate result. + return self.call_substructure_method( + cx, + trait_, + type_ident, + nonselflike_args, + &EnumMatching(0, &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: ThinVec<ast::Arm> = variants + .iter() + .enumerate() + .filter(|&(_, v)| !(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 fields = trait_.create_struct_pattern_fields(cx, &variant.data, &prefixes); + + let sp = variant.span.with_ctxt(trait_.span.ctxt()); + let variant_path = cx.path(sp, vec![type_ident, variant.ident]); + let by_ref = ByRef::No; // because enums can't be repr(packed) + let mut subpats = trait_.create_struct_patterns( + cx, + variant_path, + &variant.data, + &prefixes, + by_ref, + ); + + // `(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, 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 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, 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: ThinVec<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 discriminant and there are multiple variants, we need + // to add a discriminant check operation before the match. Otherwise, the match + // is enough. + if unify_fieldless_variants && variants.len() > 1 { + let (discr_field, mut discr_let_stmts) = get_discr_pieces(cx); + + // Combine a discriminant check with the match. + let mut discr_check_plus_match = self.call_substructure_method( + cx, + trait_, + type_ident, + nonselflike_args, + &EnumDiscr(discr_field, Some(get_match_expr(selflike_args))), + ); + discr_let_stmts.append(&mut discr_check_plus_match.0); + BlockOrExpr(discr_let_stmts, discr_check_plus_match.1) + } else { + BlockOrExpr(ThinVec::new(), Some(get_match_expr(selflike_args))) + } + } + + fn expand_static_enum_method_body( + &self, + cx: &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: &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 = match struct_def { + ast::VariantData::Tuple(..) => IsTuple::Yes, + _ => IsTuple::No, + }; + match (just_spans.is_empty(), named_idents.is_empty()) { + (false, false) => cx + .dcx() + .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: &ExtCtxt<'_>, + struct_path: ast::Path, + struct_def: &'a VariantData, + prefixes: &[String], + by_ref: ByRef, + ) -> ThinVec<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 ident = self.mk_pattern_ident(prefix, i); + let path = ident.with_span_pos(sp); + ( + sp, + struct_field.ident, + cx.pat( + path.span, + PatKind::Ident(BindingMode(by_ref, Mutability::Not), 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.dcx().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: &ExtCtxt<'_>, + struct_def: &'a VariantData, + prefixes: &[String], + ) -> Vec<FieldInfo> { + self.create_fields(struct_def, |i, _struct_field, sp| { + prefixes + .iter() + .map(|prefix| { + let ident = self.mk_pattern_ident(prefix, i); + cx.expr_path(cx.path_ident(sp, ident)) + }) + .collect() + }) + } + + fn create_struct_field_access_fields( + &self, + cx: &ExtCtxt<'_>, + selflike_args: &[P<Expr>], + struct_def: &'a VariantData, + is_packed: 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 is_packed { + // In general, fields in packed structs are copied via a + // block, e.g. `&{self.0}`. The two exceptions are `[u8]` + // and `str` fields, which cannot be copied and also never + // cause unaligned references. These exceptions are allowed + // to handle the `FlexZeroSlice` type in the `zerovec` + // crate within `icu4x-0.9.0`. + // + // Once use of `icu4x-0.9.0` has dropped sufficiently, this + // exception should be removed. + let is_simple_path = |ty: &P<ast::Ty>, sym| { + if let TyKind::Path(None, ast::Path { segments, .. }) = &ty.kind + && let [seg] = segments.as_slice() + && seg.ident.name == sym + && seg.args.is_none() + { + true + } else { + false + } + }; + + let exception = if let TyKind::Slice(ty) = &struct_field.ty.kind + && is_simple_path(ty, sym::u8) + { + Some("byte") + } else if is_simple_path(&struct_field.ty, sym::str) { + Some("string") + } else { + None + }; + + if let Some(ty) = exception { + cx.sess.psess.buffer_lint( + BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE, + sp, + ast::CRATE_NODE_ID, + rustc_lint_defs::BuiltinLintDiag::ByteSliceInPackedStructWithDerive { + ty: ty.to_string(), + }, + ); + } else { + // Wrap the expression in `{...}`, causing a copy. + field_expr = cx.expr_block( + cx.block(struct_field.span, thin_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(crate) 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(crate) fn cs_fold<F>( + use_foldl: bool, + cx: &ExtCtxt<'_>, + trait_span: Span, + substructure: &Substructure<'_>, + mut f: F, +) -> P<Expr> +where + F: FnMut(&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) + } + } + EnumDiscr(discr_field, match_expr) => { + let discr_check_expr = f(cx, CsFold::Single(discr_field)); + if let Some(match_expr) = match_expr { + if use_foldl { + f(cx, CsFold::Combine(trait_span, discr_check_expr, match_expr.clone())) + } else { + f(cx, CsFold::Combine(trait_span, match_expr.clone(), discr_check_expr)) + } + } else { + discr_check_expr + } + } + StaticEnum(..) | StaticStruct(..) => { + cx.dcx().span_bug(trait_span, "static function in `derive`") + } + AllFieldlessEnum(..) => cx.dcx().span_bug(trait_span, "fieldless enum in `derive`"), + } +} 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 00000000000..f01d586033e --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs @@ -0,0 +1,204 @@ +//! 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(crate) 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; +use rustc_span::symbol::{kw, Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use thin_vec::ThinVec; + +/// A path, e.g., `::std::option::Option::<i32>` (global). Has support +/// for type parameters. +#[derive(Clone)] +pub(crate) struct Path { + path: Vec<Symbol>, + params: Vec<Box<Ty>>, + kind: PathKind, +} + +#[derive(Clone)] +pub(crate) 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(crate) 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(crate) 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_ref(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(ThinVec::new()); + 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(p) => p.to_path(cx, span, self_ty, generics), + Ref(..) => cx.dcx().span_bug(span, "ref in a path in generic `derive`"), + Unit => cx.dcx().span_bug(span, "unit in a path in generic `derive`"), + } + } +} + +fn mk_ty_param( + cx: &ExtCtxt<'_>, + span: Span, + name: Symbol, + 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, false) + }) + .collect(); + cx.typaram(span, Ident::new(name, span), bounds, None) +} + +/// Bounds on type parameters. +#[derive(Clone)] +pub(crate) 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(|&(name, ref bounds)| mk_ty_param(cx, span, name, bounds, self_ty, self_generics)) + .collect(); + + Generics { + params, + where_clause: ast::WhereClause { + has_where_token: false, + predicates: ThinVec::new(), + span, + }, + span, + } + } +} + +pub(crate) 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 00000000000..dcd92819865 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/hash.rs @@ -0,0 +1,78 @@ +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; +use thin_vec::thin_vec; + +pub(crate) fn expand_deriving_hash( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + 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, + path, + skip_path_as_bound: false, + needs_copy_as_bound_if_packed: true, + additional_bounds: Vec::new(), + 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: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + hash_substructure(a, b, c) + })), + }], + associated_types: Vec::new(), + is_const, + }; + + hash_trait_def.expand(cx, mitem, item, push); +} + +fn hash_substructure(cx: &ExtCtxt<'_>, trait_span: Span, substr: &Substructure<'_>) -> BlockOrExpr { + let [state_expr] = substr.nonselflike_args else { + cx.dcx().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, thin_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) + } + EnumDiscr(discr_field, match_expr) => { + assert!(discr_field.other_selflike_exprs.is_empty()); + let stmts = thin_vec![call_hash(discr_field.span, discr_field.self_expr.clone())]; + (stmts, match_expr.clone()) + } + _ => cx.dcx().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 00000000000..32936ac183d --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs @@ -0,0 +1,133 @@ +//! The compiler code necessary to implement the `#[derive]` extensions. + +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::{GenericArg, MetaItem}; +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier}; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::Span; +use thin_vec::{thin_vec, ThinVec}; + +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(crate) mod bounds; +pub(crate) mod clone; +pub(crate) mod debug; +pub(crate) mod decodable; +pub(crate) mod default; +pub(crate) mod encodable; +pub(crate) mod hash; +pub(crate) mod smart_ptr; + +#[path = "cmp/eq.rs"] +pub(crate) mod eq; +#[path = "cmp/ord.rs"] +pub(crate) mod ord; +#[path = "cmp/partial_eq.rs"] +pub(crate) mod partial_eq; +#[path = "cmp/partial_ord.rs"] +pub(crate) mod partial_ord; + +pub(crate) mod generic; + +pub(crate) type BuiltinDeriveFn = + fn(&ExtCtxt<'_>, Span, &MetaItem, &Annotatable, &mut dyn FnMut(Annotatable), bool); + +pub(crate) struct BuiltinDerive(pub(crate) BuiltinDeriveFn); + +impl MultiItemModifier for BuiltinDerive { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &MetaItem, + item: Annotatable, + is_derive_const: bool, + ) -> 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, + }))); + }, + is_derive_const, + ); + } else { + unreachable!("should have already errored on non-item statement") + } + } + _ => { + (self.0)(ecx, span, meta_item, &item, &mut |a| items.push(a), is_derive_const); + } + } + ExpandResult::Ready(items) + } +} + +/// Constructs an expression that calls an intrinsic +fn call_intrinsic( + cx: &ExtCtxt<'_>, + span: Span, + intrinsic: Symbol, + args: ThinVec<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, ThinVec::new()); + + cx.expr_block(P(ast::Block { + stmts: thin_vec![cx.stmt_expr(call)], + id: ast::DUMMY_NODE_ID, + rules: ast::BlockCheckMode::Unsafe(ast::CompilerGenerated), + span, + tokens: None, + could_be_bare_literal: false, + })) +} + +fn assert_ty_bounds( + cx: &ExtCtxt<'_>, + stmts: &mut ThinVec<ast::Stmt>, + ty: P<ast::Ty>, + span: Span, + assert_path: &[Symbol], +) { + // Deny anonymous structs or unions to avoid weird errors. + assert!(!ty.kind.is_anon_adt(), "Anonymous structs or unions cannot be type parameters"); + // 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/deriving/smart_ptr.rs b/compiler/rustc_builtin_macros/src/deriving/smart_ptr.rs new file mode 100644 index 00000000000..ea054a7e355 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/smart_ptr.rs @@ -0,0 +1,140 @@ +use std::mem::swap; + +use ast::HasAttrs; +use rustc_ast::{ + self as ast, GenericArg, GenericBound, GenericParamKind, ItemKind, MetaItem, + TraitBoundModifiers, +}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::Span; +use smallvec::{smallvec, SmallVec}; +use thin_vec::{thin_vec, ThinVec}; + +macro_rules! path { + ($span:expr, $($part:ident)::*) => { vec![$(Ident::new(sym::$part, $span),)*] } +} + +pub fn expand_deriving_smart_ptr( + cx: &ExtCtxt<'_>, + span: Span, + _mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + _is_const: bool, +) { + let (name_ident, generics) = if let Annotatable::Item(aitem) = item + && let ItemKind::Struct(_, g) = &aitem.kind + { + (aitem.ident, g) + } else { + cx.dcx().struct_span_err(span, "`SmartPointer` can only be derived on `struct`s").emit(); + return; + }; + + // Convert generic parameters (from the struct) into generic args. + let mut pointee_param = None; + let mut multiple_pointee_diag: SmallVec<[_; 2]> = smallvec![]; + let self_params = generics + .params + .iter() + .enumerate() + .map(|(idx, p)| match p.kind { + GenericParamKind::Lifetime => GenericArg::Lifetime(cx.lifetime(p.span(), p.ident)), + GenericParamKind::Type { .. } => { + if p.attrs().iter().any(|attr| attr.has_name(sym::pointee)) { + if pointee_param.is_some() { + multiple_pointee_diag.push(cx.dcx().struct_span_err( + p.span(), + "`SmartPointer` can only admit one type as pointee", + )); + } else { + pointee_param = Some(idx); + } + } + GenericArg::Type(cx.ty_ident(p.span(), p.ident)) + } + GenericParamKind::Const { .. } => GenericArg::Const(cx.const_ident(p.span(), p.ident)), + }) + .collect::<Vec<_>>(); + let Some(pointee_param_idx) = pointee_param else { + cx.dcx().struct_span_err( + span, + "At least one generic type should be designated as `#[pointee]` in order to derive `SmartPointer` traits", + ).emit(); + return; + }; + if !multiple_pointee_diag.is_empty() { + for diag in multiple_pointee_diag { + diag.emit(); + } + return; + } + + // Create the type of `self`. + let path = cx.path_all(span, false, vec![name_ident], self_params.clone()); + let self_type = cx.ty_path(path); + + // Declare helper function that adds implementation blocks. + // FIXME(dingxiangfei2009): Investigate the set of attributes on target struct to be propagated to impls + let attrs = thin_vec![cx.attr_word(sym::automatically_derived, span),]; + let mut add_impl_block = |generics, trait_symbol, trait_args| { + let mut parts = path!(span, core::ops); + parts.push(Ident::new(trait_symbol, span)); + let trait_path = cx.path_all(span, true, parts, trait_args); + let trait_ref = cx.trait_ref(trait_path); + let item = cx.item( + span, + Ident::empty(), + attrs.clone(), + ast::ItemKind::Impl(Box::new(ast::Impl { + safety: ast::Safety::Default, + polarity: ast::ImplPolarity::Positive, + defaultness: ast::Defaultness::Final, + constness: ast::Const::No, + generics, + of_trait: Some(trait_ref), + self_ty: self_type.clone(), + items: ThinVec::new(), + })), + ); + push(Annotatable::Item(item)); + }; + + // Create unsized `self`, that is, one where the `#[pointee]` type arg is replaced with `__S`. For + // example, instead of `MyType<'a, T>`, it will be `MyType<'a, __S>`. + let s_ty = cx.ty_ident(span, Ident::new(sym::__S, span)); + let mut alt_self_params = self_params; + alt_self_params[pointee_param_idx] = GenericArg::Type(s_ty.clone()); + let alt_self_type = cx.ty_path(cx.path_all(span, false, vec![name_ident], alt_self_params)); + + // Find the `#[pointee]` parameter and add an `Unsize<__S>` bound to it. + let mut impl_generics = generics.clone(); + { + let p = &mut impl_generics.params[pointee_param_idx]; + let arg = GenericArg::Type(s_ty.clone()); + let unsize = cx.path_all(span, true, path!(span, core::marker::Unsize), vec![arg]); + p.bounds.push(cx.trait_bound(unsize, false)); + let mut attrs = thin_vec![]; + swap(&mut p.attrs, &mut attrs); + p.attrs = attrs.into_iter().filter(|attr| !attr.has_name(sym::pointee)).collect(); + } + + // Add the `__S: ?Sized` extra parameter to the impl block. + let sized = cx.path_global(span, path!(span, core::marker::Sized)); + let bound = GenericBound::Trait( + cx.poly_trait_ref(span, sized), + TraitBoundModifiers { + polarity: ast::BoundPolarity::Maybe(span), + constness: ast::BoundConstness::Never, + asyncness: ast::BoundAsyncness::Normal, + }, + ); + let extra_param = cx.typaram(span, Ident::new(sym::__S, span), vec![bound], None); + impl_generics.params.push(extra_param); + + // Add the impl blocks for `DispatchFromDyn` and `CoerceUnsized`. + let gen_args = vec![GenericArg::Type(alt_self_type.clone())]; + add_impl_block(impl_generics.clone(), sym::DispatchFromDyn, gen_args.clone()); + add_impl_block(impl_generics.clone(), sym::CoerceUnsized, gen_args.clone()); +} 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 00000000000..cc385bade47 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/edition_panic.rs @@ -0,0 +1,86 @@ +use rustc_ast::ptr::P; +use rustc_ast::token::Delimiter; +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(crate) fn expand_panic<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'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(crate) fn expand_unreachable<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'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 ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let sp = cx.with_call_site_ctxt(sp); + + ExpandResult::Ready(MacEager::expr( + cx.expr( + sp, + ExprKind::MacCall(P(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(DelimArgs { + dspan: DelimSpan::from_single(sp), + delim: Delimiter::Parenthesis, + tokens: tts, + }), + })), + ), + )) +} + +pub(crate) 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 00000000000..b03e14cf263 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -0,0 +1,157 @@ +// The compiler code necessary to support the env! extension. Eventually this +// should all get sucked into either the compiler syntax extension plugin +// interface. +// + +use crate::errors; +use crate::util::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts}; +use rustc_ast::token::{self, LitKind}; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{AstDeref, ExprKind, GenericArg, Mutability}; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::Span; +use std::env; +use std::env::VarError; +use thin_vec::thin_vec; + +fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> { + let var = var.as_str(); + if let Some(value) = cx.sess.opts.logical_env.get(var) { + return Ok(Symbol::intern(value)); + } + // If the environment variable was not defined with the `--env-set` option, we try to retrieve it + // from rustc's environment. + Ok(Symbol::intern(&env::var(var)?)) +} + +pub(crate) fn expand_option_env<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "option_env!") else { + return ExpandResult::Retry(()); + }; + let var = match mac { + Ok(var) => var, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + + let sp = cx.with_def_site_ctxt(sp); + let value = lookup_env(cx, var).ok(); + cx.sess.psess.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_ref( + sp, + cx.ty_ident(sp, Ident::new(sym::str, sp)), + Some(lt), + Mutability::Not, + ))], + )) + } + Some(value) => cx.expr_call_global( + sp, + cx.std_path(&[sym::option, sym::Option, sym::Some]), + thin_vec![cx.expr_str(sp, value)], + ), + }; + ExpandResult::Ready(MacEager::expr(e)) +} + +pub(crate) fn expand_env<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else { + return ExpandResult::Retry(()); + }; + let mut exprs = match mac { + Ok(exprs) if exprs.is_empty() || exprs.len() > 2 => { + let guar = cx.dcx().emit_err(errors::EnvTakesArgs { span: sp }); + return ExpandResult::Ready(DummyResult::any(sp, guar)); + } + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + Ok(exprs) => exprs.into_iter(), + }; + + let var_expr = exprs.next().unwrap(); + let ExpandResult::Ready(mac) = expr_to_string(cx, var_expr.clone(), "expected string literal") + else { + return ExpandResult::Retry(()); + }; + let var = match mac { + Ok((var, _)) => var, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + + let custom_msg = match exprs.next() { + None => None, + Some(second) => { + let ExpandResult::Ready(mac) = expr_to_string(cx, second, "expected string literal") + else { + return ExpandResult::Retry(()); + }; + match mac { + Ok((s, _)) => Some(s), + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + } + } + }; + + let span = cx.with_def_site_ctxt(sp); + let value = lookup_env(cx, var); + cx.sess.psess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied())); + let e = match value { + Err(err) => { + let ExprKind::Lit(token::Lit { + kind: LitKind::Str | LitKind::StrRaw(..), symbol, .. + }) = &var_expr.kind + else { + unreachable!("`expr_to_string` ensures this is a string lit") + }; + + let guar = match err { + VarError::NotPresent => { + if let Some(msg_from_user) = custom_msg { + cx.dcx() + .emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user }) + } else if is_cargo_env_var(var.as_str()) { + cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar { + span, + var: *symbol, + var_expr: var_expr.ast_deref(), + }) + } else { + cx.dcx().emit_err(errors::EnvNotDefined::CustomEnvVar { + span, + var: *symbol, + var_expr: var_expr.ast_deref(), + }) + } + } + VarError::NotUnicode(_) => { + cx.dcx().emit_err(errors::EnvNotUnicode { span, var: *symbol }) + } + }; + + return ExpandResult::Ready(DummyResult::any(sp, guar)); + } + Ok(value) => cx.expr_str(span, value), + }; + ExpandResult::Ready(MacEager::expr(e)) +} + +/// Returns `true` if an environment variable from `env!` is one used by Cargo. +fn is_cargo_env_var(var: &str) -> bool { + var.starts_with("CARGO_") + || var.starts_with("DEP_") + || matches!(var, "OUT_DIR" | "OPT_LEVEL" | "PROFILE" | "HOST" | "TARGET") +} diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs new file mode 100644 index 00000000000..ed2f98f2a39 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -0,0 +1,874 @@ +use rustc_errors::{ + codes::*, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, MultiSpan, + SingleLabelManySpans, SubdiagMessageOp, Subdiagnostic, +}; +use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_span::{symbol::Ident, Span, Symbol}; + +#[derive(Diagnostic)] +#[diag(builtin_macros_requires_cfg_pattern)] +pub(crate) struct RequiresCfgPattern { + #[primary_span] + #[label] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_expected_one_cfg_pattern)] +pub(crate) struct OneCfgPattern { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_alloc_error_must_be_fn)] +pub(crate) struct AllocErrorMustBeFn { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_assert_requires_boolean)] +pub(crate) struct AssertRequiresBoolean { + #[primary_span] + #[label] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_assert_requires_expression)] +pub(crate) struct AssertRequiresExpression { + #[primary_span] + pub(crate) span: Span, + #[suggestion(code = "", applicability = "maybe-incorrect")] + pub(crate) token: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_assert_missing_comma)] +pub(crate) struct AssertMissingComma { + #[primary_span] + pub(crate) span: Span, + #[suggestion(code = ", ", applicability = "maybe-incorrect", style = "short")] + pub(crate) comma: Span, +} + +#[derive(Diagnostic)] +pub(crate) enum CfgAccessibleInvalid { + #[diag(builtin_macros_cfg_accessible_unspecified_path)] + UnspecifiedPath(#[primary_span] Span), + #[diag(builtin_macros_cfg_accessible_multiple_paths)] + MultiplePaths(#[primary_span] Span), + #[diag(builtin_macros_cfg_accessible_literal_path)] + LiteralPath(#[primary_span] Span), + #[diag(builtin_macros_cfg_accessible_has_args)] + HasArguments(#[primary_span] Span), +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_cfg_accessible_indeterminate)] +pub(crate) struct CfgAccessibleIndeterminate { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_missing_literal)] +#[note] +pub(crate) struct ConcatMissingLiteral { + #[primary_span] + pub(crate) spans: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytestr)] +pub(crate) struct ConcatBytestr { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_c_str_lit)] +pub(crate) struct ConcatCStrLit { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_export_macro_rules)] +pub(crate) struct ExportMacroRules { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_proc_macro)] +pub(crate) struct ProcMacro { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_invalid_crate_attribute)] +pub(crate) struct InvalidCrateAttr { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_non_abi)] +pub(crate) struct NonABI { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_trace_macros)] +pub(crate) struct TraceMacros { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_bench_sig)] +pub(crate) struct BenchSig { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_alloc_must_statics)] +pub(crate) struct AllocMustStatics { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_invalid)] +pub(crate) struct ConcatBytesInvalid { + #[primary_span] + pub(crate) span: Span, + pub(crate) lit_kind: &'static str, + #[subdiagnostic] + pub(crate) sugg: Option<ConcatBytesInvalidSuggestion>, +} + +#[derive(Subdiagnostic)] +pub(crate) enum ConcatBytesInvalidSuggestion { + #[suggestion( + builtin_macros_byte_char, + code = "b{snippet}", + applicability = "machine-applicable" + )] + CharLit { + #[primary_span] + span: Span, + snippet: String, + }, + #[suggestion( + builtin_macros_byte_str, + code = "b{snippet}", + applicability = "machine-applicable" + )] + StrLit { + #[primary_span] + span: Span, + snippet: String, + }, + #[suggestion( + builtin_macros_number_array, + code = "[{snippet}]", + applicability = "machine-applicable" + )] + IntLit { + #[primary_span] + span: Span, + snippet: String, + }, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_oob)] +pub(crate) struct ConcatBytesOob { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_non_u8)] +pub(crate) struct ConcatBytesNonU8 { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_missing_literal)] +#[note] +pub(crate) struct ConcatBytesMissingLiteral { + #[primary_span] + pub(crate) spans: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_array)] +pub(crate) struct ConcatBytesArray { + #[primary_span] + pub(crate) span: Span, + #[note] + #[help] + pub(crate) bytestr: bool, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_bytes_bad_repeat)] +pub(crate) struct ConcatBytesBadRepeat { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_idents_missing_args)] +pub(crate) struct ConcatIdentsMissingArgs { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_idents_missing_comma)] +pub(crate) struct ConcatIdentsMissingComma { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_idents_ident_args)] +pub(crate) struct ConcatIdentsIdentArgs { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_bad_derive_target, code = E0774)] +pub(crate) struct BadDeriveTarget { + #[primary_span] + #[label] + pub(crate) span: Span, + #[label(builtin_macros_label2)] + pub(crate) item: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_tests_not_support)] +pub(crate) struct TestsNotSupport {} + +#[derive(Diagnostic)] +#[diag(builtin_macros_unexpected_lit, code = E0777)] +pub(crate) struct BadDeriveLit { + #[primary_span] + #[label] + pub(crate) span: Span, + #[subdiagnostic] + pub help: BadDeriveLitHelp, +} + +#[derive(Subdiagnostic)] +pub(crate) enum BadDeriveLitHelp { + #[help(builtin_macros_str_lit)] + StrLit { sym: Symbol }, + #[help(builtin_macros_other)] + Other, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_path_args_list)] +pub(crate) struct DerivePathArgsList { + #[suggestion(code = "", applicability = "machine-applicable")] + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_path_args_value)] +pub(crate) struct DerivePathArgsValue { + #[suggestion(code = "", applicability = "machine-applicable")] + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_unsafe_path)] +pub(crate) struct DeriveUnsafePath { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_no_default_variant)] +#[help] +pub(crate) struct NoDefaultVariant { + #[primary_span] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) suggs: Vec<NoDefaultVariantSugg>, +} + +#[derive(Subdiagnostic)] +#[suggestion( + builtin_macros_suggestion, + code = "#[default] {ident}", + applicability = "maybe-incorrect", + style = "tool-only" +)] +pub(crate) struct NoDefaultVariantSugg { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_multiple_defaults)] +#[note] +pub(crate) struct MultipleDefaults { + #[primary_span] + pub(crate) span: Span, + #[label] + pub(crate) first: Span, + #[label(builtin_macros_additional)] + pub additional: Vec<Span>, + #[subdiagnostic] + pub suggs: Vec<MultipleDefaultsSugg>, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + builtin_macros_suggestion, + applicability = "maybe-incorrect", + style = "tool-only" +)] +pub(crate) struct MultipleDefaultsSugg { + #[suggestion_part(code = "")] + pub(crate) spans: Vec<Span>, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_non_unit_default)] +#[help] +pub(crate) struct NonUnitDefault { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_non_exhaustive_default)] +#[help] +pub(crate) struct NonExhaustiveDefault { + #[primary_span] + pub(crate) span: Span, + #[label] + pub(crate) non_exhaustive: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_multiple_default_attrs)] +#[note] +pub(crate) struct MultipleDefaultAttrs { + #[primary_span] + pub(crate) span: Span, + #[label] + pub(crate) first: Span, + #[label(builtin_macros_label_again)] + pub(crate) first_rest: Span, + #[help] + pub(crate) rest: MultiSpan, + pub(crate) only_one: bool, + #[subdiagnostic] + pub(crate) sugg: MultipleDefaultAttrsSugg, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + builtin_macros_help, + applicability = "machine-applicable", + style = "tool-only" +)] +pub(crate) struct MultipleDefaultAttrsSugg { + #[suggestion_part(code = "")] + pub(crate) spans: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_default_arg)] +pub(crate) struct DefaultHasArg { + #[primary_span] + #[suggestion(code = "#[default]", style = "hidden", applicability = "maybe-incorrect")] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_macro_call)] +pub(crate) struct DeriveMacroCall { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_cannot_derive_union)] +pub(crate) struct DeriveUnion { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_env_takes_args)] +pub(crate) struct EnvTakesArgs { + #[primary_span] + pub(crate) span: Span, +} + +pub(crate) struct EnvNotDefinedWithUserMessage { + pub(crate) span: Span, + pub(crate) msg_from_user: Symbol, +} + +// Hand-written implementation to support custom user messages. +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for EnvNotDefinedWithUserMessage { + #[track_caller] + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { + #[expect( + rustc::untranslatable_diagnostic, + reason = "cannot translate user-provided messages" + )] + let mut diag = Diag::new(dcx, level, self.msg_from_user.to_string()); + diag.span(self.span); + diag + } +} + +#[derive(Diagnostic)] +pub(crate) enum EnvNotDefined<'a> { + #[diag(builtin_macros_env_not_defined)] + #[help(builtin_macros_cargo)] + CargoEnvVar { + #[primary_span] + span: Span, + var: Symbol, + var_expr: &'a rustc_ast::Expr, + }, + #[diag(builtin_macros_env_not_defined)] + #[help(builtin_macros_custom)] + CustomEnvVar { + #[primary_span] + span: Span, + var: Symbol, + var_expr: &'a rustc_ast::Expr, + }, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_env_not_unicode)] +pub(crate) struct EnvNotUnicode { + #[primary_span] + pub(crate) span: Span, + pub(crate) var: Symbol, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_requires_string)] +pub(crate) struct FormatRequiresString { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_duplicate_arg)] +pub(crate) struct FormatDuplicateArg { + #[primary_span] + pub(crate) span: Span, + #[label(builtin_macros_label1)] + pub(crate) prev: Span, + #[label(builtin_macros_label2)] + pub(crate) duplicate: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_positional_after_named)] +pub(crate) struct PositionalAfterNamed { + #[primary_span] + #[label] + pub(crate) span: Span, + #[label(builtin_macros_named_args)] + pub(crate) args: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_string_invalid)] +pub(crate) struct InvalidFormatString { + #[primary_span] + #[label] + pub(crate) span: Span, + pub(crate) desc: String, + pub(crate) label1: String, + #[subdiagnostic] + pub(crate) note_: Option<InvalidFormatStringNote>, + #[subdiagnostic] + pub(crate) label_: Option<InvalidFormatStringLabel>, + #[subdiagnostic] + pub(crate) sugg_: Option<InvalidFormatStringSuggestion>, +} + +#[derive(Subdiagnostic)] +#[note(builtin_macros_note)] +pub(crate) struct InvalidFormatStringNote { + pub(crate) note: String, +} + +#[derive(Subdiagnostic)] +#[label(builtin_macros_second_label)] +pub(crate) struct InvalidFormatStringLabel { + #[primary_span] + pub(crate) span: Span, + pub(crate) label: String, +} + +#[derive(Subdiagnostic)] +pub(crate) enum InvalidFormatStringSuggestion { + #[multipart_suggestion( + builtin_macros_format_use_positional, + style = "verbose", + applicability = "machine-applicable" + )] + UsePositional { + #[suggestion_part(code = "{len}")] + captured: Span, + len: String, + #[suggestion_part(code = ", {arg}")] + span: Span, + arg: String, + }, + #[suggestion( + builtin_macros_format_remove_raw_ident, + code = "", + applicability = "machine-applicable" + )] + RemoveRawIdent { + #[primary_span] + span: Span, + }, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_no_arg_named)] +#[note] +#[note(builtin_macros_note2)] +pub(crate) struct FormatNoArgNamed { + #[primary_span] + pub(crate) span: Span, + pub(crate) name: Symbol, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_unknown_trait)] +#[note] +pub(crate) struct FormatUnknownTrait<'a> { + #[primary_span] + pub(crate) span: Span, + pub(crate) ty: &'a str, + #[subdiagnostic] + pub(crate) suggs: Vec<FormatUnknownTraitSugg>, +} + +#[derive(Subdiagnostic)] +#[suggestion( + builtin_macros_suggestion, + code = "{fmt}", + style = "tool-only", + applicability = "maybe-incorrect" +)] +pub(crate) struct FormatUnknownTraitSugg { + #[primary_span] + pub span: Span, + pub fmt: &'static str, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_unused_arg)] +pub(crate) struct FormatUnusedArg { + #[primary_span] + #[label(builtin_macros_format_unused_arg)] + pub(crate) span: Span, + pub(crate) named: bool, +} + +// Allow the singular form to be a subdiagnostic of the multiple-unused +// form of diagnostic. +impl Subdiagnostic for FormatUnusedArg { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + f: &F, + ) { + diag.arg("named", self.named); + let msg = f(diag, crate::fluent_generated::builtin_macros_format_unused_arg.into()); + diag.span_label(self.span, msg); + } +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_unused_args)] +pub(crate) struct FormatUnusedArgs { + #[primary_span] + pub(crate) unused: Vec<Span>, + #[label] + pub(crate) fmt: Span, + #[subdiagnostic] + pub(crate) unused_labels: Vec<FormatUnusedArg>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_pos_mismatch)] +pub(crate) struct FormatPositionalMismatch { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) n: usize, + pub(crate) desc: String, + #[subdiagnostic] + pub(crate) highlight: SingleLabelManySpans, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_format_redundant_args)] +pub(crate) struct FormatRedundantArgs { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) n: usize, + + #[note] + pub(crate) note: MultiSpan, + + #[subdiagnostic] + pub(crate) sugg: Option<FormatRedundantArgsSugg>, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(builtin_macros_suggestion, applicability = "machine-applicable")] +pub(crate) struct FormatRedundantArgsSugg { + #[suggestion_part(code = "")] + pub(crate) spans: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_test_case_non_item)] +pub(crate) struct TestCaseNonItem { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_test_bad_fn)] +pub(crate) struct TestBadFn { + #[primary_span] + pub(crate) span: Span, + #[label] + pub(crate) cause: Span, + pub(crate) kind: &'static str, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_explicit_register_name)] +pub(crate) struct AsmExplicitRegisterName { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_mutually_exclusive)] +pub(crate) struct AsmMutuallyExclusive { + #[primary_span] + pub(crate) spans: Vec<Span>, + pub(crate) opt1: &'static str, + pub(crate) opt2: &'static str, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_pure_combine)] +pub(crate) struct AsmPureCombine { + #[primary_span] + pub(crate) spans: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_pure_no_output)] +pub(crate) struct AsmPureNoOutput { + #[primary_span] + pub(crate) spans: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_modifier_invalid)] +pub(crate) struct AsmModifierInvalid { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_requires_template)] +pub(crate) struct AsmRequiresTemplate { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_expected_comma)] +pub(crate) struct AsmExpectedComma { + #[primary_span] + #[label] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_underscore_input)] +pub(crate) struct AsmUnderscoreInput { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_sym_no_path)] +pub(crate) struct AsmSymNoPath { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_expected_other)] +pub(crate) struct AsmExpectedOther { + #[primary_span] + #[label(builtin_macros_asm_expected_other)] + pub(crate) span: Span, + pub(crate) is_global_asm: bool, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_duplicate_arg)] +pub(crate) struct AsmDuplicateArg { + #[primary_span] + #[label(builtin_macros_arg)] + pub(crate) span: Span, + #[label] + pub(crate) prev: Span, + pub(crate) name: Symbol, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_pos_after)] +pub(crate) struct AsmPositionalAfter { + #[primary_span] + #[label(builtin_macros_pos)] + pub(crate) span: Span, + #[label(builtin_macros_named)] + pub(crate) named: Vec<Span>, + #[label(builtin_macros_explicit)] + pub(crate) explicit: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_noreturn)] +pub(crate) struct AsmNoReturn { + #[primary_span] + pub(crate) outputs_sp: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_mayunwind)] +pub(crate) struct AsmMayUnwind { + #[primary_span] + pub(crate) labels_sp: Vec<Span>, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_global_asm_clobber_abi)] +pub(crate) struct GlobalAsmClobberAbi { + #[primary_span] + pub(crate) spans: Vec<Span>, +} + +pub(crate) struct AsmClobberNoReg { + pub(crate) spans: Vec<Span>, + pub(crate) clobbers: Vec<Span>, +} + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AsmClobberNoReg { + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { + // eager translation as `span_labels` takes `AsRef<str>` + let lbl1 = dcx.eagerly_translate_to_string( + crate::fluent_generated::builtin_macros_asm_clobber_abi, + [].into_iter(), + ); + let lbl2 = dcx.eagerly_translate_to_string( + crate::fluent_generated::builtin_macros_asm_clobber_outputs, + [].into_iter(), + ); + Diag::new(dcx, level, crate::fluent_generated::builtin_macros_asm_clobber_no_reg) + .with_span(self.spans.clone()) + .with_span_labels(self.clobbers, &lbl1) + .with_span_labels(self.spans, &lbl2) + } +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_opt_already_provided)] +pub(crate) struct AsmOptAlreadyprovided { + #[primary_span] + #[label] + pub(crate) span: Span, + pub(crate) symbol: Symbol, + #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")] + pub(crate) full_span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_test_runner_invalid)] +pub(crate) struct TestRunnerInvalid { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_test_runner_nargs)] +pub(crate) struct TestRunnerNargs { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_expected_register_class_or_explicit_register)] +pub(crate) struct ExpectedRegisterClassOrExplicitRegister { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_expected_comma_in_list)] +pub(crate) struct ExpectedCommaInList { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_only_one_argument)] +pub(crate) struct OnlyOneArgument<'a> { + #[primary_span] + pub span: Span, + pub name: &'a str, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_takes_no_arguments)] +pub(crate) struct TakesNoArguments<'a> { + #[primary_span] + pub span: Span, + pub name: &'a str, +} diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs new file mode 100644 index 00000000000..5cb0407bd59 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -0,0 +1,1015 @@ +use crate::errors; +use crate::util::expr_to_spanned_string; +use parse::Position::ArgumentNamed; +use rustc_ast::ptr::P; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{token, StmtKind}; +use rustc_ast::{ + Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs, + FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount, + FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, Recovered, +}; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans}; +use rustc_expand::base::*; +use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY; +use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId}; +use rustc_parse_format as parse; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::{BytePos, ErrorGuaranteed, InnerSpan, Span}; + +// The format_args!() macro is expanded in three steps: +// 1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax, +// but doesn't parse the template (the literal) itself. +// 2. Second, `make_format_args` will parse the template, the format options, resolve argument references, +// produce diagnostics, and turn the whole thing into a `FormatArgs` AST node. +// 3. Much later, in AST lowering (rustc_ast_lowering), that `FormatArgs` structure will be turned +// into the expression of type `core::fmt::Arguments`. + +// See rustc_ast/src/format.rs for the FormatArgs structure and glossary. + +// Only used in parse_args and report_invalid_references, +// to indicate how a referred argument was used. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PositionUsedAs { + Placeholder(Option<Span>), + Precision, + Width, +} +use PositionUsedAs::*; + +#[derive(Debug)] +struct MacroInput { + fmtstr: P<Expr>, + args: FormatArguments, + /// Whether the first argument was a string literal or a result from eager macro expansion. + /// If it's not a string literal, we disallow implicit argument capturing. + /// + /// This does not correspond to whether we can treat spans to the literal normally, as the whole + /// invocation might be the result of another macro expansion, in which case this flag may still be true. + /// + /// See [RFC 2795] for more information. + /// + /// [RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html#macro-hygiene + is_direct_literal: bool, +} + +/// Parses the arguments from the given list of tokens, returning the diagnostic +/// if there's a parse error so we can continue parsing other format! +/// expressions. +/// +/// If parsing succeeds, the return value is: +/// +/// ```text +/// Ok((fmtstr, parsed arguments)) +/// ``` +fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> { + let mut args = FormatArguments::new(); + + let mut p = ecx.new_parser_from_tts(tts); + + if p.token == token::Eof { + return Err(ecx.dcx().create_err(errors::FormatRequiresString { span: sp })); + } + + let first_token = &p.token; + + let fmtstr = if let token::Literal(lit) = first_token.kind + && matches!(lit.kind, token::Str | token::StrRaw(_)) + { + // This allows us to properly handle cases when the first comma + // after the format string is mistakenly replaced with any operator, + // which cause the expression parser to eat too much tokens. + p.parse_literal_maybe_minus()? + } else { + // Otherwise, we fall back to the expression parser. + p.parse_expr()? + }; + + // Only allow implicit captures to be used when the argument is a direct literal + // instead of a macro expanding to one. + let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_)); + + let mut first = true; + + while p.token != token::Eof { + if !p.eat(&token::Comma) { + if first { + p.clear_expected_tokens(); + } + + match p.expect(&token::Comma) { + Err(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::Yes(_)) => (), + Ok(Recovered::No) => unreachable!(), + } + } + 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) => { + p.bump(); + p.expect(&token::Eq)?; + let expr = p.parse_expr()?; + if let Some((_, prev)) = args.by_name(ident.name) { + ecx.dcx().emit_err(errors::FormatDuplicateArg { + span: ident.span, + prev: prev.kind.ident().unwrap().span, + duplicate: ident.span, + ident, + }); + continue; + } + args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); + } + _ => { + let expr = p.parse_expr()?; + if !args.named_args().is_empty() { + return Err(ecx.dcx().create_err(errors::PositionalAfterNamed { + span: expr.span, + args: args + .named_args() + .iter() + .filter_map(|a| a.kind.ident().map(|ident| (a, ident))) + .map(|(arg, n)| n.span.to(arg.expr.span)) + .collect(), + })); + } + args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); + } + } + } + Ok(MacroInput { fmtstr, args, is_direct_literal }) +} + +fn make_format_args( + ecx: &mut ExtCtxt<'_>, + input: MacroInput, + append_newline: bool, +) -> ExpandResult<Result<FormatArgs, ErrorGuaranteed>, ()> { + let msg = "format argument must be a string literal"; + let unexpanded_fmt_span = input.fmtstr.span; + + let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input; + + let (fmt_str, fmt_style, fmt_span) = { + let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, efmt.clone(), msg) else { + return ExpandResult::Retry(()); + }; + match mac { + Ok(mut fmt) if append_newline => { + fmt.0 = Symbol::intern(&format!("{}\n", fmt.0)); + fmt + } + Ok(fmt) => fmt, + Err(err) => { + let guar = match err { + Ok((mut err, suggested)) => { + if !suggested { + if let ExprKind::Block(block, None) = &efmt.kind + && block.stmts.len() == 1 + && let StmtKind::Expr(expr) = &block.stmts[0].kind + && let ExprKind::Path(None, path) = &expr.kind + && path.is_potential_trivial_const_arg() + { + err.multipart_suggestion( + "quote your inlined format argument to use as string literal", + vec![ + (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()), + (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + let sugg_fmt = match args.explicit_args().len() { + 0 => "{}".to_string(), + _ => { + format!("{}{{}}", "{} ".repeat(args.explicit_args().len())) + } + }; + err.span_suggestion( + unexpanded_fmt_span.shrink_to_lo(), + "you might be missing a string literal to format with", + format!("\"{sugg_fmt}\", "), + Applicability::MaybeIncorrect, + ); + } + } + err.emit() + } + Err(guar) => guar, + }; + return ExpandResult::Ready(Err(guar)); + } + } + }; + + let str_style = match fmt_style { + rustc_ast::StrStyle::Cooked => None, + rustc_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(unexpanded_fmt_span).ok(); + let mut parser = parse::Parser::new( + fmt_str, + str_style, + fmt_snippet, + append_newline, + parse::ParseMode::Format, + ); + + let mut pieces = Vec::new(); + while let Some(piece) = parser.next() { + if !parser.errors.is_empty() { + break; + } else { + pieces.push(piece); + } + } + + let is_source_literal = parser.is_source_literal; + + if !parser.errors.is_empty() { + let err = parser.errors.remove(0); + let sp = if is_source_literal { + fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)) + } else { + // The format string could be another macro invocation, e.g.: + // 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 = errors::InvalidFormatString { + span: sp, + note_: None, + label_: None, + sugg_: None, + desc: err.description, + label1: err.label, + }; + if let Some(note) = err.note { + e.note_ = Some(errors::InvalidFormatStringNote { note }); + } + if let Some((label, span)) = err.secondary_label + && is_source_literal + { + e.label_ = Some(errors::InvalidFormatStringLabel { + span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)), + label, + }); + } + match err.suggestion { + parse::Suggestion::None => {} + parse::Suggestion::UsePositional => { + let captured_arg_span = + fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)); + if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { + let span = match args.unnamed_args().last() { + Some(arg) => arg.expr.span, + None => fmt_span, + }; + e.sugg_ = Some(errors::InvalidFormatStringSuggestion::UsePositional { + captured: captured_arg_span, + len: args.unnamed_args().len().to_string(), + span: span.shrink_to_hi(), + arg, + }); + } + } + parse::Suggestion::RemoveRawIdent(span) => { + if is_source_literal { + let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end)); + e.sugg_ = Some(errors::InvalidFormatStringSuggestion::RemoveRawIdent { span }) + } + } + } + let guar = ecx.dcx().emit_err(e); + return ExpandResult::Ready(Err(guar)); + } + + let to_span = |inner_span: parse::InnerSpan| { + is_source_literal.then(|| { + fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }) + }) + }; + + let mut used = vec![false; args.explicit_args().len()]; + let mut invalid_refs = Vec::new(); + let mut numeric_references_to_named_arg = Vec::new(); + + enum ArgRef<'a> { + Index(usize), + Name(&'a str, Option<Span>), + } + use ArgRef::*; + + let mut unnamed_arg_after_named_arg = false; + + let mut lookup_arg = |arg: ArgRef<'_>, + span: Option<Span>, + used_as: PositionUsedAs, + kind: FormatArgPositionKind| + -> FormatArgPosition { + let index = match arg { + Index(index) => { + if let Some(arg) = args.by_index(index) { + used[index] = true; + if arg.kind.ident().is_some() { + // This was a named argument, but it was used as a positional argument. + numeric_references_to_named_arg.push((index, span, used_as)); + } + Ok(index) + } else { + // Doesn't exist as an explicit argument. + invalid_refs.push((index, span, used_as, kind)); + Err(index) + } + } + Name(name, span) => { + let name = Symbol::intern(name); + if let Some((index, _)) = args.by_name(name) { + // Name found in `args`, so we resolve it to its index. + if index < args.explicit_args().len() { + // Mark it as used, if it was an explicit argument. + used[index] = true; + } + Ok(index) + } else { + // Name not found in `args`, so we add it as an implicitly captured argument. + let span = span.unwrap_or(fmt_span); + let ident = Ident::new(name, span); + let expr = if is_direct_literal { + ecx.expr_ident(span, ident) + } else { + // For the moment capturing variables from format strings expanded from macros is + // disabled (see RFC #2795) + let guar = ecx.dcx().emit_err(errors::FormatNoArgNamed { span, name }); + unnamed_arg_after_named_arg = true; + DummyResult::raw_expr(span, Some(guar)) + }; + Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) + } + } + }; + FormatArgPosition { index, kind, span } + }; + + let mut template = Vec::new(); + let mut unfinished_literal = String::new(); + let mut placeholder_index = 0; + + for piece in &pieces { + match *piece { + parse::Piece::String(s) => { + unfinished_literal.push_str(s); + } + parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => { + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); + unfinished_literal.clear(); + } + + let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s)); + placeholder_index += 1; + + let position_span = to_span(position_span); + let argument = match position { + parse::ArgumentImplicitlyIs(i) => lookup_arg( + Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Implicit, + ), + parse::ArgumentIs(i) => lookup_arg( + Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Number, + ), + parse::ArgumentNamed(name) => lookup_arg( + Name(name, position_span), + position_span, + Placeholder(span), + FormatArgPositionKind::Named, + ), + }; + + let alignment = match format.align { + parse::AlignUnknown => None, + parse::AlignLeft => Some(FormatAlignment::Left), + parse::AlignRight => Some(FormatAlignment::Right), + parse::AlignCenter => Some(FormatAlignment::Center), + }; + + let format_trait = match format.ty { + "" => FormatTrait::Display, + "?" => FormatTrait::Debug, + "e" => FormatTrait::LowerExp, + "E" => FormatTrait::UpperExp, + "o" => FormatTrait::Octal, + "p" => FormatTrait::Pointer, + "b" => FormatTrait::Binary, + "x" => FormatTrait::LowerHex, + "X" => FormatTrait::UpperHex, + _ => { + invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span); + FormatTrait::Display + } + }; + + let precision_span = format.precision_span.and_then(to_span); + let precision = match format.precision { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + Name(name, to_span(name_span)), + precision_span, + Precision, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + Index(i), + precision_span, + Precision, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg( + Index(i), + precision_span, + Precision, + FormatArgPositionKind::Implicit, + ))), + parse::CountImplied => None, + }; + + let width_span = format.width_span.and_then(to_span); + let width = match format.width { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + Name(name, to_span(name_span)), + width_span, + Width, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + Index(i), + width_span, + Width, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(_) => unreachable!(), + parse::CountImplied => None, + }; + + template.push(FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span, + format_trait, + format_options: FormatOptions { + fill: format.fill, + alignment, + sign: format.sign.map(|s| match s { + parse::Sign::Plus => FormatSign::Plus, + parse::Sign::Minus => FormatSign::Minus, + }), + alternate: format.alternate, + zero_pad: format.zero_pad, + debug_hex: format.debug_hex.map(|s| match s { + parse::DebugHex::Lower => FormatDebugHex::Lower, + parse::DebugHex::Upper => FormatDebugHex::Upper, + }), + precision, + width, + }, + })); + } + } + } + + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); + } + + if !invalid_refs.is_empty() { + report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser); + } + + let unused = used + .iter() + .enumerate() + .filter(|&(_, used)| !used) + .map(|(i, _)| { + let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_)); + (args.explicit_args()[i].expr.span, named) + }) + .collect::<Vec<_>>(); + + let has_unused = !unused.is_empty(); + if has_unused { + // If there's a lot of unused arguments, + // let's check if this format arguments looks like another syntax (printf / shell). + let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2; + report_missing_placeholders( + ecx, + unused, + &used, + &args, + &pieces, + detect_foreign_fmt, + str_style, + fmt_str, + fmt_span, + ); + } + + // 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. + if invalid_refs.is_empty() && !has_unused && !unnamed_arg_after_named_arg { + for &(index, span, used_as) in &numeric_references_to_named_arg { + let (position_sp_to_replace, position_sp_for_msg) = match used_as { + Placeholder(pspan) => (span, pspan), + Precision => { + // Strip the leading `.` for precision. + let span = span.map(|span| span.with_lo(span.lo() + BytePos(1))); + (span, span) + } + Width => (span, span), + }; + let arg_name = args.explicit_args()[index].kind.ident().unwrap(); + ecx.buffered_early_lint.push(BufferedEarlyLint { + span: arg_name.span.into(), + node_id: rustc_ast::CRATE_NODE_ID, + lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY), + diagnostic: BuiltinLintDiag::NamedArgumentUsedPositionally { + position_sp_to_replace, + position_sp_for_msg, + named_arg_sp: arg_name.span, + named_arg_name: arg_name.name.to_string(), + is_formatting_arg: matches!(used_as, Width | Precision), + }, + }); + } + } + + ExpandResult::Ready(Ok(FormatArgs { span: fmt_span, template, arguments: args })) +} + +fn invalid_placeholder_type_error( + ecx: &ExtCtxt<'_>, + ty: &str, + ty_span: Option<parse::InnerSpan>, + fmt_span: Span, +) { + let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end))); + let suggs = if let Some(sp) = sp { + [ + ("", "Display"), + ("?", "Debug"), + ("e", "LowerExp"), + ("E", "UpperExp"), + ("o", "Octal"), + ("p", "Pointer"), + ("b", "Binary"), + ("x", "LowerHex"), + ("X", "UpperHex"), + ] + .into_iter() + .map(|(fmt, trait_name)| errors::FormatUnknownTraitSugg { span: sp, fmt, trait_name }) + .collect() + } else { + vec![] + }; + ecx.dcx().emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs }); +} + +fn report_missing_placeholders( + ecx: &ExtCtxt<'_>, + unused: Vec<(Span, bool)>, + used: &[bool], + args: &FormatArguments, + pieces: &[parse::Piece<'_>], + detect_foreign_fmt: bool, + str_style: Option<usize>, + fmt_str: &str, + fmt_span: Span, +) { + let mut diag = if let &[(span, named)] = &unused[..] { + ecx.dcx().create_err(errors::FormatUnusedArg { span, named }) + } else { + let unused_labels = + unused.iter().map(|&(span, named)| errors::FormatUnusedArg { span, named }).collect(); + let unused_spans = unused.iter().map(|&(span, _)| span).collect(); + ecx.dcx().create_err(errors::FormatUnusedArgs { + fmt: fmt_span, + unused: unused_spans, + unused_labels, + }) + }; + + let placeholders = pieces + .iter() + .filter_map(|piece| { + if let parse::Piece::NextArgument(argument) = piece + && let ArgumentNamed(binding) = argument.position + { + let span = fmt_span.from_inner(InnerSpan::new( + argument.position_span.start, + argument.position_span.end, + )); + Some((span, binding)) + } else { + None + } + }) + .collect::<Vec<_>>(); + + if !placeholders.is_empty() { + if let Some(new_diag) = report_redundant_format_arguments(ecx, args, used, placeholders) { + diag.cancel(); + new_diag.emit(); + return; + } + } + + // 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 detect_foreign_fmt { + 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); + + if !found_foreign { + found_foreign = true; + show_doc_note = true; + } + + let sp = fmt_span.from_inner(pos); + + if success { + suggestions.push((sp, trn)); + } else { + diag.span_note( + sp, + format!("format specifiers use curly braces, and {}", trn), + ); + } + } + + if show_doc_note { + diag.note(concat!( + stringify!($kind), + " formatting is 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 && unused.len() == 1 { + diag.span_label(fmt_span, "formatting specifier missing"); + } + + diag.emit(); +} + +/// This function detects and reports unused format!() arguments that are +/// redundant due to implicit captures (e.g. `format!("{x}", x)`). +fn report_redundant_format_arguments<'a>( + ecx: &ExtCtxt<'a>, + args: &FormatArguments, + used: &[bool], + placeholders: Vec<(Span, &str)>, +) -> Option<Diag<'a>> { + let mut fmt_arg_indices = vec![]; + let mut args_spans = vec![]; + let mut fmt_spans = vec![]; + + for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() { + let Some(ty) = unnamed_arg.expr.to_ty() else { continue }; + let Some(argument_binding) = ty.kind.is_simple_path() else { continue }; + let argument_binding = argument_binding.as_str(); + + if used[i] { + continue; + } + + let matching_placeholders = placeholders + .iter() + .filter(|(_, inline_binding)| argument_binding == *inline_binding) + .map(|(span, _)| span) + .collect::<Vec<_>>(); + + if !matching_placeholders.is_empty() { + fmt_arg_indices.push(i); + args_spans.push(unnamed_arg.expr.span); + for span in &matching_placeholders { + if fmt_spans.contains(*span) { + continue; + } + fmt_spans.push(**span); + } + } + } + + if !args_spans.is_empty() { + let multispan = MultiSpan::from(fmt_spans); + let mut suggestion_spans = vec![]; + + for (arg_span, fmt_arg_idx) in args_spans.iter().zip(fmt_arg_indices.iter()) { + let span = if fmt_arg_idx + 1 == args.explicit_args().len() { + *arg_span + } else { + arg_span.until(args.explicit_args()[*fmt_arg_idx + 1].expr.span) + }; + + suggestion_spans.push(span); + } + + let sugg = if args.named_args().len() == 0 { + Some(errors::FormatRedundantArgsSugg { spans: suggestion_spans }) + } else { + None + }; + + return Some(ecx.dcx().create_err(errors::FormatRedundantArgs { + n: args_spans.len(), + span: MultiSpan::from(args_spans), + note: multispan, + sugg, + })); + } + + None +} + +/// 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( + ecx: &ExtCtxt<'_>, + invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)], + template: &[FormatArgsPiece], + fmt_span: Span, + args: &FormatArguments, + parser: parse::Parser<'_>, +) { + let num_args_desc = match args.explicit_args().len() { + 0 => "no arguments were given".to_string(), + 1 => "there is 1 argument".to_string(), + n => format!("there are {n} arguments"), + }; + + let mut e; + + if template.iter().all(|piece| match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. }, + .. + }) => false, + FormatArgsPiece::Placeholder(FormatPlaceholder { + format_options: + FormatOptions { + precision: + Some(FormatCount::Argument(FormatArgPosition { + kind: FormatArgPositionKind::Number, + .. + })), + .. + } + | FormatOptions { + width: + Some(FormatCount::Argument(FormatArgPosition { + kind: FormatArgPositionKind::Number, + .. + })), + .. + }, + .. + }) => false, + _ => true, + }) { + // There are no numeric positions. + // Collect all the implicit positions: + let mut spans = Vec::new(); + let mut num_placeholders = 0; + for piece in template { + let mut placeholder = None; + // `{arg:.*}` + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + format_options: + FormatOptions { + precision: + Some(FormatCount::Argument(FormatArgPosition { + span, + kind: FormatArgPositionKind::Implicit, + .. + })), + .. + }, + .. + }) = piece + { + placeholder = *span; + num_placeholders += 1; + } + // `{}` + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. }, + span, + .. + }) = piece + { + placeholder = *span; + num_placeholders += 1; + } + // For `{:.*}`, we only push one span. + spans.extend(placeholder); + } + let span = if spans.is_empty() { + MultiSpan::from_span(fmt_span) + } else { + MultiSpan::from_spans(spans) + }; + e = ecx.dcx().create_err(errors::FormatPositionalMismatch { + span, + n: num_placeholders, + desc: num_args_desc, + highlight: SingleLabelManySpans { + spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(), + label: "", + }, + }); + // Point out `{:.*}` placeholders: those take an extra argument. + let mut has_precision_star = false; + for piece in template { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + format_options: + FormatOptions { + precision: + Some(FormatCount::Argument(FormatArgPosition { + index, + span: Some(span), + kind: FormatArgPositionKind::Implicit, + .. + })), + .. + }, + .. + }) = piece + { + let (Ok(index) | Err(index)) = index; + has_precision_star = true; + e.span_label( + *span, + format!( + "this precision flag adds an extra required argument at position {}, which is why there {} expected", + index, + if num_placeholders == 1 { + "is 1 argument".to_string() + } else { + format!("are {num_placeholders} arguments") + }, + ), + ); + } + } + if has_precision_star { + e.note("positional arguments are zero-based"); + } + } else { + let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect(); + // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` + // for `println!("{7:7$}", 1);` + indexes.sort(); + indexes.dedup(); + let span: MultiSpan = if !parser.is_source_literal || parser.arg_places.is_empty() { + MultiSpan::from_span(fmt_span) + } else { + MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect()) + }; + let arg_list = if let &[index] = &indexes[..] { + format!("argument {index}") + } else { + let tail = indexes.pop().unwrap(); + format!( + "arguments {head} and {tail}", + head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ") + ) + }; + e = ecx.dcx().struct_span_err( + span, + format!("invalid reference to positional {arg_list} ({num_args_desc})"), + ); + e.note("positional arguments are zero-based"); + } + + if template.iter().any(|piece| match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => { + *f != FormatOptions::default() + } + _ => false, + }) { + e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html"); + } + + e.emit(); +} + +fn expand_format_args_impl<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + mut sp: Span, + tts: TokenStream, + nl: bool, +) -> MacroExpanderResult<'cx> { + sp = ecx.with_def_site_ctxt(sp); + ExpandResult::Ready(match parse_args(ecx, sp, tts) { + Ok(input) => { + let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl) else { + return ExpandResult::Retry(()); + }; + match mac { + Ok(format_args) => { + MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args)))) + } + Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))), + } + } + Err(err) => { + let guar = err.emit(); + DummyResult::any(sp, guar) + } + }) +} + +pub(crate) fn expand_format_args<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + expand_format_args_impl(ecx, sp, tts, false) +} + +pub(crate) fn expand_format_args_nl<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + expand_format_args_impl(ecx, sp, tts, true) +} 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 00000000000..bc2c6def68a --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format_foreign.rs @@ -0,0 +1,822 @@ +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(fmt) => fmt.span, + Substitution::Escape(_) => "%%", + } + } + + pub fn position(&self) -> InnerSpan { + match self { + Substitution::Format(fmt) => fmt.position, + &Substitution::Escape((start, end)) => InnerSpan::new(start, end), + } + } + + pub fn set_position(&mut self, start: usize, end: usize) { + match self { + Substitution::Format(fmt) => fmt.position = InnerSpan::new(start, end), + Substitution::Escape(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(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 `{c}` is unknown or unsupported"))); + } + } + } + (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>) -> Option<Self> { + if let Some(arg) = arg { + arg.parse().ok().map(|arg| Num::Arg(arg)) + } else if s == "*" { + Some(Num::Next) + } else { + s.parse().ok().map(|num| Num::Num(num)) + } + } + + 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; + let 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 = at.slice_between(end).and_then(|num| Num::from_str(num, None)); + if width.is_none() { + return fallback(); + } + 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 = at.slice_between(end).and_then(|num| Num::from_str(num, None)); + if width.is_none() { + return fallback(); + } + 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 = Num::from_str("", at.slice_between(end)); + 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 = 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 = at.slice_between(end).and_then(|num| Num::from_str(num, 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 { + type_ = at.slice_between(next).unwrap(); + + // Don't use `move_to!` here, as we *can* be at the end of the input. + at = next; + } + + let _ = c; // to avoid never used value + + 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) -> InnerSpan { + let (Self::Ordinal(_, pos) | Self::Name(_, pos) | Self::Escape(pos)) = self; + InnerSpan::new(pos.0, pos.1) + } + + pub fn set_position(&mut self, start: usize, end: usize) { + let (Self::Ordinal(_, pos) | Self::Name(_, pos) | Self::Escape(pos)) = self; + *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; + let 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 00000000000..df773910dbc --- /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(Option::as_deref).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 00000000000..93a7afcd6e8 --- /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(Option::as_deref).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 00000000000..b44ff979303 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -0,0 +1,174 @@ +use crate::util::check_builtin_macro_attribute; + +use crate::errors; +use rustc_ast::expand::allocator::{ + global_fn_name, AllocatorMethod, AllocatorMethodInput, AllocatorTy, ALLOCATOR_METHODS, +}; +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, AttrVec, Expr, FnHeader, FnSig, Generics, Param, StmtKind}; +use rustc_ast::{Fn, ItemKind, Mutability, Safety, Stmt, Ty, TyKind}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::Span; +use thin_vec::{thin_vec, ThinVec}; + +pub(crate) 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(); + + // 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) = if let Annotatable::Item(item) = &item + && let ItemKind::Static(box ast::StaticItem { ty, .. }) = &item.kind + { + (item, false, ecx.with_def_site_ctxt(ty.span)) + } else if let Annotatable::Stmt(stmt) = &item + && let StmtKind::Item(item) = &stmt.kind + && let ItemKind::Static(box ast::StaticItem { ty, .. }) = &item.kind + { + (item, true, ecx.with_def_site_ctxt(ty.span)) + } else { + ecx.dcx().emit_err(errors::AllocMustStatics { span: item.span() }); + return vec![orig_item]; + }; + + // Generate a bunch of new items using the AllocFnFactory + let span = ecx.with_def_site_ctxt(item.span); + let f = AllocFnFactory { span, ty_span, 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(ThinVec::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, + global: Ident, + cx: &'b ExtCtxt<'a>, +} + +impl AllocFnFactory<'_, '_> { + fn allocator_fn(&self, method: &AllocatorMethod) -> Stmt { + let mut abi_args = ThinVec::new(); + let args = method.inputs.iter().map(|input| self.arg_ty(input, &mut abi_args)).collect(); + let result = self.call_allocator(method.name, args); + let output_ty = self.ret_ty(&method.output); + let decl = self.cx.fn_decl(abi_args, ast::FnRetTy::Ty(output_ty)); + let header = FnHeader { safety: Safety::Unsafe(self.span), ..FnHeader::default() }; + let sig = FnSig { decl, header, span: self.span }; + let body = Some(self.cx.block_expr(result)); + 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(&global_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: ThinVec<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) -> AttrVec { + thin_vec![self.cx.attr_word(sym::rustc_std_internal_symbol, self.span)] + } + + fn arg_ty(&self, input: &AllocatorMethodInput, args: &mut ThinVec<Param>) -> P<Expr> { + match input.ty { + AllocatorTy::Layout => { + // If an allocator method is ever introduced having multiple + // Layout arguments, these argument names need to be + // disambiguated somehow. Currently the generated code would + // fail to compile with "identifier is bound more than once in + // this parameter list". + let size = Ident::from_str_and_span("size", self.span); + let align = Ident::from_str_and_span("align", self.span); + + let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span)); + let ty_usize = self.cx.ty_path(usize); + 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, thin_vec![size, align]); + layout + } + + AllocatorTy::Ptr => { + let ident = Ident::from_str_and_span(input.name, self.span); + args.push(self.cx.param(self.span, ident, self.ptr_u8())); + self.cx.expr_ident(self.span, ident) + } + + AllocatorTy::Usize => { + let ident = Ident::from_str_and_span(input.name, self.span); + 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) -> P<Ty> { + match *ty { + AllocatorTy::ResultPtr => self.ptr_u8(), + + AllocatorTy::Unit => self.cx.ty(self.span, TyKind::Tup(ThinVec::new())), + + 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 00000000000..f8d93666145 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -0,0 +1,135 @@ +//! This crate contains implementations of built-in macros and other code generating facilities +//! injecting code into the crate before it is lowered to HIR. + +// tidy-alphabetical-start +#![allow(internal_features)] +#![allow(rustc::diagnostic_outside_of_impl)] +#![allow(rustc::untranslatable_diagnostic)] +#![cfg_attr(bootstrap, feature(lint_reasons))] +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] +#![doc(rust_logo)] +#![feature(assert_matches)] +#![feature(box_patterns)] +#![feature(decl_macro)] +#![feature(if_let_guard)] +#![feature(let_chains)] +#![feature(proc_macro_internals)] +#![feature(proc_macro_quote)] +#![feature(rustdoc_internals)] +#![feature(try_blocks)] +// tidy-alphabetical-end + +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 alloc_error_handler; +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 errors; +mod format; +mod format_foreign; +mod global_allocator; +mod log_syntax; +mod pattern_type; +mod source_util; +mod test; +mod trace_macros; + +pub mod asm; +pub mod cmdline_attrs; +pub mod proc_macro_harness; +pub mod standard_library_imports; +pub mod test_harness; +pub mod util; + +rustc_fluent_macro::fluent_messages! { "../messages.ftl" } + +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! { + // tidy-alphabetical-start + 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: concat::expand_concat, + concat_bytes: concat_bytes::expand_concat_bytes, + concat_idents: concat_idents::expand_concat_idents, + const_format_args: format::expand_format_args, + core_panic: edition_panic::expand_panic, + env: env::expand_env, + file: source_util::expand_file, + format_args: format::expand_format_args, + format_args_nl: format::expand_format_args_nl, + global_asm: asm::expand_global_asm, + include: source_util::expand_include, + include_bytes: source_util::expand_include_bytes, + include_str: source_util::expand_include_str, + line: source_util::expand_line, + log_syntax: log_syntax::expand_log_syntax, + module_path: source_util::expand_mod, + option_env: env::expand_option_env, + pattern_type: pattern_type::expand, + std_panic: edition_panic::expand_panic, + stringify: source_util::expand_stringify, + trace_macros: trace_macros::expand_trace_macros, + unreachable: edition_panic::expand_unreachable, + // tidy-alphabetical-end + } + + register_attr! { + alloc_error_handler: alloc_error_handler::expand, + bench: test::expand_bench, + cfg_accessible: cfg_accessible::Expander, + cfg_eval: cfg_eval::expand, + derive: derive::Expander { is_const: false }, + derive_const: derive::Expander { is_const: true }, + 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, + ConstParamTy: bounds::expand_deriving_const_param_ty, + 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, + SmartPointer: smart_ptr::expand_deriving_smart_ptr, + } + + 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 00000000000..205f21ae7c9 --- /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::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; + +pub(crate) fn expand_log_syntax<'cx>( + _cx: &'cx mut ExtCtxt<'_>, + sp: rustc_span::Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + println!("{}", pprust::tts_to_string(&tts)); + + // any so that `log_syntax` can be invoked as an expression and item. + ExpandResult::Ready(DummyResult::any_valid(sp)) +} diff --git a/compiler/rustc_builtin_macros/src/pattern_type.rs b/compiler/rustc_builtin_macros/src/pattern_type.rs new file mode 100644 index 00000000000..31f5656df13 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/pattern_type.rs @@ -0,0 +1,29 @@ +use rustc_ast::{ast, ptr::P, tokenstream::TokenStream, Pat, Ty}; +use rustc_errors::PResult; +use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; +use rustc_span::{sym, Span}; + +pub(crate) fn expand<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let (ty, pat) = match parse_pat_ty(cx, tts) { + Ok(parsed) => parsed, + Err(err) => { + return ExpandResult::Ready(DummyResult::any(sp, err.emit())); + } + }; + + ExpandResult::Ready(base::MacEager::ty(cx.ty(sp, ast::TyKind::Pat(ty, pat)))) +} + +fn parse_pat_ty<'a>(cx: &mut ExtCtxt<'a>, stream: TokenStream) -> PResult<'a, (P<Ty>, P<Pat>)> { + let mut parser = cx.new_parser_from_tts(stream); + + let ty = parser.parse_ty()?; + parser.eat_keyword(sym::is); + let pat = parser.parse_pat_no_top_alt(None, None)?; + + Ok((ty, pat)) +} 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 00000000000..99d0191958d --- /dev/null +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -0,0 +1,387 @@ +use crate::errors; +use rustc_ast::ptr::P; +use rustc_ast::visit::{self, Visitor}; +use rustc_ast::{self as ast, attr, NodeId}; +use rustc_ast_pretty::pprust; +use rustc_errors::DiagCtxtHandle; +use rustc_expand::base::{parse_macro_name_and_helper_attrs, ExtCtxt, ResolverExpand}; +use rustc_expand::expand::{AstFragment, ExpansionConfig}; +use rustc_feature::Features; +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; +use std::mem; +use thin_vec::{thin_vec, ThinVec}; + +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> { + macros: Vec<ProcMacro>, + in_root: bool, + dcx: DiagCtxtHandle<'a>, + source_map: &'a SourceMap, + is_proc_macro_crate: bool, + is_test_crate: bool, +} + +pub fn inject( + krate: &mut ast::Crate, + sess: &Session, + features: &Features, + resolver: &mut dyn ResolverExpand, + is_proc_macro_crate: bool, + has_proc_macro_decls: bool, + is_test_crate: bool, + dcx: DiagCtxtHandle<'_>, +) { + let ecfg = ExpansionConfig::default("proc_macro".to_string(), features); + let mut cx = ExtCtxt::new(sess, ecfg, resolver, None); + + let mut collect = CollectProcMacros { + macros: Vec::new(), + in_root: true, + dcx, + 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; + } + + if is_test_crate { + return; + } + + let decls = mk_decls(&mut cx, ¯os); + krate.items.push(decls); +} + +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.dcx.emit_err(errors::ProcMacro { span: sp }); + } + } + + 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.dcx, 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.dcx.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.dcx.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.dcx.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 && attr::contains_name(&item.attrs, sym::macro_export) { + self.dcx.emit_err(errors::ExportMacroRules { + span: self.source_map.guess_head_span(item.span), + }); + } + } + + // 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 attr.is_proc_macro_attr() { + if let Some(prev_attr) = found_attr { + let prev_item = prev_attr.get_normal_item(); + let item = attr.get_normal_item(); + 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 `#[{path_str}]` attribute is allowed on any given function", + ) + } else { + format!( + "`#[{}]` and `#[{}]` attributes cannot both be applied + to the same function", + path_str, + pprust::path_to_string(&prev_item.path), + ) + }; + + self.dcx + .struct_span_err(attr.span, msg) + .with_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.dcx.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.dcx.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] +// #[used] +// #[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, ast::AttrVec::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), + thin_vec![ + cx.expr_str(span, cd.trait_name), + cx.expr_array_ref( + span, + cd.attrs + .iter() + .map(|&s| cx.expr_str(span, s)) + .collect::<ThinVec<_>>(), + ), + 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), + thin_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_ref( + 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| { + i.attrs.push(cx.attr_word(sym::rustc_proc_macro_decls, span)); + i.attrs.push(cx.attr_word(sym::used, span)); + i.attrs.push(cx.attr_nested_word(sym::allow, sym::deprecated, span)); + i + }); + + let block = cx.expr_block( + cx.block(span, thin_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(ThinVec::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 00000000000..dc1d82df0c3 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -0,0 +1,343 @@ +use crate::util::{ + check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts, parse_expr, +}; +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_data_structures::sync::Lrc; +use rustc_expand::base::{ + resolve_path, DummyResult, ExpandResult, ExtCtxt, MacEager, MacResult, MacroExpanderResult, +}; +use rustc_expand::module::DirOwnership; +use rustc_lint_defs::BuiltinLintDiag; +use rustc_parse::parser::{ForceCollect, Parser}; +use rustc_parse::{new_parser_from_file, unwrap_or_emit_fatal}; +use rustc_session::lint::builtin::INCOMPLETE_INCLUDE; +use rustc_span::source_map::SourceMap; +use rustc_span::symbol::Symbol; +use rustc_span::{Pos, Span}; +use smallvec::SmallVec; +use std::path::{Path, PathBuf}; +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(crate) fn expand_line( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + 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()); + + ExpandResult::Ready(MacEager::expr(cx.expr_u32(topmost, loc.line as u32))) +} + +/* column!(): expands to the current column number */ +pub(crate) fn expand_column( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + 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()); + + ExpandResult::Ready(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(crate) fn expand_file( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + 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()); + + use rustc_session::{config::RemapPathScopeComponents, RemapFileNameExt}; + ExpandResult::Ready(MacEager::expr(cx.expr_str( + topmost, + Symbol::intern( + &loc.file.name.for_scope(cx.sess, RemapPathScopeComponents::MACRO).to_string_lossy(), + ), + ))) +} + +pub(crate) fn expand_stringify( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + let s = pprust::tts_to_string(&tts); + ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&s)))) +} + +pub(crate) fn expand_mod( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + 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("::"); + + ExpandResult::Ready(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(crate) fn expand_include<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'cx> { + let sp = cx.with_def_site_ctxt(sp); + let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include!") else { + return ExpandResult::Retry(()); + }; + let file = match mac { + Ok(file) => file, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + // The file will be added to the code map by the parser + let file = match resolve_path(&cx.sess, file.as_str(), sp) { + Ok(f) => f, + Err(err) => { + let guar = err.emit(); + return ExpandResult::Ready(DummyResult::any(sp, guar)); + } + }; + let p = unwrap_or_emit_fatal(new_parser_from_file(cx.psess(), &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 ExpandInclude<'a> { + p: Parser<'a>, + node_id: ast::NodeId, + } + impl<'a> MacResult for ExpandInclude<'a> { + fn make_expr(mut self: Box<ExpandInclude<'a>>) -> Option<P<ast::Expr>> { + let expr = parse_expr(&mut self.p).ok()?; + if self.p.token != token::Eof { + self.p.psess.buffer_lint( + INCOMPLETE_INCLUDE, + self.p.token.span, + self.node_id, + BuiltinLintDiag::IncompleteInclude, + ); + } + Some(expr) + } + + fn make_items(mut self: Box<ExpandInclude<'a>>) -> Option<SmallVec<[P<ast::Item>; 1]>> { + let mut ret = SmallVec::new(); + loop { + match self.p.parse_item(ForceCollect::No) { + Err(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.dcx().span_err(self.p.token.span, msg); + } + + break; + } + } + } + Some(ret) + } + } + + ExpandResult::Ready(Box::new(ExpandInclude { p, node_id: cx.current_expansion.lint_node_id })) +} + +/// `include_str!`: read the given file, insert it as a literal string expr +pub(crate) fn expand_include_str( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_str!") + else { + return ExpandResult::Retry(()); + }; + let (path, path_span) = match mac { + Ok(res) => res, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) { + Ok((bytes, bsp)) => match std::str::from_utf8(&bytes) { + Ok(src) => { + let interned_src = Symbol::intern(src); + MacEager::expr(cx.expr_str(cx.with_def_site_ctxt(bsp), interned_src)) + } + Err(_) => { + let guar = cx.dcx().span_err(sp, format!("`{path}` wasn't a utf-8 file")); + DummyResult::any(sp, guar) + } + }, + Err(dummy) => dummy, + }) +} + +pub(crate) fn expand_include_bytes( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> MacroExpanderResult<'static> { + let sp = cx.with_def_site_ctxt(sp); + let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_bytes!") + else { + return ExpandResult::Retry(()); + }; + let (path, path_span) = match mac { + Ok(res) => res, + Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), + }; + ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) { + Ok((bytes, _bsp)) => { + // Don't care about getting the span for the raw bytes, + // because the console can't really show them anyway. + let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes)); + MacEager::expr(expr) + } + Err(dummy) => dummy, + }) +} + +fn load_binary_file( + cx: &ExtCtxt<'_>, + original_path: &Path, + macro_span: Span, + path_span: Span, +) -> Result<(Lrc<[u8]>, Span), Box<dyn MacResult>> { + let resolved_path = match resolve_path(&cx.sess, original_path, macro_span) { + Ok(path) => path, + Err(err) => { + let guar = err.emit(); + return Err(DummyResult::any(macro_span, guar)); + } + }; + match cx.source_map().load_binary_file(&resolved_path) { + Ok(data) => Ok(data), + Err(io_err) => { + let mut err = cx.dcx().struct_span_err( + macro_span, + format!("couldn't read `{}`: {io_err}", resolved_path.display()), + ); + + if original_path.is_relative() { + let source_map = cx.sess.source_map(); + let new_path = source_map + .span_to_filename(macro_span.source_callsite()) + .into_local_path() + .and_then(|src| find_path_suggestion(source_map, src.parent()?, original_path)) + .and_then(|path| path.into_os_string().into_string().ok()); + + if let Some(new_path) = new_path { + err.span_suggestion( + path_span, + "there is a file with the same name in a different directory", + format!("\"{}\"", new_path.replace('\\', "/").escape_debug()), + rustc_lint_defs::Applicability::MachineApplicable, + ); + } + } + let guar = err.emit(); + Err(DummyResult::any(macro_span, guar)) + } + } +} + +fn find_path_suggestion( + source_map: &SourceMap, + base_dir: &Path, + wanted_path: &Path, +) -> Option<PathBuf> { + // Fix paths that assume they're relative to cargo manifest dir + let mut base_c = base_dir.components(); + let mut wanted_c = wanted_path.components(); + let mut without_base = None; + while let Some(wanted_next) = wanted_c.next() { + if wanted_c.as_path().file_name().is_none() { + break; + } + // base_dir may be absolute + while let Some(base_next) = base_c.next() { + if base_next == wanted_next { + without_base = Some(wanted_c.as_path()); + break; + } + } + } + let root_absolute = without_base.into_iter().map(PathBuf::from); + + let base_dir_components = base_dir.components().count(); + // Avoid going all the way to the root dir + let max_parent_components = if base_dir.is_relative() { + base_dir_components + 1 + } else { + base_dir_components.saturating_sub(1) + }; + + // Try with additional leading ../ + let mut prefix = PathBuf::new(); + let add = std::iter::from_fn(|| { + prefix.push(".."); + Some(prefix.join(wanted_path)) + }) + .take(max_parent_components.min(3)); + + // Try without leading directories + let mut trimmed_path = wanted_path; + let remove = std::iter::from_fn(|| { + let mut components = trimmed_path.components(); + let removed = components.next()?; + trimmed_path = components.as_path(); + let _ = trimmed_path.file_name()?; // ensure there is a file name left + Some([ + Some(trimmed_path.to_path_buf()), + (removed != std::path::Component::ParentDir) + .then(|| Path::new("..").join(trimmed_path)), + ]) + }) + .flatten() + .flatten() + .take(4); + + root_absolute + .chain(add) + .chain(remove) + .find(|new_path| source_map.file_exists(&base_dir.join(&new_path))) +} 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 00000000000..9bcd793c450 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs @@ -0,0 +1,105 @@ +use rustc_ast::{self as ast, attr}; +use rustc_expand::base::{ExtCtxt, ResolverExpand}; +use rustc_expand::expand::ExpansionConfig; +use rustc_feature::Features; +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; +use thin_vec::thin_vec; + +pub fn inject( + krate: &mut ast::Crate, + pre_configured_attrs: &[ast::Attribute], + resolver: &mut dyn ResolverExpand, + sess: &Session, + features: &Features, +) -> usize { + let orig_num_items = krate.items.len(); + let edition = sess.psess.edition; + + // the first name in this list is the crate name of the crate with the prelude + let names: &[Symbol] = if attr::contains_name(pre_configured_attrs, sym::no_core) { + return 0; + } else if attr::contains_name(pre_configured_attrs, sym::no_std) { + if attr::contains_name(pre_configured_attrs, sym::compiler_builtins) { + &[sym::core] + } else { + &[sym::core, sym::compiler_builtins] + } + } 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(), features); + 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_span = if edition >= Edition2018 { span } else { call_site }; + let item = if name == sym::compiler_builtins { + // compiler_builtins is a private implementation detail. We only + // need to insert it into the crate graph for linking and should not + // expose any of its public API. + // + // FIXME(#113634) We should inject this during post-processing like + // we do for the panic runtime, profiler runtime, etc. + cx.item( + span, + Ident::new(kw::Underscore, ident_span), + thin_vec![], + ast::ItemKind::ExternCrate(Some(name)), + ) + } else { + cx.item( + span, + Ident::new(name, ident_span), + thin_vec![cx.attr_word(sym::macro_use, span)], + ast::ItemKind::ExternCrate(None), + ) + }; + krate.items.insert(0, item); + } + + // 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_some(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(), + thin_vec![cx.attr_word(sym::prelude_import, span)], + ast::ItemKind::Use(ast::UseTree { + prefix: cx.path(span, import_path), + kind: ast::UseTreeKind::Glob, + span, + }), + ); + + krate.items.insert(0, use_item); + krate.items.len() - orig_num_items +} diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs new file mode 100644 index 00000000000..c0310a2f4b0 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -0,0 +1,620 @@ +//! 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::errors; +use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, attr, GenericParamKind}; +use rustc_ast_pretty::pprust; +use rustc_errors::{Applicability, Diag, Level}; +use rustc_expand::base::*; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::{ErrorGuaranteed, FileNameDisplayPreference, Span}; +use std::assert_matches::assert_matches; +use std::iter; +use thin_vec::{thin_vec, ThinVec}; +use tracing::debug; + +/// #[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(crate) 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, is_stmt) = match anno_item { + Annotatable::Item(item) => (item, false), + Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => { + if let ast::StmtKind::Item(i) = stmt.into_inner().kind { + (i, true) + } else { + unreachable!() + } + } + _ => { + ecx.dcx().emit_err(errors::TestCaseNonItem { span: anno_item.span() }); + return vec![]; + } + }; + item = item.map(|mut item| { + let test_path_symbol = Symbol::intern(&item_path( + // skip the name of the root module + &ecx.current_expansion.module.mod_path[1..], + &item.ident, + )); + 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.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp)); + item + }); + + let ret = if is_stmt { + Annotatable::Stmt(P(ecx.stmt_item(item.span, item))) + } else { + Annotatable::Item(item) + }; + + vec![ret] +} + +pub(crate) 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(crate) 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(crate) fn expand_test_or_bench( + cx: &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 => { + not_testable_error(cx, attr_sp, None); + return vec![other]; + } + }; + + let ast::ItemKind::Fn(fn_) = &item.kind else { + not_testable_error(cx, attr_sp, Some(&item)); + return if is_stmt { + vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))] + } else { + vec![Annotatable::Item(item)] + }; + }; + + // check_*_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. + let check_result = if is_bench { + check_bench_signature(cx, &item, fn_) + } else { + check_test_signature(cx, &item, fn_) + }; + if check_result.is_err() { + return if is_stmt { + vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))] + } else { + vec![Annotatable::Item(item)] + }; + } + + let sp = cx.with_def_site_ctxt(item.span); + let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span()); + let attr_sp = 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(ret_ty_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); + + // Adds `#[coverage(off)]` to a closure, so it won't be instrumented in + // `-Cinstrument-coverage` builds. + // This requires `#[allow_internal_unstable(coverage_attribute)]` on the + // corresponding macro declaration in `core::macros`. + let coverage_off = |mut expr: P<ast::Expr>| { + assert_matches!(expr.kind, ast::ExprKind::Closure(_)); + expr.attrs.push(cx.attr_nested_word(sym::coverage, sym::off, 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")), + thin_vec![ + // #[coverage(off)] + // |b| self::test::assert_test_result( + coverage_off(cx.lambda1( + sp, + cx.expr_call( + sp, + cx.expr_path(test_path("assert_test_result")), + thin_vec![ + // super::$test_fn(b) + cx.expr_call( + ret_ty_sp, + cx.expr_path(cx.path(sp, vec![item.ident])), + thin_vec![cx.expr_ident(sp, b)], + ), + ], + ), + b, + )), // ) + ], + ) + } else { + cx.expr_call( + sp, + cx.expr_path(test_path("StaticTestFn")), + thin_vec![ + // #[coverage(off)] + // || { + coverage_off(cx.lambda0( + sp, + // test::assert_test_result( + cx.expr_call( + sp, + cx.expr_path(test_path("assert_test_result")), + thin_vec![ + // $test_fn() + cx.expr_call( + ret_ty_sp, + cx.expr_path(cx.path(sp, vec![item.ident])), + ThinVec::new(), + ), // ) + ], + ), // } + )), // ) + ], + ) + }; + + let test_path_symbol = Symbol::intern(&item_path( + // skip the name of the root module + &cx.current_expansion.module.mod_path[1..], + &item.ident, + )); + + let location_info = get_location_info(cx, &item); + + let mut test_const = + cx.item( + sp, + Ident::new(item.ident.name, sp), + thin_vec![ + // #[cfg(test)] + cx.attr_nested_word(sym::cfg, sym::test, attr_sp), + // #[rustc_test_marker = "test_case_sort_key"] + cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp), + ], + // const $ident: test::TestDescAndFn = + ast::ItemKind::Const( + ast::ConstItem { + defaultness: ast::Defaultness::Final, + generics: ast::Generics::default(), + ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), + // test::TestDescAndFn { + expr: Some( + cx.expr_struct( + sp, + test_path("TestDescAndFn"), + thin_vec![ + // desc: test::TestDesc { + field( + "desc", + cx.expr_struct( + sp, + test_path("TestDesc"), + thin_vec![ + // name: "path::to::test" + field( + "name", + cx.expr_call( + sp, + cx.expr_path(test_path("StaticTestName")), + thin_vec![cx.expr_str(sp, test_path_symbol)], + ), + ), + // ignore: true | false + field("ignore", cx.expr_bool(sp, should_ignore(&item)),), + // ignore_message: Some("...") | None + field( + "ignore_message", + if let Some(msg) = should_ignore_message(&item) { + cx.expr_some(sp, cx.expr_str(sp, msg)) + } else { + cx.expr_none(sp) + }, + ), + // source_file: <relative_path_of_source_file> + field("source_file", cx.expr_str(sp, location_info.0)), + // start_line: start line of the test fn identifier. + field("start_line", cx.expr_usize(sp, location_info.1)), + // start_col: start column of the test fn identifier. + field("start_col", cx.expr_usize(sp, location_info.2)), + // end_line: end line of the test fn identifier. + field("end_line", cx.expr_usize(sp, location_info.3)), + // end_col: end column of the test fn identifier. + field("end_col", cx.expr_usize(sp, location_info.4)), + // compile_fail: true | false + field("compile_fail", cx.expr_bool(sp, false)), + // no_run: true | false + 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")), + thin_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), // } + ], + ), // } + ), + } + .into(), + ), + ); + 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, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)); + + 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 not_testable_error(cx: &ExtCtxt<'_>, attr_sp: Span, item: Option<&ast::Item>) { + let dcx = cx.dcx(); + let msg = "the `#[test]` attribute may only be used on a non-associated function"; + let level = match item.map(|i| &i.kind) { + // These were a warning before #92959 and need to continue being that to avoid breaking + // stable user code (#94508). + Some(ast::ItemKind::MacCall(_)) => Level::Warning, + _ => Level::Error, + }; + let mut err = Diag::<()>::new(dcx, level, msg); + err.span(attr_sp); + if let Some(item) = item { + err.span_label( + item.span, + format!( + "expected a non-associated function, found {} {}", + item.kind.article(), + item.kind.descr() + ), + ); + } + err.with_span_label(attr_sp, "the `#[test]` macro causes a function to be run as a test and has no effect on non-functions") + .with_span_suggestion(attr_sp, + "replace with conditional compilation to make the item only exist when tests are being run", + "#[cfg(test)]", + Applicability::MaybeIncorrect) + .emit(); +} + +fn get_location_info(cx: &ExtCtxt<'_>, item: &ast::Item) -> (Symbol, usize, usize, usize, usize) { + let span = item.ident.span; + let (source_file, lo_line, lo_col, hi_line, hi_col) = + cx.sess.source_map().span_to_location_info(span); + + let file_name = match source_file { + Some(sf) => sf.name.display(FileNameDisplayPreference::Remapped).to_string(), + None => "no-location".to_string(), + }; + + (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col) +} + +fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String { + mod_path + .iter() + .chain(iter::once(item_ident)) + .map(|x| x.to_string()) + .collect::<Vec<String>>() + .join("::") +} + +enum ShouldPanic { + No, + Yes(Option<Symbol>), +} + +fn should_ignore(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, sym::ignore) +} + +fn should_ignore_message(i: &ast::Item) -> Option<Symbol> { + match attr::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 attr::find_by_name(&i.attrs, sym::should_panic) { + Some(attr) => { + 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() { + cx.dcx() + .struct_span_warn( + attr.span, + "argument must be of the form: \ + `expected = \"error message\"`", + ) + .with_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 check_test_signature( + cx: &ExtCtxt<'_>, + i: &ast::Item, + f: &ast::Fn, +) -> Result<(), ErrorGuaranteed> { + let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic); + let dcx = cx.dcx(); + + if let ast::Safety::Unsafe(span) = f.sig.header.safety { + return Err(dcx.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "unsafe" })); + } + + if let Some(coroutine_kind) = f.sig.header.coroutine_kind { + match coroutine_kind { + ast::CoroutineKind::Async { span, .. } => { + return Err(dcx.emit_err(errors::TestBadFn { + span: i.span, + cause: span, + kind: "async", + })); + } + ast::CoroutineKind::Gen { span, .. } => { + return Err(dcx.emit_err(errors::TestBadFn { + span: i.span, + cause: span, + kind: "gen", + })); + } + ast::CoroutineKind::AsyncGen { span, .. } => { + return Err(dcx.emit_err(errors::TestBadFn { + span: i.span, + cause: span, + kind: "async gen", + })); + } + } + } + + // 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 &f.sig.decl.output { + ast::FnRetTy::Default(..) => false, + ast::FnRetTy::Ty(t) if t.kind.is_unit() => false, + _ => true, + }; + + if !f.sig.decl.inputs.is_empty() { + return Err(dcx.span_err(i.span, "functions used as tests can not have any arguments")); + } + + if has_should_panic_attr && has_output { + return Err(dcx.span_err(i.span, "functions using `#[should_panic]` must return `()`")); + } + + if f.generics.params.iter().any(|param| !matches!(param.kind, GenericParamKind::Lifetime)) { + return Err(dcx.span_err( + i.span, + "functions used as tests can not have any non-lifetime generic parameters", + )); + } + + Ok(()) +} + +fn check_bench_signature( + cx: &ExtCtxt<'_>, + i: &ast::Item, + f: &ast::Fn, +) -> Result<(), ErrorGuaranteed> { + // N.B., inadequate check, but we're running + // well before resolve, can't get too deep. + if f.sig.decl.inputs.len() != 1 { + return Err(cx.dcx().emit_err(errors::BenchSig { span: i.span })); + } + Ok(()) +} 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 00000000000..9d032eb190a --- /dev/null +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -0,0 +1,411 @@ +// 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::*; +use rustc_ast::ptr::P; +use rustc_ast::visit::{walk_item, Visitor}; +use rustc_ast::{attr, ModKind}; +use rustc_errors::DiagCtxtHandle; +use rustc_expand::base::{ExtCtxt, ResolverExpand}; +use rustc_expand::expand::{AstFragment, ExpansionConfig}; +use rustc_feature::Features; +use rustc_lint_defs::BuiltinLintDiag; +use rustc_session::lint::builtin::UNNAMEABLE_TEST_ITEMS; +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 thin_vec::{thin_vec, ThinVec}; +use tracing::debug; + +use std::{iter, mem}; + +use crate::errors; + +#[derive(Clone)] +struct Test { + span: Span, + ident: Ident, + name: Symbol, +} + +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( + krate: &mut ast::Crate, + sess: &Session, + features: &Features, + resolver: &mut dyn ResolverExpand, +) { + let dcx = sess.dcx(); + 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 = + attr::first_attr_value_str_by_name(&krate.attrs, sym::reexport_test_harness_main); + + // Do this here so that the test_runner crate attribute gets marked as used + // even in non-test builds + let test_runner = get_test_runner(dcx, krate); + + if sess.is_test_crate() { + let panic_strategy = match (panic_strategy, sess.opts.unstable_opts.panic_abort_tests) { + (PanicStrategy::Abort, true) => PanicStrategy::Abort, + (PanicStrategy::Abort, false) => { + 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 { + dcx.emit_err(errors::TestsNotSupport {}); + } + PanicStrategy::Unwind + } + (PanicStrategy::Unwind, _) => PanicStrategy::Unwind, + }; + generate_test_harness( + sess, + resolver, + reexport_test_harness_main, + krate, + features, + 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 let Some(name) = get_test_name(&item) { + debug!("this is a test item"); + + let test = Test { span: item.span, ident: item.ident, name }; + 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(.., ast::ModSpans { inner_span: span, .. })) = + item.kind + { + 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); + } else { + // But in those cases, we emit a lint to warn the user of these missing tests. + walk_item(&mut InnerItemLinter { sess: self.cx.ext_cx.sess }, &item); + } + smallvec![P(item)] + } +} + +struct InnerItemLinter<'a> { + sess: &'a Session, +} + +impl<'a> Visitor<'a> for InnerItemLinter<'_> { + fn visit_item(&mut self, i: &'a ast::Item) { + if let Some(attr) = attr::find_by_name(&i.attrs, sym::rustc_test_marker) { + self.sess.psess.buffer_lint( + UNNAMEABLE_TEST_ITEMS, + attr.span, + i.id, + BuiltinLintDiag::UnnameableTestItems, + ); + } + } +} + +fn entry_point_type(item: &ast::Item, at_root: bool) -> EntryPointType { + match item.kind { + ast::ItemKind::Fn(..) => { + rustc_ast::entry::entry_point_type(&item.attrs, at_root, Some(item.ident.name)) + } + _ => EntryPointType::None, + } +} + +/// A folder used to remove any entry points (like fn main) because the harness +/// coroutine 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(&item, self.depth == 0) { + EntryPointType::MainNamed | EntryPointType::RustcMainAttr | EntryPointType::Start => { + item.map(|ast::Item { id, ident, attrs, kind, vis, span, tokens }| { + let allow_dead_code = attr::mk_attr_nested_word( + &self.sess.psess.attr_id_generator, + ast::AttrStyle::Outer, + ast::Safety::Default, + sym::allow, + sym::dead_code, + self.def_site, + ); + 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 econfig = ExpansionConfig::default("test".to_string(), 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, sym::coverage_attribute], + 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 (messes with test internals) +/// #[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, thin_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, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)), + ); + + // #[rustc_main] + let main_attr = ecx.attr_word(sym::rustc_main, sp); + // #[coverage(off)] + let coverage_attr = ecx.attr_nested_word(sym::coverage, sym::off, sp); + + // pub fn main() { ... } + let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(ThinVec::new())); + + // 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, thin_vec![test_extern_stmt, call_test_main]) + } else { + ecx.block(sp, thin_vec![call_test_main]) + }; + + let decl = ecx.fn_decl(ThinVec::new(), 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: thin_vec![main_attr, coverage_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; + + let mut tests = cx.test_cases.clone(); + tests.sort_by(|a, b| a.name.as_str().cmp(b.name.as_str())); + + ecx.expr_array_ref( + sp, + tests + .iter() + .map(|test| { + ecx.expr_addr_of(test.span, ecx.expr_path(ecx.path(test.span, vec![test.ident]))) + }) + .collect(), + ) +} + +fn get_test_name(i: &ast::Item) -> Option<Symbol> { + attr::first_attr_value_str_by_name(&i.attrs, sym::rustc_test_marker) +} + +fn get_test_runner(dcx: DiagCtxtHandle<'_>, krate: &ast::Crate) -> Option<ast::Path> { + let test_attr = attr::find_by_name(&krate.attrs, sym::test_runner)?; + let meta_list = test_attr.meta_item_list()?; + let span = test_attr.span; + match &*meta_list { + [single] => match single.meta_item() { + Some(meta_item) if meta_item.is_word() => return Some(meta_item.path.clone()), + _ => { + dcx.emit_err(errors::TestRunnerInvalid { span }); + } + }, + _ => { + dcx.emit_err(errors::TestRunnerNargs { span }); + } + } + 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 00000000000..4833ec32f76 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/trace_macros.rs @@ -0,0 +1,30 @@ +use crate::errors; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; +use rustc_span::symbol::kw; +use rustc_span::Span; + +pub(crate) fn expand_trace_macros( + cx: &mut ExtCtxt<'_>, + sp: Span, + tt: TokenStream, +) -> MacroExpanderResult<'static> { + let mut cursor = tt.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.dcx().emit_err(errors::TraceMacros { span: sp }); + } else { + cx.set_trace_macros(value); + } + + ExpandResult::Ready(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 00000000000..652e34268ea --- /dev/null +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -0,0 +1,228 @@ +use crate::errors; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{self as ast, attr, ptr::P, token, AttrStyle, Attribute, MetaItem}; +use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt}; +use rustc_expand::expand::AstFragment; +use rustc_feature::AttributeTemplate; +use rustc_lint_defs::{builtin::DUPLICATE_MACRO_ATTRIBUTES, BuiltinLintDiag}; +use rustc_parse::{parser, validate_attr}; +use rustc_session::errors::report_lit_error; +use rustc_span::{BytePos, Span, Symbol}; + +pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) { + // All the built-in macro attributes are "words" at the moment. + let template = AttributeTemplate { word: true, ..Default::default() }; + validate_attr::check_builtin_meta_item( + &ecx.sess.psess, + meta_item, + AttrStyle::Outer, + name, + template, + ); +} + +/// Emit a warning if the item is annotated with the given attribute. This is used to diagnose when +/// an attribute may have been mistakenly duplicated. +pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) { + let attrs: Option<&[Attribute]> = match item { + Annotatable::Item(item) => Some(&item.attrs), + Annotatable::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) = attr::find_by_name(attrs, name) { + ecx.psess().buffer_lint( + DUPLICATE_MACRO_ATTRIBUTES, + attr.span, + ecx.current_expansion.lint_node_id, + BuiltinLintDiag::DuplicateMacroAttribute, + ); + } + } +} + +/// `Ok` represents successfully retrieving the string literal at the correct +/// position, e.g., `println("abc")`. +type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>; + +/// - `Ok` is returned when the conversion to a string literal is unsuccessful, +/// but another type of expression is obtained instead. +/// - `Err` is returned when the conversion process fails. +type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>; + +/// Extracts a string literal from the macro expanded version of `expr`, +/// returning a diagnostic error of `err_msg` if `expr` is not a string literal. +/// The returned bool indicates whether an applicable suggestion has already been +/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))` +/// indicates that an ast error was encountered. +// FIXME(Nilstrieb) Make this function setup translatable +#[allow(rustc::untranslatable_diagnostic)] +pub(crate) fn expr_to_spanned_string<'a>( + cx: &'a mut ExtCtxt<'_>, + expr: P<ast::Expr>, + err_msg: &'static str, +) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> { + if !cx.force_mode + && let ast::ExprKind::MacCall(m) = &expr.kind + && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() + { + return ExpandResult::Retry(()); + } + + // Perform eager expansion on the expression. + // We want to be able to handle e.g., `concat!("foo", "bar")`. + let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); + + ExpandResult::Ready(Err(match expr.kind { + ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) { + Ok(ast::LitKind::Str(s, style)) => { + return ExpandResult::Ready(Ok((s, style, expr.span))); + } + Ok(ast::LitKind::ByteStr(..)) => { + let mut err = cx.dcx().struct_span_err(expr.span, err_msg); + let span = expr.span.shrink_to_lo(); + err.span_suggestion( + span.with_hi(span.lo() + BytePos(1)), + "consider removing the leading `b`", + "", + Applicability::MaybeIncorrect, + ); + Ok((err, true)) + } + Ok(ast::LitKind::Err(guar)) => Err(guar), + Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)), + _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), + }, + ast::ExprKind::Err(guar) => Err(guar), + ast::ExprKind::Dummy => { + cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`") + } + _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), + })) +} + +/// Extracts a string literal from the macro expanded version of `expr`, +/// emitting `err_msg` if `expr` is not a string literal. This does not stop +/// compilation on error, merely emits a non-fatal error and returns `Err`. +pub(crate) fn expr_to_string( + cx: &mut ExtCtxt<'_>, + expr: P<ast::Expr>, + err_msg: &'static str, +) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> { + expr_to_spanned_string(cx, expr, err_msg).map(|res| { + res.map_err(|err| match err { + Ok((err, _)) => err.emit(), + Err(guar) => guar, + }) + .map(|(symbol, style, _)| (symbol, style)) + }) +} + +/// Non-fatally assert that `tts` is empty. Note that this function +/// returns even when `tts` is non-empty, macros that *need* to stop +/// compilation should call `cx.diagnostic().abort_if_errors()` +/// (this should be done as rarely as possible). +pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) { + if !tts.is_empty() { + cx.dcx().emit_err(errors::TakesNoArguments { span, name }); + } +} + +/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`. +pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> { + let guar = match p.parse_expr() { + Ok(expr) => return Ok(expr), + Err(err) => err.emit(), + }; + while p.token != token::Eof { + p.bump(); + } + Err(guar) +} + +/// Interpreting `tts` as a comma-separated sequence of expressions, +/// expect exactly one string literal, or emit an error and return `Err`. +pub(crate) fn get_single_str_from_tts( + cx: &mut ExtCtxt<'_>, + span: Span, + tts: TokenStream, + name: &str, +) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> { + get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s)) +} + +pub(crate) fn get_single_str_spanned_from_tts( + cx: &mut ExtCtxt<'_>, + span: Span, + tts: TokenStream, + name: &str, +) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> { + let mut p = cx.new_parser_from_tts(tts); + if p.token == token::Eof { + let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); + return ExpandResult::Ready(Err(guar)); + } + let ret = match parse_expr(&mut p) { + Ok(ret) => ret, + Err(guar) => return ExpandResult::Ready(Err(guar)), + }; + let _ = p.eat(&token::Comma); + + if p.token != token::Eof { + cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); + } + expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| { + res.map_err(|err| match err { + Ok((err, _)) => err.emit(), + Err(guar) => guar, + }) + .map(|(symbol, _style, span)| (symbol, span)) + }) +} + +/// Extracts comma-separated expressions from `tts`. +/// On error, emit it, and return `Err`. +pub(crate) fn get_exprs_from_tts( + cx: &mut ExtCtxt<'_>, + tts: TokenStream, +) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> { + let mut p = cx.new_parser_from_tts(tts); + let mut es = Vec::new(); + while p.token != token::Eof { + let expr = match parse_expr(&mut p) { + Ok(expr) => expr, + Err(guar) => return ExpandResult::Ready(Err(guar)), + }; + if !cx.force_mode + && let ast::ExprKind::MacCall(m) = &expr.kind + && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() + { + return ExpandResult::Retry(()); + } + + // Perform eager expansion on the expression. + // We want to be able to handle e.g., `concat!("foo", "bar")`. + let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); + + es.push(expr); + if p.eat(&token::Comma) { + continue; + } + if p.token != token::Eof { + let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span }); + return ExpandResult::Ready(Err(guar)); + } + } + ExpandResult::Ready(Ok(es)) +} |
