diff options
| author | mark <markm@cs.wisc.edu> | 2020-08-27 22:58:48 -0500 |
|---|---|---|
| committer | Vadim Petrochenkov <vadim.petrochenkov@gmail.com> | 2020-08-30 18:45:07 +0300 |
| commit | 9e5f7d5631b8f4009ac1c693e585d4b7108d4275 (patch) | |
| tree | 158a05eb3f204a8e72939b58427d0c2787a4eade /compiler/rustc_builtin_macros/src | |
| parent | db534b3ac286cf45688c3bbae6aa6e77439e52d2 (diff) | |
| download | rust-9e5f7d5631b8f4009ac1c693e585d4b7108d4275.tar.gz rust-9e5f7d5631b8f4009ac1c693e585d4b7108d4275.zip | |
mv compiler to compiler/
Diffstat (limited to 'compiler/rustc_builtin_macros/src')
39 files changed, 9614 insertions, 0 deletions
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs new file mode 100644 index 00000000000..5dafd6b77ab --- /dev/null +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -0,0 +1,632 @@ +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_expand::base::{self, *}; +use rustc_parse::parser::Parser; +use rustc_parse_format as parse; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::{InnerSpan, Span}; + +struct AsmArgs { + templates: Vec<P<ast::Expr>>, + operands: Vec<(ast::InlineAsmOperand, Span)>, + named_args: FxHashMap<Symbol, usize>, + reg_args: FxHashSet<usize>, + options: ast::InlineAsmOptions, + options_spans: Vec<Span>, +} + +fn parse_args<'a>( + ecx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> Result<AsmArgs, DiagnosticBuilder<'a>> { + let mut p = ecx.new_parser_from_tts(tts); + + if p.token == token::Eof { + return Err(ecx.struct_span_err(sp, "requires at least a template string argument")); + } + + // Detect use of the legacy llvm_asm! syntax (which used to be called asm!) + if p.look_ahead(1, |t| *t == token::Colon || *t == token::ModSep) { + let mut err = + ecx.struct_span_err(sp, "the legacy LLVM-style asm! syntax is no longer supported"); + err.note("consider migrating to the new asm! syntax specified in RFC 2873"); + err.note("alternatively, switch to llvm_asm! to keep your code working as it is"); + + // Find the span of the "asm!" so that we can offer an automatic suggestion + let asm_span = sp.from_inner(InnerSpan::new(0, 4)); + if let Ok(s) = ecx.source_map().span_to_snippet(asm_span) { + if s == "asm!" { + err.span_suggestion( + asm_span, + "replace with", + "llvm_asm!".into(), + Applicability::MachineApplicable, + ); + } + } + return Err(err); + } + + let first_template = p.parse_expr()?; + let mut args = AsmArgs { + templates: vec![first_template], + operands: vec![], + named_args: FxHashMap::default(), + reg_args: FxHashSet::default(), + options: ast::InlineAsmOptions::empty(), + options_spans: vec![], + }; + + let mut allow_templates = true; + while p.token != token::Eof { + if !p.eat(&token::Comma) { + if allow_templates { + // After a template string, we always expect *only* a comma... + let mut err = ecx.struct_span_err(p.token.span, "expected token: `,`"); + err.span_label(p.token.span, "expected `,`"); + p.maybe_annotate_with_ascription(&mut err, false); + return Err(err); + } else { + // ...after that delegate to `expect` to also include the other expected tokens. + return Err(p.expect(&token::Comma).err().unwrap()); + } + } + if p.token == token::Eof { + break; + } // accept trailing commas + + // Parse options + if p.eat(&token::Ident(sym::options, false)) { + parse_options(&mut p, &mut args)?; + 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 p.eat(&token::Ident(kw::In, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = p.parse_expr()?; + ast::InlineAsmOperand::In { reg, expr } + } else if p.eat(&token::Ident(sym::out, false)) { + let reg = parse_reg(&mut 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 p.eat(&token::Ident(sym::lateout, false)) { + let reg = parse_reg(&mut 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 p.eat(&token::Ident(sym::inout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + 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 p.eat(&token::Ident(sym::inlateout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + 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(&token::Ident(kw::Const, false)) { + let expr = p.parse_expr()?; + ast::InlineAsmOperand::Const { expr } + } else if p.eat(&token::Ident(sym::sym, false)) { + let expr = p.parse_expr()?; + match expr.kind { + ast::ExprKind::Path(..) => {} + _ => { + let err = ecx + .struct_span_err(expr.span, "argument to `sym` must be a path expression"); + return Err(err); + } + } + ast::InlineAsmOperand::Sym { expr } + } else if allow_templates { + let template = p.parse_expr()?; + // If it can't possibly expand to a string, provide diagnostics here to include other + // things it could have been. + match template.kind { + ast::ExprKind::Lit(ast::Lit { kind: ast::LitKind::Str(..), .. }) => {} + ast::ExprKind::MacCall(..) => {} + _ => { + let errstr = "expected operand, options, or additional template string"; + let mut err = ecx.struct_span_err(template.span, errstr); + err.span_label(template.span, errstr); + return Err(err); + } + } + args.templates.push(template); + continue; + } else { + return Err(p.expect_one_of(&[], &[]).unwrap_err()); + }; + + 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 options. We do + // this at the end once we have the full span of the argument available. + if !args.options_spans.is_empty() { + ecx.struct_span_err(span, "arguments are not allowed after options") + .span_labels(args.options_spans.clone(), "previous options") + .span_label(span, "argument") + .emit(); + } + if explicit_reg { + if name.is_some() { + ecx.struct_span_err(span, "explicit register arguments cannot have names").emit(); + } + args.reg_args.insert(slot); + } else if let Some(name) = name { + if let Some(&prev) = args.named_args.get(&name) { + ecx.struct_span_err(span, &format!("duplicate argument named `{}`", name)) + .span_label(args.operands[prev].1, "previously here") + .span_label(span, "duplicate argument") + .emit(); + continue; + } + if !args.reg_args.is_empty() { + let mut err = ecx.struct_span_err( + span, + "named arguments cannot follow explicit register arguments", + ); + err.span_label(span, "named argument"); + for pos in &args.reg_args { + err.span_label(args.operands[*pos].1, "explicit register argument"); + } + err.emit(); + } + args.named_args.insert(name, slot); + } else { + if !args.named_args.is_empty() || !args.reg_args.is_empty() { + let mut err = ecx.struct_span_err( + span, + "positional arguments cannot follow named arguments \ + or explicit register arguments", + ); + err.span_label(span, "positional argument"); + for pos in args.named_args.values() { + err.span_label(args.operands[*pos].1, "named argument"); + } + for pos in &args.reg_args { + err.span_label(args.operands[*pos].1, "explicit register argument"); + } + err.emit(); + } + } + } + + if args.options.contains(ast::InlineAsmOptions::NOMEM) + && args.options.contains(ast::InlineAsmOptions::READONLY) + { + let spans = args.options_spans.clone(); + ecx.struct_span_err(spans, "the `nomem` and `readonly` options are mutually exclusive") + .emit(); + } + if args.options.contains(ast::InlineAsmOptions::PURE) + && args.options.contains(ast::InlineAsmOptions::NORETURN) + { + let spans = args.options_spans.clone(); + ecx.struct_span_err(spans, "the `pure` and `noreturn` options are mutually exclusive") + .emit(); + } + if args.options.contains(ast::InlineAsmOptions::PURE) + && !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY) + { + let spans = args.options_spans.clone(); + ecx.struct_span_err( + spans, + "the `pure` option must be combined with either `nomem` or `readonly`", + ) + .emit(); + } + + let mut have_real_output = false; + let mut outputs_sp = vec![]; + for (op, op_sp) in &args.operands { + match op { + ast::InlineAsmOperand::Out { expr, .. } + | ast::InlineAsmOperand::SplitInOut { out_expr: expr, .. } => { + outputs_sp.push(*op_sp); + have_real_output |= expr.is_some(); + } + ast::InlineAsmOperand::InOut { .. } => { + outputs_sp.push(*op_sp); + have_real_output = true; + } + _ => {} + } + } + if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output { + ecx.struct_span_err( + args.options_spans.clone(), + "asm with `pure` option must have at least one output", + ) + .emit(); + } + if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() { + let err = ecx + .struct_span_err(outputs_sp, "asm outputs are not allowed with the `noreturn` option"); + + // Bail out now since this is likely to confuse MIR + return Err(err); + } + + Ok(args) +} + +/// Report a duplicate option error. +/// +/// This function must be called immediately after the option token is parsed. +/// Otherwise, the suggestion will be incorrect. +fn err_duplicate_option<'a>(p: &mut Parser<'a>, symbol: Symbol, span: Span) { + let mut err = p + .sess + .span_diagnostic + .struct_span_err(span, &format!("the `{}` option was already provided", symbol)); + err.span_label(span, "this option was already provided"); + + // Tool-only output + let mut full_span = span; + if p.token.kind == token::Comma { + full_span = full_span.to(p.token.span); + } + err.tool_only_span_suggestion( + full_span, + "remove this option", + String::new(), + Applicability::MachineApplicable, + ); + + err.emit(); +} + +/// Try to set the provided option in the provided `AsmArgs`. +/// If it is already set, report a duplicate option error. +/// +/// This function must be called immediately after the option token is parsed. +/// Otherwise, the error will not point to the correct spot. +fn try_set_option<'a>( + p: &mut Parser<'a>, + args: &mut AsmArgs, + symbol: Symbol, + option: ast::InlineAsmOptions, +) { + if !args.options.contains(option) { + args.options |= option; + } else { + err_duplicate_option(p, symbol, p.prev_token.span); + } +} + +fn parse_options<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> Result<(), DiagnosticBuilder<'a>> { + let span_start = p.prev_token.span; + + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + + while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + if p.eat(&token::Ident(sym::pure, false)) { + try_set_option(p, args, sym::pure, ast::InlineAsmOptions::PURE); + } else if p.eat(&token::Ident(sym::nomem, false)) { + try_set_option(p, args, sym::nomem, ast::InlineAsmOptions::NOMEM); + } else if p.eat(&token::Ident(sym::readonly, false)) { + try_set_option(p, args, sym::readonly, ast::InlineAsmOptions::READONLY); + } else if p.eat(&token::Ident(sym::preserves_flags, false)) { + try_set_option(p, args, sym::preserves_flags, ast::InlineAsmOptions::PRESERVES_FLAGS); + } else if p.eat(&token::Ident(sym::noreturn, false)) { + try_set_option(p, args, sym::noreturn, ast::InlineAsmOptions::NORETURN); + } else if p.eat(&token::Ident(sym::nostack, false)) { + try_set_option(p, args, sym::nostack, ast::InlineAsmOptions::NOSTACK); + } else { + p.expect(&token::Ident(sym::att_syntax, false))?; + try_set_option(p, args, sym::att_syntax, ast::InlineAsmOptions::ATT_SYNTAX); + } + + // Allow trailing commas + if p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + break; + } + p.expect(&token::Comma)?; + } + + let new_span = span_start.to(p.prev_token.span); + args.options_spans.push(new_span); + + Ok(()) +} + +fn parse_reg<'a>( + p: &mut Parser<'a>, + explicit_reg: &mut bool, +) -> Result<ast::InlineAsmRegOrRegClass, DiagnosticBuilder<'a>> { + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + let result = match p.token.kind { + token::Ident(name, false) => ast::InlineAsmRegOrRegClass::RegClass(name), + token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => { + *explicit_reg = true; + ast::InlineAsmRegOrRegClass::Reg(symbol) + } + _ => { + return Err( + p.struct_span_err(p.token.span, "expected register class or explicit register") + ); + } + }; + p.bump(); + p.expect(&token::CloseDelim(token::DelimToken::Paren))?; + Ok(result) +} + +fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P<ast::Expr> { + let mut template = vec![]; + // Register operands are implicitly used since they are not allowed to be + // referenced in the template string. + let mut used = vec![false; args.operands.len()]; + for pos in &args.reg_args { + used[*pos] = true; + } + let named_pos: FxHashMap<usize, Symbol> = + args.named_args.iter().map(|(&sym, &idx)| (idx, sym)).collect(); + let mut line_spans = Vec::with_capacity(args.templates.len()); + let mut curarg = 0; + + for template_expr in args.templates.into_iter() { + if !template.is_empty() { + template.push(ast::InlineAsmTemplatePiece::String("\n".to_string())); + } + + let msg = "asm template must be a string literal"; + let template_sp = template_expr.span; + let (template_str, template_style, template_span) = + match expr_to_spanned_string(ecx, template_expr, msg) { + Ok(template_part) => template_part, + Err(err) => { + if let Some(mut err) = err { + err.emit(); + } + return DummyResult::raw_expr(sp, true); + } + }; + + let str_style = match template_style { + ast::StrStyle::Cooked => None, + ast::StrStyle::Raw(raw) => Some(raw as usize), + }; + + let template_str = &template_str.as_str(); + let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok(); + 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(err.span); + let msg = &format!("invalid asm template string: {}", err.description); + let mut e = ecx.struct_span_err(err_sp, msg); + e.span_label(err_sp, err.label + " in asm template string"); + if let Some(note) = err.note { + e.note(¬e); + } + if let Some((label, span)) = err.secondary_label { + let err_sp = template_span.from_inner(span); + e.span_label(err_sp, label); + } + e.emit(); + return DummyResult::raw_expr(sp, true); + } + + curarg = parser.curarg; + + let mut arg_spans = parser.arg_places.iter().map(|span| template_span.from_inner(*span)); + for piece in unverified_pieces { + match piece { + parse::Piece::String(s) => { + template.push(ast::InlineAsmTemplatePiece::String(s.to_string())) + } + parse::Piece::NextArgument(arg) => { + let span = arg_spans.next().unwrap_or(template_sp); + + let operand_idx = match arg.position { + parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => { + if idx >= args.operands.len() + || named_pos.contains_key(&idx) + || args.reg_args.contains(&idx) + { + let msg = format!("invalid reference to argument at index {}", idx); + let mut err = ecx.struct_span_err(span, &msg); + err.span_label(span, "from here"); + + let positional_args = args.operands.len() + - args.named_args.len() + - args.reg_args.len(); + let positional = if positional_args != args.operands.len() { + "positional " + } else { + "" + }; + let msg = match positional_args { + 0 => format!("no {}arguments were given", positional), + 1 => format!("there is 1 {}argument", positional), + x => format!("there are {} {}arguments", x, positional), + }; + err.note(&msg); + + if named_pos.contains_key(&idx) { + err.span_label(args.operands[idx].1, "named argument"); + err.span_note( + args.operands[idx].1, + "named arguments cannot be referenced by position", + ); + } else if args.reg_args.contains(&idx) { + err.span_label( + args.operands[idx].1, + "explicit register argument", + ); + err.span_note( + args.operands[idx].1, + "explicit register arguments cannot be used in the asm template", + ); + } + err.emit(); + None + } else { + Some(idx) + } + } + parse::ArgumentNamed(name) => match args.named_args.get(&name) { + Some(&idx) => Some(idx), + None => { + let msg = format!("there is no argument named `{}`", name); + ecx.struct_span_err(span, &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(sp)) + .unwrap_or(template_sp); + ecx.struct_span_err( + span, + "asm template modifier must be a single character", + ) + .emit(); + modifier = None; + } + + if let Some(operand_idx) = operand_idx { + used[operand_idx] = true; + template.push(ast::InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier, + span, + }); + } + } + } + } + + if parser.line_spans.is_empty() { + let template_num_lines = 1 + template_str.matches('\n').count(); + line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines)); + } else { + line_spans.extend(parser.line_spans.iter().map(|span| template_span.from_inner(*span))); + }; + } + + let mut unused_operands = vec![]; + let mut help_str = String::new(); + for (idx, used) in used.into_iter().enumerate() { + if !used { + let msg = if let Some(sym) = named_pos.get(&idx) { + help_str.push_str(&format!(" {{{}}}", sym)); + "named argument never used" + } else { + help_str.push_str(&format!(" {{{}}}", idx)); + "argument never used" + }; + unused_operands.push((args.operands[idx].1, msg)); + } + } + match unused_operands.len() { + 0 => {} + 1 => { + let (sp, msg) = unused_operands.into_iter().next().unwrap(); + let mut err = ecx.struct_span_err(sp, msg); + err.span_label(sp, msg); + err.help(&format!( + "if this argument is intentionally unused, \ + consider using it in an asm comment: `\"/*{} */\"`", + help_str + )); + err.emit(); + } + _ => { + let mut err = ecx.struct_span_err( + unused_operands.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(), + "multiple unused asm arguments", + ); + for (sp, msg) in unused_operands { + err.span_label(sp, msg); + } + err.help(&format!( + "if these arguments are intentionally unused, \ + consider using them in an asm comment: `\"/*{} */\"`", + help_str + )); + err.emit(); + } + } + + let inline_asm = + ast::InlineAsm { template, operands: args.operands, options: args.options, line_spans }; + P(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind: ast::ExprKind::InlineAsm(P(inline_asm)), + span: sp, + attrs: ast::AttrVec::new(), + tokens: None, + }) +} + +pub fn expand_asm<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + match parse_args(ecx, sp, tts) { + Ok(args) => MacEager::expr(expand_preparsed_asm(ecx, sp, args)), + Err(mut err) => { + err.emit(); + DummyResult::any(sp) + } + } +} diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs new file mode 100644 index 00000000000..25181715540 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -0,0 +1,133 @@ +use rustc_errors::{Applicability, DiagnosticBuilder}; + +use rustc_ast::ptr::P; +use rustc_ast::token::{self, TokenKind}; +use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; +use rustc_ast::{self as ast, *}; +use rustc_ast_pretty::pprust; +use rustc_expand::base::*; +use rustc_parse::parser::Parser; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; + +pub fn expand_assert<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn MacResult + 'cx> { + let Assert { cond_expr, custom_message } = match parse_assert(cx, sp, tts) { + Ok(assert) => assert, + Err(mut err) => { + err.emit(); + return DummyResult::any(sp); + } + }; + + // `core::panic` and `std::panic` are different macros, so we use call-site + // context to pick up whichever is currently in scope. + let sp = cx.with_call_site_ctxt(sp); + let tokens = custom_message.unwrap_or_else(|| { + TokenStream::from(TokenTree::token( + TokenKind::lit( + token::Str, + Symbol::intern(&format!( + "assertion failed: {}", + pprust::expr_to_string(&cond_expr).escape_debug() + )), + None, + ), + DUMMY_SP, + )) + }); + let args = P(MacArgs::Delimited(DelimSpan::from_single(sp), MacDelimiter::Parenthesis, tokens)); + let panic_call = MacCall { + path: Path::from_ident(Ident::new(sym::panic, sp)), + args, + prior_type_ascription: None, + }; + let if_expr = cx.expr_if( + sp, + cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)), + cx.expr(sp, ExprKind::MacCall(panic_call)), + None, + ); + MacEager::expr(if_expr) +} + +struct Assert { + cond_expr: P<ast::Expr>, + custom_message: Option<TokenStream>, +} + +fn parse_assert<'a>( + cx: &mut ExtCtxt<'a>, + sp: Span, + stream: TokenStream, +) -> Result<Assert, DiagnosticBuilder<'a>> { + let mut parser = cx.new_parser_from_tts(stream); + + if parser.token == token::Eof { + let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument"); + err.span_label(sp, "boolean expression required"); + return Err(err); + } + + let cond_expr = parser.parse_expr()?; + + // Some crates use the `assert!` macro in the following form (note extra semicolon): + // + // assert!( + // my_function(); + // ); + // + // Emit an error about semicolon and suggest removing it. + if parser.token == token::Semi { + let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument"); + err.span_suggestion( + parser.token.span, + "try removing semicolon", + String::new(), + Applicability::MaybeIncorrect, + ); + err.emit(); + + parser.bump(); + } + + // Some crates use the `assert!` macro in the following form (note missing comma before + // message): + // + // assert!(true "error message"); + // + // Emit an error and suggest inserting a comma. + let custom_message = + if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind { + let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal"); + let comma_span = parser.prev_token.span.shrink_to_hi(); + err.span_suggestion_short( + comma_span, + "try adding a comma", + ", ".to_string(), + Applicability::MaybeIncorrect, + ); + err.emit(); + + parse_custom_message(&mut parser) + } else if parser.eat(&token::Comma) { + parse_custom_message(&mut parser) + } else { + None + }; + + if parser.token != token::Eof { + parser.expect_one_of(&[], &[])?; + unreachable!(); + } + + 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/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs new file mode 100644 index 00000000000..4c00162b556 --- /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 rustc_ast as ast; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_attr as attr; +use rustc_errors::DiagnosticBuilder; +use rustc_expand::base::{self, *}; +use rustc_span::Span; + +pub fn expand_cfg( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + + match parse_cfg(cx, sp, tts) { + Ok(cfg) => { + let matches_cfg = attr::cfg_matches(&cfg, &cx.sess.parse_sess, cx.ecfg.features); + MacEager::expr(cx.expr_bool(sp, matches_cfg)) + } + Err(mut err) => { + err.emit(); + DummyResult::any(sp) + } + } +} + +fn parse_cfg<'a>( + cx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> Result<ast::MetaItem, DiagnosticBuilder<'a>> { + let mut p = cx.new_parser_from_tts(tts); + + if p.token == token::Eof { + let mut err = cx.struct_span_err(sp, "macro requires a cfg-pattern as an argument"); + err.span_label(sp, "cfg-pattern required"); + return Err(err); + } + + let cfg = p.parse_meta_item()?; + + let _ = p.eat(&token::Comma); + + if !p.eat(&token::Eof) { + return Err(cx.struct_span_err(sp, "expected 1 cfg-pattern")); + } + + 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..75f4b077640 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs @@ -0,0 +1,59 @@ +//! Implementation of the `#[cfg_accessible(path)]` attribute macro. + +use rustc_ast as ast; +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier}; +use rustc_feature::AttributeTemplate; +use rustc_parse::validate_attr; +use rustc_span::symbol::sym; +use rustc_span::Span; + +crate struct Expander; + +fn validate_input<'a>(ecx: &mut ExtCtxt<'_>, mi: &'a ast::MetaItem) -> Option<&'a ast::Path> { + match mi.meta_item_list() { + None => {} + Some([]) => ecx.span_err(mi.span, "`cfg_accessible` path is not specified"), + Some([_, .., l]) => ecx.span_err(l.span(), "multiple `cfg_accessible` paths are specified"), + Some([nmi]) => match nmi.meta_item() { + None => ecx.span_err(nmi.span(), "`cfg_accessible` path cannot be a literal"), + Some(mi) => { + if !mi.is_word() { + ecx.span_err(mi.span, "`cfg_accessible` path cannot accept arguments"); + } + return Some(&mi.path); + } + }, + } + None +} + +impl MultiItemModifier for Expander { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + _span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + ) -> ExpandResult<Vec<Annotatable>, Annotatable> { + let template = AttributeTemplate { list: Some("path"), ..Default::default() }; + let attr = &ecx.attribute(meta_item.clone()); + validate_attr::check_builtin_attribute( + &ecx.sess.parse_sess, + attr, + sym::cfg_accessible, + template, + ); + + let path = match validate_input(ecx, meta_item) { + Some(path) => path, + None => return ExpandResult::Ready(Vec::new()), + }; + + let failure_msg = "cannot determine whether the path is accessible or not"; + match ecx.resolver.cfg_accessible(ecx.current_expansion.id, path) { + Ok(true) => ExpandResult::Ready(vec![item]), + Ok(false) => ExpandResult::Ready(Vec::new()), + Err(_) => ExpandResult::Retry(item, failure_msg.into()), + } + } +} 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..34e2accc615 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs @@ -0,0 +1,35 @@ +//! Attributes injected into the crate root from command line using `-Z crate-attr`. + +use rustc_ast::attr::mk_attr; +use rustc_ast::token; +use rustc_ast::{self as ast, AttrItem, AttrStyle}; +use rustc_session::parse::ParseSess; +use rustc_span::FileName; + +pub fn inject(mut krate: ast::Crate, parse_sess: &ParseSess, attrs: &[String]) -> ast::Crate { + for raw_attr in attrs { + let mut parser = rustc_parse::new_parser_from_source_str( + parse_sess, + FileName::cli_crate_attr_source_code(&raw_attr), + raw_attr.clone(), + ); + + let start_span = parser.token.span; + let AttrItem { path, args } = match parser.parse_attr_item() { + Ok(ai) => ai, + Err(mut err) => { + err.emit(); + continue; + } + }; + let end_span = parser.token.span; + if parser.token != token::Eof { + parse_sess.span_diagnostic.span_err(start_span.to(end_span), "invalid crate attribute"); + continue; + } + + krate.attrs.push(mk_attr(AttrStyle::Inner, path, args, start_span.to(end_span))); + } + + krate +} diff --git a/compiler/rustc_builtin_macros/src/compile_error.rs b/compiler/rustc_builtin_macros/src/compile_error.rs new file mode 100644 index 00000000000..f5955604e5f --- /dev/null +++ b/compiler/rustc_builtin_macros/src/compile_error.rs @@ -0,0 +1,20 @@ +// The compiler code necessary to support the compile_error! extension. + +use rustc_ast::tokenstream::TokenStream; +use rustc_expand::base::{self, *}; +use rustc_span::Span; + +pub fn expand_compile_error<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + let var = match get_single_str_from_tts(cx, sp, tts, "compile_error!") { + None => return DummyResult::any(sp), + Some(v) => v, + }; + + cx.span_err(sp, &var); + + DummyResult::any(sp) +} diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs new file mode 100644 index 00000000000..e5077d93674 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -0,0 +1,65 @@ +use rustc_ast as ast; +use rustc_ast::tokenstream::TokenStream; +use rustc_expand::base::{self, DummyResult}; +use rustc_span::symbol::Symbol; + +use std::string::String; + +pub fn expand_concat( + cx: &mut base::ExtCtxt<'_>, + sp: rustc_span::Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let es = match base::get_exprs_from_tts(cx, sp, tts) { + Some(e) => e, + None => return DummyResult::any(sp), + }; + let mut accumulator = String::new(); + let mut missing_literal = vec![]; + let mut has_errors = false; + for e in es { + match e.kind { + ast::ExprKind::Lit(ref lit) => match lit.kind { + ast::LitKind::Str(ref s, _) | ast::LitKind::Float(ref s, _) => { + accumulator.push_str(&s.as_str()); + } + ast::LitKind::Char(c) => { + accumulator.push(c); + } + ast::LitKind::Int( + i, + ast::LitIntType::Unsigned(_) + | ast::LitIntType::Signed(_) + | ast::LitIntType::Unsuffixed, + ) => { + accumulator.push_str(&i.to_string()); + } + ast::LitKind::Bool(b) => { + accumulator.push_str(&b.to_string()); + } + ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..) => { + cx.span_err(e.span, "cannot concatenate a byte string literal"); + } + ast::LitKind::Err(_) => { + has_errors = true; + } + }, + ast::ExprKind::Err => { + has_errors = true; + } + _ => { + missing_literal.push(e.span); + } + } + } + if !missing_literal.is_empty() { + let mut err = cx.struct_span_err(missing_literal, "expected a literal"); + err.note("only literals (like `\"foo\"`, `42` and `3.14`) can be passed to `concat!()`"); + err.emit(); + return DummyResult::any(sp); + } else if has_errors { + return DummyResult::any(sp); + } + let sp = cx.with_def_site_ctxt(sp); + base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&accumulator))) +} diff --git a/compiler/rustc_builtin_macros/src/concat_idents.rs b/compiler/rustc_builtin_macros/src/concat_idents.rs new file mode 100644 index 00000000000..8223cdda072 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/concat_idents.rs @@ -0,0 +1,69 @@ +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::token::{self, Token}; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_expand::base::{self, *}; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::Span; + +pub fn expand_concat_idents<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + if tts.is_empty() { + cx.span_err(sp, "concat_idents! takes 1 or more arguments."); + return DummyResult::any(sp); + } + + let mut res_str = String::new(); + for (i, e) in tts.into_trees().enumerate() { + if i & 1 == 1 { + match e { + TokenTree::Token(Token { kind: token::Comma, .. }) => {} + _ => { + cx.span_err(sp, "concat_idents! expecting comma."); + return DummyResult::any(sp); + } + } + } else { + match e { + TokenTree::Token(Token { kind: token::Ident(name, _), .. }) => { + res_str.push_str(&name.as_str()) + } + _ => { + cx.span_err(sp, "concat_idents! requires ident args."); + return DummyResult::any(sp); + } + } + } + } + + let ident = Ident::new(Symbol::intern(&res_str), cx.with_call_site_ctxt(sp)); + + struct ConcatIdentsResult { + ident: Ident, + } + + impl base::MacResult for ConcatIdentsResult { + fn make_expr(self: Box<Self>) -> Option<P<ast::Expr>> { + Some(P(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind: ast::ExprKind::Path(None, ast::Path::from_ident(self.ident)), + span: self.ident.span, + attrs: ast::AttrVec::new(), + tokens: None, + })) + } + + fn make_ty(self: Box<Self>) -> Option<P<ast::Ty>> { + Some(P(ast::Ty { + id: ast::DUMMY_NODE_ID, + kind: ast::TyKind::Path(None, ast::Path::from_ident(self.ident)), + span: self.ident.span, + })) + } + } + + Box::new(ConcatIdentsResult { ident }) +} 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..12ef166b8b0 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/bounds.rs @@ -0,0 +1,29 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::path_std; + +use rustc_ast::MetaItem; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::Span; + +pub fn expand_deriving_copy( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: path_std!(marker::Copy), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: true, + methods: Vec::new(), + associated_types: Vec::new(), + }; + + trait_def.expand(cx, mitem, item, push); +} diff --git a/compiler/rustc_builtin_macros/src/deriving/clone.rs b/compiler/rustc_builtin_macros/src/deriving/clone.rs new file mode 100644 index 00000000000..957c8035399 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs @@ -0,0 +1,224 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::path_std; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Expr, GenericArg, Generics, ItemKind, MetaItem, VariantData}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::Span; + +pub fn expand_deriving_clone( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + // check if we can use a short form + // + // the short form is `fn clone(&self) -> Self { *self }` + // + // we can use the short form if: + // - the item is Copy (unfortunately, all we can check is whether it's also deriving Copy) + // - there are no generic parameters (after specialization this limitation can be removed) + // if we used the short 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. and until specialization we can't write both impls. + // - the item is a union with Copy fields + // Unions with generic parameters still can derive Clone because they require Copy + // for deriving, Clone alone is not enough. + // Whever Clone is implemented for fields is irrelevant so we don't assert it. + let bounds; + let substructure; + let is_shallow; + match *item { + Annotatable::Item(ref annitem) => match annitem.kind { + ItemKind::Struct(_, Generics { ref params, .. }) + | ItemKind::Enum(_, Generics { ref params, .. }) => { + let container_id = cx.current_expansion.id.expn_data().parent; + if cx.resolver.has_derive_copy(container_id) + && !params.iter().any(|param| match param.kind { + ast::GenericParamKind::Type { .. } => true, + _ => false, + }) + { + bounds = vec![]; + is_shallow = true; + substructure = combine_substructure(Box::new(|c, s, sub| { + cs_clone_shallow("Clone", c, s, sub, false) + })); + } else { + bounds = vec![]; + is_shallow = false; + substructure = + combine_substructure(Box::new(|c, s, sub| cs_clone("Clone", c, s, sub))); + } + } + ItemKind::Union(..) => { + bounds = vec![Literal(path_std!(marker::Copy))]; + is_shallow = true; + substructure = combine_substructure(Box::new(|c, s, sub| { + cs_clone_shallow("Clone", c, s, sub, true) + })); + } + _ => { + bounds = vec![]; + is_shallow = false; + substructure = + combine_substructure(Box::new(|c, s, sub| cs_clone("Clone", c, s, sub))); + } + }, + + _ => cx.span_bug(span, "`#[derive(Clone)]` on trait item or impl item"), + } + + let inline = cx.meta_word(span, sym::inline); + let attrs = vec![cx.attribute(inline)]; + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: path_std!(clone::Clone), + additional_bounds: bounds, + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: true, + methods: vec![MethodDef { + name: sym::clone, + generics: Bounds::empty(), + explicit_self: borrowed_explicit_self(), + args: Vec::new(), + ret_ty: Self_, + attributes: attrs, + is_unsafe: false, + unify_fieldless_variants: false, + combine_substructure: substructure, + }], + associated_types: Vec::new(), + }; + + trait_def.expand_ext(cx, mitem, item, push, is_shallow) +} + +fn cs_clone_shallow( + name: &str, + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, + is_union: bool, +) -> P<Expr> { + fn assert_ty_bounds( + cx: &mut ExtCtxt<'_>, + stmts: &mut Vec<ast::Stmt>, + ty: P<ast::Ty>, + span: Span, + helper_name: &str, + ) { + // Generate statement `let _: helper_name<ty>;`, + // set the expn ID so we can use the unstable struct. + let span = cx.with_def_site_ctxt(span); + let assert_path = cx.path_all( + span, + true, + cx.std_path(&[sym::clone, Symbol::intern(helper_name)]), + vec![GenericArg::Type(ty)], + ); + stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path))); + } + fn process_variant(cx: &mut ExtCtxt<'_>, stmts: &mut Vec<ast::Stmt>, variant: &VariantData) { + for field in variant.fields() { + // let _: AssertParamIsClone<FieldTy>; + assert_ty_bounds(cx, stmts, field.ty.clone(), field.span, "AssertParamIsClone"); + } + } + + let mut stmts = Vec::new(); + if is_union { + // let _: AssertParamIsCopy<Self>; + let self_ty = cx.ty_path(cx.path_ident(trait_span, Ident::with_dummy_span(kw::SelfUpper))); + assert_ty_bounds(cx, &mut stmts, self_ty, trait_span, "AssertParamIsCopy"); + } else { + match *substr.fields { + StaticStruct(vdata, ..) => { + process_variant(cx, &mut stmts, vdata); + } + StaticEnum(enum_def, ..) => { + for variant in &enum_def.variants { + process_variant(cx, &mut stmts, &variant.data); + } + } + _ => cx.span_bug( + trait_span, + &format!( + "unexpected substructure in \ + shallow `derive({})`", + name + ), + ), + } + } + stmts.push(cx.stmt_expr(cx.expr_deref(trait_span, cx.expr_self(trait_span)))); + cx.expr_block(cx.block(trait_span, stmts)) +} + +fn cs_clone( + name: &str, + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, +) -> P<Expr> { + let ctor_path; + let all_fields; + let fn_path = cx.std_path(&[sym::clone, sym::Clone, sym::clone]); + let subcall = |cx: &mut ExtCtxt<'_>, field: &FieldInfo<'_>| { + let args = vec![cx.expr_addr_of(field.span, field.self_.clone())]; + cx.expr_call_global(field.span, fn_path.clone(), args) + }; + + let vdata; + match *substr.fields { + Struct(vdata_, ref af) => { + ctor_path = cx.path(trait_span, vec![substr.type_ident]); + all_fields = af; + vdata = vdata_; + } + EnumMatching(.., variant, ref af) => { + ctor_path = cx.path(trait_span, vec![substr.type_ident, variant.ident]); + all_fields = af; + vdata = &variant.data; + } + EnumNonMatchingCollapsed(..) => { + cx.span_bug(trait_span, &format!("non-matching enum variants in `derive({})`", name,)) + } + StaticEnum(..) | StaticStruct(..) => { + cx.span_bug(trait_span, &format!("associated function in `derive({})`", name)) + } + } + + match *vdata { + VariantData::Struct(..) => { + let fields = all_fields + .iter() + .map(|field| { + let ident = match field.name { + Some(i) => i, + None => cx.span_bug( + trait_span, + &format!("unnamed field in normal struct in `derive({})`", name,), + ), + }; + let call = subcall(cx, field); + cx.field_imm(field.span, ident, call) + }) + .collect::<Vec<_>>(); + + cx.expr_struct(trait_span, ctor_path, fields) + } + VariantData::Tuple(..) => { + let subcalls = all_fields.iter().map(|f| subcall(cx, f)).collect(); + let path = cx.expr_path(ctor_path); + cx.expr_call(trait_span, path, subcalls) + } + VariantData::Unit(..) => cx.expr_path(ctor_path), + } +} 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..79f35ad5819 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -0,0 +1,98 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::path_std; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Expr, GenericArg, MetaItem}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; + +pub fn expand_deriving_eq( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + let inline = cx.meta_word(span, sym::inline); + let hidden = rustc_ast::attr::mk_nested_word_item(Ident::new(sym::hidden, span)); + let doc = rustc_ast::attr::mk_list_item(Ident::new(sym::doc, span), vec![hidden]); + let attrs = vec![cx.attribute(inline), cx.attribute(doc)]; + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: path_std!(cmp::Eq), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: true, + methods: vec![MethodDef { + name: sym::assert_receiver_is_total_eq, + generics: Bounds::empty(), + explicit_self: borrowed_explicit_self(), + args: vec![], + ret_ty: nil_ty(), + attributes: attrs, + is_unsafe: false, + unify_fieldless_variants: true, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + cs_total_eq_assert(a, b, c) + })), + }], + associated_types: Vec::new(), + }; + + super::inject_impl_of_structural_trait(cx, span, item, path_std!(marker::StructuralEq), push); + + trait_def.expand_ext(cx, mitem, item, push, true) +} + +fn cs_total_eq_assert( + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, +) -> P<Expr> { + fn assert_ty_bounds( + cx: &mut ExtCtxt<'_>, + stmts: &mut Vec<ast::Stmt>, + ty: P<ast::Ty>, + span: Span, + helper_name: &str, + ) { + // Generate statement `let _: helper_name<ty>;`, + // set the expn ID so we can use the unstable struct. + let span = cx.with_def_site_ctxt(span); + let assert_path = cx.path_all( + span, + true, + cx.std_path(&[sym::cmp, Symbol::intern(helper_name)]), + vec![GenericArg::Type(ty)], + ); + stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path))); + } + fn process_variant( + cx: &mut ExtCtxt<'_>, + stmts: &mut Vec<ast::Stmt>, + variant: &ast::VariantData, + ) { + for field in variant.fields() { + // let _: AssertParamIsEq<FieldTy>; + assert_ty_bounds(cx, stmts, field.ty.clone(), field.span, "AssertParamIsEq"); + } + } + + let mut stmts = Vec::new(); + match *substr.fields { + StaticStruct(vdata, ..) => { + process_variant(cx, &mut stmts, vdata); + } + StaticEnum(enum_def, ..) => { + for variant in &enum_def.variants { + process_variant(cx, &mut stmts, &variant.data); + } + } + _ => cx.span_bug(trait_span, "unexpected substructure in `derive(Eq)`"), + } + cx.expr_block(cx.block(trait_span, 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..c1473e24093 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs @@ -0,0 +1,113 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::path_std; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Expr, MetaItem}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::Span; + +pub fn expand_deriving_ord( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + let inline = cx.meta_word(span, sym::inline); + let attrs = vec![cx.attribute(inline)]; + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: path_std!(cmp::Ord), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods: vec![MethodDef { + name: sym::cmp, + generics: Bounds::empty(), + explicit_self: borrowed_explicit_self(), + args: vec![(borrowed_self(), sym::other)], + ret_ty: Literal(path_std!(cmp::Ordering)), + attributes: attrs, + is_unsafe: false, + unify_fieldless_variants: true, + combine_substructure: combine_substructure(Box::new(|a, b, c| cs_cmp(a, b, c))), + }], + associated_types: Vec::new(), + }; + + trait_def.expand(cx, mitem, item, push) +} + +pub fn ordering_collapsed( + cx: &mut ExtCtxt<'_>, + span: Span, + self_arg_tags: &[Ident], +) -> P<ast::Expr> { + let lft = cx.expr_ident(span, self_arg_tags[0]); + let rgt = cx.expr_addr_of(span, cx.expr_ident(span, self_arg_tags[1])); + cx.expr_method_call(span, lft, Ident::new(sym::cmp, span), vec![rgt]) +} + +pub fn cs_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> { + let test_id = Ident::new(sym::cmp, span); + let equals_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 ::std::cmp::Ord::cmp(&self_field1, &other_field1) { + // ::std::cmp::Ordering::Equal => + // match ::std::cmp::Ord::cmp(&self_field2, &other_field2) { + // ::std::cmp::Ordering::Equal => { + // ... + // } + // cmp => cmp + // }, + // cmp => cmp + // } + // + 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, old, self_f, other_fs| { + // match new { + // ::std::cmp::Ordering::Equal => old, + // cmp => cmp + // } + + let new = { + let other_f = match other_fs { + [o_f] => o_f, + _ => cx.span_bug(span, "not exactly 2 arguments in `derive(Ord)`"), + }; + + let args = + vec![cx.expr_addr_of(span, self_f), cx.expr_addr_of(span, other_f.clone())]; + + cx.expr_call_global(span, cmp_path.clone(), args) + }; + + let eq_arm = cx.arm(span, cx.pat_path(span, equals_path.clone()), old); + let neq_arm = cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id)); + + cx.expr_match(span, new, vec![eq_arm, neq_arm]) + }, + cx.expr_path(equals_path.clone()), + Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| { + if self_args.len() != 2 { + cx.span_bug(span, "not exactly 2 arguments in `derive(Ord)`") + } else { + ordering_collapsed(cx, span, tag_tuple) + } + }), + cx, + span, + substr, + ) +} 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..8e9f15743cc --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs @@ -0,0 +1,112 @@ +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, Expr, MetaItem}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::sym; +use rustc_span::Span; + +pub fn expand_deriving_partial_eq( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + // structures are equal if all fields are equal, and non equal, if + // any fields are not equal or if the enum variants are different + fn cs_op( + cx: &mut ExtCtxt<'_>, + span: Span, + substr: &Substructure<'_>, + op: BinOpKind, + combiner: BinOpKind, + base: bool, + ) -> P<Expr> { + let op = |cx: &mut ExtCtxt<'_>, span: Span, self_f: P<Expr>, other_fs: &[P<Expr>]| { + let other_f = match other_fs { + [o_f] => o_f, + _ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialEq)`"), + }; + + cx.expr_binary(span, op, self_f, other_f.clone()) + }; + + cs_fold1( + true, // use foldl + |cx, span, subexpr, self_f, other_fs| { + let eq = op(cx, span, self_f, other_fs); + cx.expr_binary(span, combiner, subexpr, eq) + }, + |cx, args| { + match args { + Some((span, self_f, other_fs)) => { + // Special-case the base case to generate cleaner code. + op(cx, span, self_f, other_fs) + } + None => cx.expr_bool(span, base), + } + }, + Box::new(|cx, span, _, _| cx.expr_bool(span, !base)), + cx, + span, + substr, + ) + } + + fn cs_eq(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> { + cs_op(cx, span, substr, BinOpKind::Eq, BinOpKind::And, true) + } + fn cs_ne(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> { + cs_op(cx, span, substr, BinOpKind::Ne, BinOpKind::Or, false) + } + + macro_rules! md { + ($name:expr, $f:ident) => {{ + let inline = cx.meta_word(span, sym::inline); + let attrs = vec![cx.attribute(inline)]; + MethodDef { + name: $name, + generics: Bounds::empty(), + explicit_self: borrowed_explicit_self(), + args: vec![(borrowed_self(), sym::other)], + ret_ty: Literal(path_local!(bool)), + attributes: attrs, + is_unsafe: false, + unify_fieldless_variants: true, + combine_substructure: combine_substructure(Box::new(|a, b, c| $f(a, b, c))), + } + }}; + } + + super::inject_impl_of_structural_trait( + cx, + span, + item, + path_std!(marker::StructuralPartialEq), + push, + ); + + // avoid defining `ne` if we can + // c-like enums, enums without any fields and structs without fields + // can safely define only `eq`. + let mut methods = vec![md!(sym::eq, cs_eq)]; + if !is_type_without_fields(item) { + methods.push(md!(sym::ne, cs_ne)); + } + + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: path_std!(cmp::PartialEq), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods, + associated_types: Vec::new(), + }; + trait_def.expand(cx, mitem, item, push) +} diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs new file mode 100644 index 00000000000..21174ca4c8b --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs @@ -0,0 +1,302 @@ +pub use OrderingOp::*; + +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::{path_local, path_std, pathvec_std}; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, BinOpKind, Expr, MetaItem}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; + +pub fn expand_deriving_partial_ord( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + macro_rules! md { + ($name:expr, $op:expr, $equal:expr) => {{ + let inline = cx.meta_word(span, sym::inline); + let attrs = vec![cx.attribute(inline)]; + MethodDef { + name: $name, + generics: Bounds::empty(), + explicit_self: borrowed_explicit_self(), + args: vec![(borrowed_self(), sym::other)], + ret_ty: Literal(path_local!(bool)), + attributes: attrs, + is_unsafe: false, + unify_fieldless_variants: true, + combine_substructure: combine_substructure(Box::new(|cx, span, substr| { + cs_op($op, $equal, cx, span, substr) + })), + } + }}; + } + + let ordering_ty = Literal(path_std!(cmp::Ordering)); + let ret_ty = Literal(Path::new_( + pathvec_std!(option::Option), + None, + vec![Box::new(ordering_ty)], + PathKind::Std, + )); + + let inline = cx.meta_word(span, sym::inline); + let attrs = vec![cx.attribute(inline)]; + + let partial_cmp_def = MethodDef { + name: sym::partial_cmp, + generics: Bounds::empty(), + explicit_self: borrowed_explicit_self(), + args: vec![(borrowed_self(), sym::other)], + ret_ty, + attributes: attrs, + is_unsafe: false, + unify_fieldless_variants: true, + combine_substructure: combine_substructure(Box::new(|cx, span, substr| { + cs_partial_cmp(cx, span, substr) + })), + }; + + // avoid defining extra methods if we can + // c-like enums, enums without any fields and structs without fields + // can safely define only `partial_cmp`. + let methods = if is_type_without_fields(item) { + vec![partial_cmp_def] + } else { + vec![ + partial_cmp_def, + md!(sym::lt, true, false), + md!(sym::le, true, true), + md!(sym::gt, false, false), + md!(sym::ge, false, true), + ] + }; + + let trait_def = TraitDef { + span, + attributes: vec![], + path: path_std!(cmp::PartialOrd), + additional_bounds: vec![], + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods, + associated_types: Vec::new(), + }; + trait_def.expand(cx, mitem, item, push) +} + +#[derive(Copy, Clone)] +pub enum OrderingOp { + PartialCmpOp, + LtOp, + LeOp, + GtOp, + GeOp, +} + +pub fn some_ordering_collapsed( + cx: &mut ExtCtxt<'_>, + span: Span, + op: OrderingOp, + self_arg_tags: &[Ident], +) -> P<ast::Expr> { + let lft = cx.expr_ident(span, self_arg_tags[0]); + let rgt = cx.expr_addr_of(span, cx.expr_ident(span, self_arg_tags[1])); + let op_sym = match op { + PartialCmpOp => sym::partial_cmp, + LtOp => sym::lt, + LeOp => sym::le, + GtOp => sym::gt, + GeOp => sym::ge, + }; + cx.expr_method_call(span, lft, Ident::new(op_sym, span), vec![rgt]) +} + +pub fn cs_partial_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> { + let test_id = Ident::new(sym::cmp, span); + let ordering = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal])); + let ordering_expr = cx.expr_path(ordering.clone()); + let equals_expr = cx.expr_some(span, ordering_expr); + + let partial_cmp_path = cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]); + + // Builds: + // + // match ::std::cmp::PartialOrd::partial_cmp(&self_field1, &other_field1) { + // ::std::option::Option::Some(::std::cmp::Ordering::Equal) => + // match ::std::cmp::PartialOrd::partial_cmp(&self_field2, &other_field2) { + // ::std::option::Option::Some(::std::cmp::Ordering::Equal) => { + // ... + // } + // cmp => cmp + // }, + // cmp => cmp + // } + // + 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, old, self_f, other_fs| { + // match new { + // Some(::std::cmp::Ordering::Equal) => old, + // cmp => cmp + // } + + let new = { + let other_f = match other_fs { + [o_f] => o_f, + _ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`"), + }; + + let args = + vec![cx.expr_addr_of(span, self_f), cx.expr_addr_of(span, other_f.clone())]; + + cx.expr_call_global(span, partial_cmp_path.clone(), args) + }; + + let eq_arm = cx.arm(span, cx.pat_some(span, cx.pat_path(span, ordering.clone())), old); + let neq_arm = cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id)); + + cx.expr_match(span, new, vec![eq_arm, neq_arm]) + }, + equals_expr, + Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| { + if self_args.len() != 2 { + cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`") + } else { + some_ordering_collapsed(cx, span, PartialCmpOp, tag_tuple) + } + }), + cx, + span, + substr, + ) +} + +/// Strict inequality. +fn cs_op( + less: bool, + inclusive: bool, + cx: &mut ExtCtxt<'_>, + span: Span, + substr: &Substructure<'_>, +) -> P<Expr> { + let ordering_path = |cx: &mut ExtCtxt<'_>, name: &str| { + cx.expr_path( + cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, Symbol::intern(name)])), + ) + }; + + let par_cmp = |cx: &mut ExtCtxt<'_>, span, self_f: P<Expr>, other_fs: &[P<Expr>], default| { + let other_f = match other_fs { + [o_f] => o_f, + _ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`"), + }; + + // `PartialOrd::partial_cmp(self.fi, other.fi)` + let cmp_path = cx.expr_path( + cx.path_global(span, cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp])), + ); + let cmp = cx.expr_call( + span, + cmp_path, + vec![cx.expr_addr_of(span, self_f), cx.expr_addr_of(span, other_f.clone())], + ); + + let default = ordering_path(cx, default); + // `Option::unwrap_or(_, Ordering::Equal)` + let unwrap_path = cx.expr_path( + cx.path_global(span, cx.std_path(&[sym::option, sym::Option, sym::unwrap_or])), + ); + cx.expr_call(span, unwrap_path, vec![cmp, default]) + }; + + let fold = cs_fold1( + false, // need foldr + |cx, span, subexpr, self_f, other_fs| { + // build up a series of `partial_cmp`s from the inside + // out (hence foldr) to get lexical ordering, i.e., for op == + // `ast::lt` + // + // ``` + // Ordering::then_with( + // Option::unwrap_or( + // PartialOrd::partial_cmp(self.f1, other.f1), Ordering::Equal) + // ), + // Option::unwrap_or( + // PartialOrd::partial_cmp(self.f2, other.f2), Ordering::Greater) + // ) + // ) + // == Ordering::Less + // ``` + // + // and for op == + // `ast::le` + // + // ``` + // Ordering::then_with( + // Option::unwrap_or( + // PartialOrd::partial_cmp(self.f1, other.f1), Ordering::Equal) + // ), + // Option::unwrap_or( + // PartialOrd::partial_cmp(self.f2, other.f2), Ordering::Greater) + // ) + // ) + // != Ordering::Greater + // ``` + // + // The optimiser should remove the redundancy. We explicitly + // get use the binops to avoid auto-deref dereferencing too many + // layers of pointers, if the type includes pointers. + + // `Option::unwrap_or(PartialOrd::partial_cmp(self.fi, other.fi), Ordering::Equal)` + let par_cmp = par_cmp(cx, span, self_f, other_fs, "Equal"); + + // `Ordering::then_with(Option::unwrap_or(..), ..)` + let then_with_path = cx.expr_path( + cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::then_with])), + ); + cx.expr_call(span, then_with_path, vec![par_cmp, cx.lambda0(span, subexpr)]) + }, + |cx, args| match args { + Some((span, self_f, other_fs)) => { + let opposite = if less { "Greater" } else { "Less" }; + par_cmp(cx, span, self_f, other_fs, opposite) + } + None => cx.expr_bool(span, inclusive), + }, + Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| { + if self_args.len() != 2 { + cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`") + } else { + let op = match (less, inclusive) { + (false, false) => GtOp, + (false, true) => GeOp, + (true, false) => LtOp, + (true, true) => LeOp, + }; + some_ordering_collapsed(cx, span, op, tag_tuple) + } + }), + cx, + span, + substr, + ); + + match *substr.fields { + EnumMatching(.., ref all_fields) | Struct(.., ref all_fields) if !all_fields.is_empty() => { + let ordering = ordering_path(cx, if less ^ inclusive { "Less" } else { "Greater" }); + let comp_op = if inclusive { BinOpKind::Ne } else { BinOpKind::Eq }; + + cx.expr_binary(span, comp_op, fold, ordering) + } + _ => fold, + } +} 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..120e859f2b1 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -0,0 +1,137 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::path_std; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Expr, MetaItem}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::{Span, DUMMY_SP}; + +pub fn expand_deriving_debug( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + // &mut ::std::fmt::Formatter + let fmtr = + Ptr(Box::new(Literal(path_std!(fmt::Formatter))), Borrowed(None, ast::Mutability::Mut)); + + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: path_std!(fmt::Debug), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods: vec![MethodDef { + name: sym::fmt, + generics: Bounds::empty(), + explicit_self: borrowed_explicit_self(), + args: vec![(fmtr, sym::f)], + ret_ty: Literal(path_std!(fmt::Result)), + attributes: Vec::new(), + is_unsafe: false, + unify_fieldless_variants: false, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + show_substructure(a, b, c) + })), + }], + associated_types: Vec::new(), + }; + trait_def.expand(cx, mitem, item, push) +} + +/// We use the debug builders to do the heavy lifting here +fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> { + // build fmt.debug_struct(<name>).field(<fieldname>, &<fieldval>)....build() + // or fmt.debug_tuple(<name>).field(&<fieldval>)....build() + // based on the "shape". + let (ident, vdata, fields) = match substr.fields { + Struct(vdata, fields) => (substr.type_ident, *vdata, fields), + EnumMatching(_, _, v, fields) => (v.ident, &v.data, fields), + EnumNonMatchingCollapsed(..) | StaticStruct(..) | StaticEnum(..) => { + cx.span_bug(span, "nonsensical .fields in `#[derive(Debug)]`") + } + }; + + // We want to make sure we have the ctxt set so that we can use unstable methods + let span = cx.with_def_site_ctxt(span); + let name = cx.expr_lit(span, ast::LitKind::Str(ident.name, ast::StrStyle::Cooked)); + let builder = Ident::new(sym::debug_trait_builder, span); + let builder_expr = cx.expr_ident(span, builder); + + let fmt = substr.nonself_args[0].clone(); + + let mut stmts = vec![]; + match vdata { + ast::VariantData::Tuple(..) | ast::VariantData::Unit(..) => { + // tuple struct/"normal" variant + let expr = + cx.expr_method_call(span, fmt, Ident::new(sym::debug_tuple, span), vec![name]); + stmts.push(cx.stmt_let(span, true, builder, expr)); + + for field in fields { + // Use double indirection to make sure this works for unsized types + let field = cx.expr_addr_of(field.span, field.self_.clone()); + let field = cx.expr_addr_of(field.span, field); + + let expr = cx.expr_method_call( + span, + builder_expr.clone(), + Ident::new(sym::field, span), + vec![field], + ); + + // Use `let _ = expr;` to avoid triggering the + // unused_results lint. + stmts.push(stmt_let_underscore(cx, span, expr)); + } + } + ast::VariantData::Struct(..) => { + // normal struct/struct variant + let expr = + cx.expr_method_call(span, fmt, Ident::new(sym::debug_struct, span), vec![name]); + stmts.push(cx.stmt_let(DUMMY_SP, true, builder, expr)); + + for field in fields { + let name = cx.expr_lit( + field.span, + ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked), + ); + + // Use double indirection to make sure this works for unsized types + let field = cx.expr_addr_of(field.span, field.self_.clone()); + let field = cx.expr_addr_of(field.span, field); + let expr = cx.expr_method_call( + span, + builder_expr.clone(), + Ident::new(sym::field, span), + vec![name, field], + ); + stmts.push(stmt_let_underscore(cx, span, expr)); + } + } + } + + let expr = cx.expr_method_call(span, builder_expr, Ident::new(sym::finish, span), vec![]); + + stmts.push(cx.stmt_expr(expr)); + let block = cx.block(span, stmts); + cx.expr_block(block) +} + +fn stmt_let_underscore(cx: &mut ExtCtxt<'_>, sp: Span, expr: P<ast::Expr>) -> ast::Stmt { + let local = P(ast::Local { + pat: cx.pat_wild(sp), + ty: None, + init: Some(expr), + id: ast::DUMMY_NODE_ID, + span: sp, + attrs: ast::AttrVec::new(), + }); + ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Local(local), span: sp } +} 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..df69f6c90d8 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs @@ -0,0 +1,223 @@ +//! The compiler code necessary for `#[derive(RustcDecodable)]`. See encodable.rs for more. + +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::pathvec_std; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Expr, MetaItem, Mutability}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; + +pub fn expand_deriving_rustc_decodable( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + let krate = sym::rustc_serialize; + let typaram = sym::__D; + + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: Path::new_(vec![krate, sym::Decodable], None, vec![], PathKind::Global), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods: vec![MethodDef { + name: sym::decode, + generics: Bounds { + bounds: vec![( + typaram, + vec![Path::new_(vec![krate, sym::Decoder], None, vec![], PathKind::Global)], + )], + }, + explicit_self: None, + args: vec![( + Ptr(Box::new(Literal(Path::new_local(typaram))), Borrowed(None, Mutability::Mut)), + sym::d, + )], + ret_ty: Literal(Path::new_( + pathvec_std!(result::Result), + None, + vec![ + Box::new(Self_), + Box::new(Literal(Path::new_( + vec![typaram, sym::Error], + None, + vec![], + PathKind::Local, + ))), + ], + PathKind::Std, + )), + attributes: Vec::new(), + is_unsafe: false, + unify_fieldless_variants: false, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + decodable_substructure(a, b, c, krate) + })), + }], + associated_types: Vec::new(), + }; + + trait_def.expand(cx, mitem, item, push) +} + +fn decodable_substructure( + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, + krate: Symbol, +) -> P<Expr> { + let decoder = substr.nonself_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); + + match *substr.fields { + StaticStruct(_, ref summary) => { + let nfields = match *summary { + Unnamed(ref fields, _) => fields.len(), + Named(ref fields) => fields.len(), + }; + let read_struct_field = Ident::new(sym::read_struct_field, trait_span); + + 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_method_call( + span, + blkdecoder.clone(), + read_struct_field, + vec![ + cx.expr_str(span, name), + cx.expr_usize(span, field), + exprdecode.clone(), + ], + ), + ) + }); + let result = cx.expr_ok(trait_span, result); + cx.expr_method_call( + trait_span, + decoder, + Ident::new(sym::read_struct, trait_span), + vec![ + cx.expr_str(trait_span, substr.type_ident.name), + cx.expr_usize(trait_span, nfields), + cx.lambda1(trait_span, result, blkarg), + ], + ) + } + StaticEnum(_, ref fields) => { + let variant = Ident::new(sym::i, trait_span); + + let mut arms = Vec::with_capacity(fields.len() + 1); + let mut variants = Vec::with_capacity(fields.len()); + let rvariant_arg = Ident::new(sym::read_enum_variant_arg, trait_span); + + 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_method_call( + span, + blkdecoder.clone(), + rvariant_arg, + vec![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_vec = cx.expr_vec(trait_span, variants); + let variant_vec = cx.expr_addr_of(trait_span, variant_vec); + let result = cx.expr_method_call( + trait_span, + blkdecoder, + Ident::new(sym::read_enum_variant, trait_span), + vec![variant_vec, lambda], + ); + cx.expr_method_call( + trait_span, + decoder, + Ident::new(sym::read_enum, trait_span), + vec![ + cx.expr_str(trait_span, substr.type_ident.name), + cx.lambda1(trait_span, result, blkarg), + ], + ) + } + _ => cx.bug("expected StaticEnum or StaticStruct in derive(Decodable)"), + } +} + +/// Creates a decoder for a single enum variant/struct: +/// - `outer_pat_path` is the path to this enum variant/struct +/// - `getarg` should retrieve the `usize`-th field with name `@str`. +fn decode_static_fields<F>( + cx: &mut ExtCtxt<'_>, + trait_span: Span, + outer_pat_path: ast::Path, + fields: &StaticFields, + mut getarg: F, +) -> P<Expr> +where + F: FnMut(&mut ExtCtxt<'_>, Span, Symbol, usize) -> P<Expr>, +{ + match *fields { + Unnamed(ref fields, is_tuple) => { + let path_expr = cx.expr_path(outer_pat_path); + if !is_tuple { + path_expr + } else { + let fields = fields + .iter() + .enumerate() + .map(|(i, &span)| getarg(cx, span, Symbol::intern(&format!("_field{}", i)), i)) + .collect(); + + cx.expr_call(trait_span, path_expr, fields) + } + } + Named(ref fields) => { + // use the field's span to get nicer error messages. + let fields = fields + .iter() + .enumerate() + .map(|(i, &(ident, span))| { + let arg = getarg(cx, span, ident.name, i); + cx.field_imm(span, ident, arg) + }) + .collect(); + cx.expr_struct(trait_span, outer_pat_path, fields) + } + } +} diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs new file mode 100644 index 00000000000..980be3a0050 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/default.rs @@ -0,0 +1,86 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; + +use rustc_ast::ptr::P; +use rustc_ast::{Expr, MetaItem}; +use rustc_errors::struct_span_err; +use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt}; +use rustc_span::symbol::{kw, sym}; +use rustc_span::Span; + +pub fn expand_deriving_default( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + let inline = cx.meta_word(span, sym::inline); + let attrs = vec![cx.attribute(inline)]; + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: Path::new(vec![kw::Default, sym::Default]), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods: vec![MethodDef { + name: kw::Default, + generics: Bounds::empty(), + explicit_self: None, + args: Vec::new(), + ret_ty: Self_, + attributes: attrs, + is_unsafe: false, + unify_fieldless_variants: false, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + default_substructure(a, b, c) + })), + }], + associated_types: Vec::new(), + }; + trait_def.expand(cx, mitem, item, push) +} + +fn default_substructure( + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, +) -> P<Expr> { + // Note that `kw::Default` is "default" and `sym::Default` is "Default"! + let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]); + let default_call = |span| cx.expr_call_global(span, default_ident.clone(), Vec::new()); + + match *substr.fields { + StaticStruct(_, ref summary) => match *summary { + Unnamed(ref fields, is_tuple) => { + if !is_tuple { + cx.expr_ident(trait_span, substr.type_ident) + } else { + let exprs = fields.iter().map(|sp| default_call(*sp)).collect(); + cx.expr_call_ident(trait_span, substr.type_ident, exprs) + } + } + Named(ref fields) => { + let default_fields = fields + .iter() + .map(|&(ident, span)| cx.field_imm(span, ident, default_call(span))) + .collect(); + cx.expr_struct_ident(trait_span, substr.type_ident, default_fields) + } + }, + StaticEnum(..) => { + struct_span_err!( + &cx.sess.parse_sess.span_diagnostic, + trait_span, + E0665, + "`Default` cannot be derived for enums, only structs" + ) + .emit(); + // let compilation continue + DummyResult::raw_expr(trait_span, true) + } + _ => cx.span_bug(trait_span, "method in `derive(Default)`"), + } +} 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..62aa1cbfbf2 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/encodable.rs @@ -0,0 +1,291 @@ +//! 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: +//! +//! ``` +//! #[derive(RustcEncodable, RustcDecodable)] +//! struct Node { id: usize } +//! ``` +//! +//! would generate two implementations like: +//! +//! ``` +//! # 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: +//! +//! ``` +//! # #[derive(RustcEncodable, RustcDecodable)] +//! # struct Span; +//! #[derive(RustcEncodable, RustcDecodable)] +//! struct Spanned<T> { node: T, span: Span } +//! ``` +//! +//! would yield functions like: +//! +//! ``` +//! # #[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::ptr::P; +use rustc_ast::{Expr, ExprKind, MetaItem, Mutability}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; + +pub fn expand_deriving_rustc_encodable( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + let krate = sym::rustc_serialize; + let typaram = sym::__S; + + let trait_def = TraitDef { + span, + attributes: Vec::new(), + path: Path::new_(vec![krate, sym::Encodable], None, vec![], PathKind::Global), + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods: vec![MethodDef { + name: sym::encode, + generics: Bounds { + bounds: vec![( + typaram, + vec![Path::new_(vec![krate, sym::Encoder], None, vec![], PathKind::Global)], + )], + }, + explicit_self: borrowed_explicit_self(), + args: vec![( + Ptr(Box::new(Literal(Path::new_local(typaram))), Borrowed(None, Mutability::Mut)), + // FIXME: we could use `sym::s` here, but making `s` a static + // symbol changes the symbol index ordering in a way that makes + // ui/lint/rfc-2457-non-ascii-idents/lint-confusable-idents.rs + // fail. The linting code should be fixed so that its output + // does not depend on the symbol index ordering. + Symbol::intern("s"), + )], + ret_ty: Literal(Path::new_( + pathvec_std!(result::Result), + None, + vec![ + Box::new(Tuple(Vec::new())), + Box::new(Literal(Path::new_( + vec![typaram, sym::Error], + None, + vec![], + PathKind::Local, + ))), + ], + PathKind::Std, + )), + attributes: Vec::new(), + is_unsafe: false, + unify_fieldless_variants: false, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + encodable_substructure(a, b, c, krate) + })), + }], + associated_types: Vec::new(), + }; + + trait_def.expand(cx, mitem, item, push) +} + +fn encodable_substructure( + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substr: &Substructure<'_>, + krate: Symbol, +) -> P<Expr> { + let encoder = substr.nonself_args[0].clone(); + // throw an underscore in front to suppress unused variable warnings + let blkarg = Ident::new(sym::_e, trait_span); + let blkencoder = cx.expr_ident(trait_span, blkarg); + let fn_path = cx.expr_path(cx.path_global( + trait_span, + vec![ + Ident::new(krate, trait_span), + Ident::new(sym::Encodable, trait_span), + Ident::new(sym::encode, trait_span), + ], + )); + + match *substr.fields { + Struct(_, ref fields) => { + let emit_struct_field = Ident::new(sym::emit_struct_field, trait_span); + let mut stmts = Vec::new(); + for (i, &FieldInfo { name, ref self_, 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_.clone()); + let enc = cx.expr_call(span, fn_path.clone(), vec![self_ref, blkencoder.clone()]); + let lambda = cx.lambda1(span, enc, blkarg); + let call = cx.expr_method_call( + span, + blkencoder.clone(), + emit_struct_field, + vec![cx.expr_str(span, name), cx.expr_usize(span, i), lambda], + ); + + // last call doesn't need a try! + let last = fields.len() - 1; + let call = if i != last { + cx.expr_try(span, call) + } else { + cx.expr(span, ExprKind::Ret(Some(call))) + }; + + let stmt = cx.stmt_expr(call); + stmts.push(stmt); + } + + // unit structs have no fields and need to return Ok() + let blk = if stmts.is_empty() { + let ok = cx.expr_ok(trait_span, cx.expr_tuple(trait_span, vec![])); + cx.lambda1(trait_span, ok, blkarg) + } else { + cx.lambda_stmts_1(trait_span, stmts, blkarg) + }; + + cx.expr_method_call( + trait_span, + encoder, + Ident::new(sym::emit_struct, trait_span), + vec![ + cx.expr_str(trait_span, substr.type_ident.name), + cx.expr_usize(trait_span, fields.len()), + blk, + ], + ) + } + + EnumMatching(idx, _, variant, ref fields) => { + // We're not generating an AST that the borrow checker is expecting, + // so we need to generate a unique local variable to take the + // mutable loan out on, otherwise we get conflicts which don't + // actually exist. + let me = cx.stmt_let(trait_span, false, blkarg, encoder); + let encoder = cx.expr_ident(trait_span, blkarg); + let emit_variant_arg = Ident::new(sym::emit_enum_variant_arg, trait_span); + let mut stmts = Vec::new(); + if !fields.is_empty() { + let last = fields.len() - 1; + for (i, &FieldInfo { ref self_, span, .. }) in fields.iter().enumerate() { + let self_ref = cx.expr_addr_of(span, self_.clone()); + let enc = + cx.expr_call(span, fn_path.clone(), vec![self_ref, blkencoder.clone()]); + let lambda = cx.lambda1(span, enc, blkarg); + let call = cx.expr_method_call( + span, + blkencoder.clone(), + emit_variant_arg, + vec![cx.expr_usize(span, i), lambda], + ); + let call = if i != last { + cx.expr_try(span, call) + } else { + cx.expr(span, ExprKind::Ret(Some(call))) + }; + stmts.push(cx.stmt_expr(call)); + } + } else { + let ok = cx.expr_ok(trait_span, cx.expr_tuple(trait_span, vec![])); + let ret_ok = cx.expr(trait_span, ExprKind::Ret(Some(ok))); + stmts.push(cx.stmt_expr(ret_ok)); + } + + let blk = cx.lambda_stmts_1(trait_span, stmts, blkarg); + let name = cx.expr_str(trait_span, variant.ident.name); + let call = cx.expr_method_call( + trait_span, + blkencoder, + Ident::new(sym::emit_enum_variant, trait_span), + vec![ + name, + cx.expr_usize(trait_span, idx), + cx.expr_usize(trait_span, fields.len()), + blk, + ], + ); + let blk = cx.lambda1(trait_span, call, blkarg); + let ret = cx.expr_method_call( + trait_span, + encoder, + Ident::new(sym::emit_enum, trait_span), + vec![cx.expr_str(trait_span, substr.type_ident.name), blk], + ); + cx.expr_block(cx.block(trait_span, vec![me, cx.stmt_expr(ret)])) + } + + _ => cx.bug("expected Struct or EnumMatching in derive(Encodable)"), + } +} diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs new file mode 100644 index 00000000000..849e8b136e1 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -0,0 +1,1757 @@ +//! 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)`) +//! - `EnumNonMatchingCollapsed` when `Self` is an enum and the arguments +//! are not the same variant (e.g., `None`, `Some(1)` and `None`). +//! - `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. For `EnumNonMatchingCollapsed` +//! this isn't possible (different variants have different fields), so the +//! fields are inaccessible. (Previous versions of the deriving infrastructure +//! had a way to expand into code that could access them, at the cost of +//! generating exponential amounts of code; see issue #15375). There are no +//! fields with values in the static cases, so these are treated entirely +//! differently. +//! +//! The non-static cases have `Option<ident>` in several places associated +//! with field `expr`s. This represents the name of the field it is +//! associated with. It is only not `None` when the associated field has +//! an identifier in the source code. For example, the `x`s in the +//! following snippet +//! +//! ```rust +//! # #![allow(dead_code)] +//! struct A { x : i32 } +//! +//! struct B(i32); +//! +//! enum C { +//! C0(i32), +//! C1 { x: i32 } +//! } +//! ``` +//! +//! The `i32`s in `B` and `C0` don't have an identifier, so the +//! `Option<ident>`s would be `None` for them. +//! +//! In the static cases, the structure is summarized, either into the just +//! spans of the fields or a list of spans and the field idents (for tuple +//! structs and record structs, respectively), or a list of these, for +//! enums (one for each variant). For empty struct and empty enum +//! variants, it is represented as a count of 0. +//! +//! # "`cs`" functions +//! +//! The `cs_...` functions ("combine substructure) are designed to +//! make life easier by providing some pre-made recipes for common +//! threads; mostly calling the function being derived on all the +//! arguments and then combining them back together in some way (or +//! letting the user chose that). They are not meant to be the only +//! way to handle the structures that this code creates. +//! +//! # Examples +//! +//! The following simplified `PartialEq` is used for in-code examples: +//! +//! ```rust +//! trait PartialEq { +//! fn eq(&self, other: &Self) -> bool; +//! } +//! impl PartialEq for i32 { +//! fn eq(&self, other: &i32) -> bool { +//! *self == *other +//! } +//! } +//! ``` +//! +//! Some examples of the values of `SubstructureFields` follow, using the +//! above `PartialEq`, `A`, `B` and `C`. +//! +//! ## Structs +//! +//! When generating the `expr` for the `A` impl, the `SubstructureFields` is +//! +//! ```{.text} +//! Struct(vec![FieldInfo { +//! span: <span of x> +//! name: Some(<ident of x>), +//! self_: <expr for &self.x>, +//! other: vec![<expr for &other.x] +//! }]) +//! ``` +//! +//! For the `B` impl, called with `B(a)` and `B(b)`, +//! +//! ```{.text} +//! Struct(vec![FieldInfo { +//! span: <span of `i32`>, +//! name: None, +//! self_: <expr for &a> +//! other: vec![<expr for &b>] +//! }]) +//! ``` +//! +//! ## Enums +//! +//! When generating the `expr` for a call with `self == C0(a)` and `other +//! == C0(b)`, the SubstructureFields is +//! +//! ```{.text} +//! EnumMatching(0, <ast::Variant for C0>, +//! vec![FieldInfo { +//! span: <span of i32> +//! name: None, +//! self_: <expr for &a>, +//! other: vec![<expr for &b>] +//! }]) +//! ``` +//! +//! For `C1 {x}` and `C1 {x}`, +//! +//! ```{.text} +//! EnumMatching(1, <ast::Variant for C1>, +//! vec![FieldInfo { +//! span: <span of x> +//! name: Some(<ident of x>), +//! self_: <expr for &self.x>, +//! other: vec![<expr for &other.x>] +//! }]) +//! ``` +//! +//! For `C0(a)` and `C1 {x}` , +//! +//! ```{.text} +//! EnumNonMatchingCollapsed( +//! vec![<ident of self>, <ident of __arg_1>], +//! &[<ast::Variant for C0>, <ast::Variant for C1>], +//! &[<ident for self index value>, <ident of __arg_1 index value>]) +//! ``` +//! +//! It is the same for when the arguments are flipped to `C1 {x}` and +//! `C0(a)`; the only difference is what the values of the identifiers +//! <ident for self index value> and <ident of __arg_1 index value> will +//! be in the generated code. +//! +//! `EnumNonMatchingCollapsed` deliberately provides far less information +//! than is generally available for a given pair of variants; see #15375 +//! for discussion. +//! +//! ## Static +//! +//! A static method on the types above would result in, +//! +//! ```{.text} +//! StaticStruct(<ast::VariantData of A>, Named(vec![(<ident of x>, <span of x>)])) +//! +//! StaticStruct(<ast::VariantData of B>, Unnamed(vec![<span of x>])) +//! +//! StaticEnum(<ast::EnumDef of C>, +//! vec![(<ident of C0>, <span of C0>, Unnamed(vec![<span of i32>])), +//! (<ident of C1>, <span of C1>, Named(vec![(<ident of x>, <span of x>)]))]) +//! ``` + +pub use StaticFields::*; +pub use SubstructureFields::*; + +use std::cell::RefCell; +use std::iter; +use std::vec; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, BinOpKind, EnumDef, Expr, Generics, PatKind}; +use rustc_ast::{GenericArg, GenericParamKind, VariantData}; +use rustc_attr as attr; +use rustc_data_structures::map_in_place::MapInPlace; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::source_map::respan; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::Span; + +use ty::{Bounds, Path, Ptr, PtrTy, Self_, Ty}; + +use crate::deriving; + +pub mod ty; + +pub struct TraitDef<'a> { + /// The span for the current #[derive(Foo)] header. + pub span: Span, + + pub attributes: Vec<ast::Attribute>, + + /// Path of the trait, including any type parameters + pub path: Path, + + /// Additional bounds required of any type parameters of the type, + /// other than the current trait + pub additional_bounds: Vec<Ty>, + + /// Any extra lifetimes and/or bounds, e.g., `D: serialize::Decoder` + pub generics: Bounds, + + /// Is it an `unsafe` trait? + pub is_unsafe: bool, + + /// Can this trait be derived for unions? + pub supports_unions: bool, + + pub methods: Vec<MethodDef<'a>>, + + pub associated_types: Vec<(Ident, Ty)>, +} + +pub struct MethodDef<'a> { + /// name of the method + pub name: Symbol, + /// List of generics, e.g., `R: rand::Rng` + pub generics: Bounds, + + /// Whether there is a self argument (outer Option) i.e., whether + /// this is a static function, and whether it is a pointer (inner + /// Option) + pub explicit_self: Option<Option<PtrTy>>, + + /// Arguments other than the self argument + pub args: Vec<(Ty, Symbol)>, + + /// Returns type + pub ret_ty: Ty, + + pub attributes: Vec<ast::Attribute>, + + // Is it an `unsafe fn`? + pub is_unsafe: bool, + + /// Can we combine fieldless variants for enums into a single match arm? + pub unify_fieldless_variants: bool, + + pub combine_substructure: RefCell<CombineSubstructureFunc<'a>>, +} + +/// All the data about the data structure/method being derived upon. +pub struct Substructure<'a> { + /// ident of self + pub type_ident: Ident, + /// ident of the method + pub method_ident: Ident, + /// dereferenced access to any `Self_` or `Ptr(Self_, _)` arguments + pub self_args: &'a [P<Expr>], + /// verbatim access to any other arguments + pub nonself_args: &'a [P<Expr>], + pub fields: &'a SubstructureFields<'a>, +} + +/// Summary of the relevant parts of a struct/enum field. +pub struct FieldInfo<'a> { + 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_: P<Expr>, + /// The expressions corresponding to references to this field in + /// the other `Self` arguments. + pub other: Vec<P<Expr>>, + /// The attributes on the field + pub attrs: &'a [ast::Attribute], +} + +/// Fields for a static method +pub enum StaticFields { + /// Tuple and unit structs/enum variants like this. + Unnamed(Vec<Span>, bool /*is tuple*/), + /// Normal structs/struct variants. + Named(Vec<(Ident, Span)>), +} + +/// A summary of the possible sets of fields. +pub enum SubstructureFields<'a> { + Struct(&'a ast::VariantData, Vec<FieldInfo<'a>>), + /// Matching variants of the enum: variant index, variant count, ast::Variant, + /// fields: the field name is only non-`None` in the case of a struct + /// variant. + EnumMatching(usize, usize, &'a ast::Variant, Vec<FieldInfo<'a>>), + + /// Non-matching variants of the enum, but with all state hidden from + /// the consequent code. The first component holds `Ident`s for all of + /// the `Self` arguments; the second component is a slice of all of the + /// variants for the enum itself, and the third component is a list of + /// `Ident`s bound to the variant index values for each of the actual + /// input `Self` arguments. + EnumNonMatchingCollapsed(Vec<Ident>, &'a [ast::Variant], &'a [Ident]), + + /// A static method where `Self` is a struct. + StaticStruct(&'a ast::VariantData, StaticFields), + /// A static method where `Self` is an enum. + StaticEnum(&'a ast::EnumDef, Vec<(Ident, Span, StaticFields)>), +} + +/// Combine the values of all the fields together. The last argument is +/// all the fields of all the structures. +pub type CombineSubstructureFunc<'a> = + Box<dyn FnMut(&mut ExtCtxt<'_>, Span, &Substructure<'_>) -> P<Expr> + 'a>; + +/// Deal with non-matching enum variants. The tuple is a list of +/// identifiers (one for each `Self` argument, which could be any of the +/// variants since they have been collapsed together) and the identifiers +/// holding the variant index value for each of the `Self` arguments. The +/// last argument is all the non-`Self` args of the method being derived. +pub type EnumNonMatchCollapsedFunc<'a> = + Box<dyn FnMut(&mut ExtCtxt<'_>, Span, (&[Ident], &[Ident]), &[P<Expr>]) -> P<Expr> + 'a>; + +pub fn combine_substructure( + f: CombineSubstructureFunc<'_>, +) -> RefCell<CombineSubstructureFunc<'_>> { + RefCell::new(f) +} + +/// 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`. +fn find_type_parameters( + ty: &ast::Ty, + ty_param_names: &[Symbol], + cx: &ExtCtxt<'_>, +) -> Vec<P<ast::Ty>> { + use rustc_ast::visit; + + struct Visitor<'a, 'b> { + cx: &'a ExtCtxt<'b>, + ty_param_names: &'a [Symbol], + types: Vec<P<ast::Ty>>, + } + + impl<'a, 'b> visit::Visitor<'a> for Visitor<'a, 'b> { + fn visit_ty(&mut self, ty: &'a ast::Ty) { + if let ast::TyKind::Path(_, ref path) = ty.kind { + if let Some(segment) = path.segments.first() { + if self.ty_param_names.contains(&segment.ident.name) { + self.types.push(P(ty.clone())); + } + } + } + + visit::walk_ty(self, ty) + } + + fn visit_mac(&mut self, mac: &ast::MacCall) { + self.cx.span_err(mac.span(), "`derive` cannot be used on items with type macros"); + } + } + + let mut visitor = Visitor { cx, ty_param_names, types: Vec::new() }; + visit::Visitor::visit_ty(&mut visitor, ty); + + visitor.types +} + +impl<'a> TraitDef<'a> { + pub fn expand( + self, + cx: &mut ExtCtxt<'_>, + mitem: &ast::MetaItem, + item: &'a Annotatable, + push: &mut dyn FnMut(Annotatable), + ) { + self.expand_ext(cx, mitem, item, push, false); + } + + pub fn expand_ext( + self, + cx: &mut ExtCtxt<'_>, + mitem: &ast::MetaItem, + item: &'a Annotatable, + push: &mut dyn FnMut(Annotatable), + from_scratch: bool, + ) { + match *item { + Annotatable::Item(ref item) => { + let is_packed = item.attrs.iter().any(|attr| { + for r in attr::find_repr_attrs(&cx.sess, attr) { + if let attr::ReprPacked(_) = r { + return true; + } + } + false + }); + let has_no_type_params = match item.kind { + ast::ItemKind::Struct(_, ref generics) + | ast::ItemKind::Enum(_, ref generics) + | ast::ItemKind::Union(_, ref generics) => { + !generics.params.iter().any(|param| match param.kind { + ast::GenericParamKind::Type { .. } => true, + _ => false, + }) + } + _ => { + // Non-ADT derive is an error, but it should have been + // set earlier; see + // librustc_expand/expand.rs:MacroExpander::fully_expand_fragment() + // librustc_expand/base.rs:Annotatable::derive_allowed() + return; + } + }; + let container_id = cx.current_expansion.id.expn_data().parent; + let always_copy = has_no_type_params && cx.resolver.has_derive_copy(container_id); + let use_temporaries = is_packed && always_copy; + + let newitem = match item.kind { + ast::ItemKind::Struct(ref struct_def, ref generics) => self.expand_struct_def( + cx, + &struct_def, + item.ident, + generics, + from_scratch, + use_temporaries, + ), + ast::ItemKind::Enum(ref enum_def, ref generics) => { + // We ignore `use_temporaries` here, because + // `repr(packed)` enums cause an error later on. + // + // This can only cause further compilation errors + // downstream in blatantly illegal code, so it + // is fine. + self.expand_enum_def(cx, enum_def, item.ident, generics, from_scratch) + } + ast::ItemKind::Union(ref struct_def, ref generics) => { + if self.supports_unions { + self.expand_struct_def( + cx, + &struct_def, + item.ident, + generics, + from_scratch, + use_temporaries, + ) + } else { + cx.span_err(mitem.span, "this trait cannot be derived for unions"); + return; + } + } + _ => unreachable!(), + }; + // Keep the lint attributes of the previous item to control how the + // generated implementations are linted + let mut attrs = newitem.attrs.clone(); + attrs.extend( + item.attrs + .iter() + .filter(|a| { + [ + sym::allow, + sym::warn, + sym::deny, + sym::forbid, + sym::stable, + sym::unstable, + ] + .contains(&a.name_or_empty()) + }) + .cloned(), + ); + push(Annotatable::Item(P(ast::Item { attrs, ..(*newitem).clone() }))) + } + _ => { + // Non-Item derive is an error, but it should have been + // set earlier; see + // librustc_expand/expand.rs:MacroExpander::fully_expand_fragment() + // librustc_expand/base.rs:Annotatable::derive_allowed() + } + } + } + + /// Given that we are deriving a trait `DerivedTrait` for a type like: + /// + /// ```ignore (only-for-syntax-highlight) + /// struct Struct<'a, ..., 'z, A, B: DeclaredTrait, C, ..., Z> where C: WhereTrait { + /// a: A, + /// b: B::Item, + /// b1: <B as DeclaredTrait>::Item, + /// c1: <C as WhereTrait>::Item, + /// c2: Option<<C as WhereTrait>::Item>, + /// ... + /// } + /// ``` + /// + /// create an impl like: + /// + /// ```ignore (only-for-syntax-highlight) + /// impl<'a, ..., 'z, A, B: DeclaredTrait, C, ... Z> where + /// C: WhereTrait, + /// A: DerivedTrait + B1 + ... + BN, + /// B: DerivedTrait + B1 + ... + BN, + /// C: DerivedTrait + B1 + ... + BN, + /// B::Item: DerivedTrait + B1 + ... + BN, + /// <C as WhereTrait>::Item: DerivedTrait + B1 + ... + BN, + /// ... + /// { + /// ... + /// } + /// ``` + /// + /// where B1, ..., BN are the bounds given by `bounds_paths`.'. Z is a phantom type, and + /// therefore does not get bound by the derived trait. + fn create_derived_impl( + &self, + cx: &mut ExtCtxt<'_>, + type_ident: Ident, + generics: &Generics, + field_tys: Vec<P<ast::Ty>>, + methods: Vec<P<ast::AssocItem>>, + ) -> P<ast::Item> { + let trait_path = self.path.to_path(cx, self.span, type_ident, generics); + + // Transform associated types from `deriving::ty::Ty` into `ast::AssocItem` + let associated_types = self.associated_types.iter().map(|&(ident, ref type_def)| { + P(ast::AssocItem { + id: ast::DUMMY_NODE_ID, + span: self.span, + ident, + vis: respan(self.span.shrink_to_lo(), ast::VisibilityKind::Inherited), + attrs: Vec::new(), + kind: ast::AssocItemKind::TyAlias( + ast::Defaultness::Final, + Generics::default(), + Vec::new(), + Some(type_def.to_ty(cx, self.span, type_ident, generics)), + ), + tokens: None, + }) + }); + + let Generics { mut params, mut where_clause, span } = + self.generics.to_generics(cx, self.span, type_ident, generics); + + // Create the generic parameters + params.extend(generics.params.iter().map(|param| match param.kind { + GenericParamKind::Lifetime { .. } => param.clone(), + GenericParamKind::Type { .. } => { + // I don't think this can be moved out of the loop, since + // a GenericBound requires an ast id + let bounds: Vec<_> = + // extra restrictions on the generics parameters to the + // type being derived upon + self.additional_bounds.iter().map(|p| { + cx.trait_bound(p.to_path(cx, self.span, type_ident, generics)) + }).chain( + // require the current trait + iter::once(cx.trait_bound(trait_path.clone())) + ).chain( + // also add in any bounds from the declaration + param.bounds.iter().cloned() + ).collect(); + + cx.typaram(self.span, param.ident, vec![], bounds, None) + } + GenericParamKind::Const { .. } => param.clone(), + })); + + // and similarly for where clauses + where_clause.predicates.extend(generics.where_clause.predicates.iter().map(|clause| { + match *clause { + ast::WherePredicate::BoundPredicate(ref wb) => { + ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate { + span: self.span, + bound_generic_params: wb.bound_generic_params.clone(), + bounded_ty: wb.bounded_ty.clone(), + bounds: wb.bounds.to_vec(), + }) + } + ast::WherePredicate::RegionPredicate(ref rb) => { + ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate { + span: self.span, + lifetime: rb.lifetime, + bounds: rb.bounds.to_vec(), + }) + } + ast::WherePredicate::EqPredicate(ref we) => { + ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { + id: ast::DUMMY_NODE_ID, + span: self.span, + lhs_ty: we.lhs_ty.clone(), + rhs_ty: we.rhs_ty.clone(), + }) + } + } + })); + + { + // Extra scope required here so ty_params goes out of scope before params is moved + + let mut ty_params = params + .iter() + .filter_map(|param| match param.kind { + ast::GenericParamKind::Type { .. } => Some(param), + _ => None, + }) + .peekable(); + + if ty_params.peek().is_some() { + let ty_param_names: Vec<Symbol> = + ty_params.map(|ty_param| ty_param.ident.name).collect(); + + for field_ty in field_tys { + let tys = find_type_parameters(&field_ty, &ty_param_names, cx); + + for ty in tys { + // if we have already handled this type, skip it + if let ast::TyKind::Path(_, ref p) = ty.kind { + if p.segments.len() == 1 + && ty_param_names.contains(&p.segments[0].ident.name) + { + continue; + }; + } + let mut bounds: Vec<_> = self + .additional_bounds + .iter() + .map(|p| cx.trait_bound(p.to_path(cx, self.span, type_ident, generics))) + .collect(); + + // require the current trait + bounds.push(cx.trait_bound(trait_path.clone())); + + let predicate = ast::WhereBoundPredicate { + span: self.span, + bound_generic_params: Vec::new(), + bounded_ty: 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(self.span, param.ident)) + } + GenericParamKind::Type { .. } => { + GenericArg::Type(cx.ty_ident(self.span, param.ident)) + } + GenericParamKind::Const { .. } => { + GenericArg::Const(cx.const_ident(self.span, param.ident)) + } + }) + .collect(); + + // Create the type of `self`. + let path = cx.path_all(self.span, false, vec![type_ident], self_params); + let self_type = cx.ty_path(path); + + let attr = cx.attribute(cx.meta_word(self.span, sym::automatically_derived)); + // Just mark it now since we know that it'll end up used downstream + cx.sess.mark_attr_used(&attr); + let opt_trait_ref = Some(trait_ref); + let unused_qual = { + let word = rustc_ast::attr::mk_nested_word_item(Ident::new( + sym::unused_qualifications, + self.span, + )); + let list = rustc_ast::attr::mk_list_item(Ident::new(sym::allow, self.span), vec![word]); + cx.attribute(list) + }; + + let mut a = vec![attr, unused_qual]; + a.extend(self.attributes.iter().cloned()); + + let unsafety = if self.is_unsafe { ast::Unsafe::Yes(self.span) } else { ast::Unsafe::No }; + + cx.item( + self.span, + Ident::invalid(), + a, + ast::ItemKind::Impl { + unsafety, + polarity: ast::ImplPolarity::Positive, + defaultness: ast::Defaultness::Final, + constness: ast::Const::No, + generics: trait_generics, + of_trait: opt_trait_ref, + self_ty: self_type, + items: methods.into_iter().chain(associated_types).collect(), + }, + ) + } + + fn expand_struct_def( + &self, + cx: &mut ExtCtxt<'_>, + struct_def: &'a VariantData, + type_ident: Ident, + generics: &Generics, + from_scratch: bool, + use_temporaries: 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, self_args, nonself_args, tys) = + method_def.split_self_nonself_args(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, + &self_args[..], + &nonself_args[..], + ) + } else { + method_def.expand_struct_method_body( + cx, + self, + struct_def, + type_ident, + &self_args[..], + &nonself_args[..], + use_temporaries, + ) + }; + + method_def.create_method(cx, self, type_ident, generics, explicit_self, tys, body) + }) + .collect(); + + self.create_derived_impl(cx, type_ident, generics, field_tys, methods) + } + + fn expand_enum_def( + &self, + cx: &mut ExtCtxt<'_>, + enum_def: &'a EnumDef, + type_ident: Ident, + generics: &Generics, + from_scratch: bool, + ) -> P<ast::Item> { + let mut field_tys = Vec::new(); + + for variant in &enum_def.variants { + field_tys.extend(variant.data.fields().iter().map(|field| field.ty.clone())); + } + + let methods = self + .methods + .iter() + .map(|method_def| { + let (explicit_self, self_args, nonself_args, tys) = + method_def.split_self_nonself_args(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, + &self_args[..], + &nonself_args[..], + ) + } else { + method_def.expand_enum_method_body( + cx, + self, + enum_def, + type_ident, + self_args, + &nonself_args[..], + ) + }; + + method_def.create_method(cx, self, type_ident, generics, explicit_self, tys, body) + }) + .collect(); + + self.create_derived_impl(cx, type_ident, generics, field_tys, methods) + } +} + +impl<'a> MethodDef<'a> { + fn call_substructure_method( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'_>, + type_ident: Ident, + self_args: &[P<Expr>], + nonself_args: &[P<Expr>], + fields: &SubstructureFields<'_>, + ) -> P<Expr> { + let substructure = Substructure { + type_ident, + method_ident: Ident::new(self.name, trait_.span), + self_args, + nonself_args, + fields, + }; + let mut f = self.combine_substructure.borrow_mut(); + let f: &mut CombineSubstructureFunc<'_> = &mut *f; + f(cx, trait_.span, &substructure) + } + + fn get_ret_ty( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'_>, + generics: &Generics, + type_ident: Ident, + ) -> P<ast::Ty> { + self.ret_ty.to_ty(cx, trait_.span, type_ident, generics) + } + + fn is_static(&self) -> bool { + self.explicit_self.is_none() + } + + fn split_self_nonself_args( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'_>, + type_ident: Ident, + generics: &Generics, + ) -> (Option<ast::ExplicitSelf>, Vec<P<Expr>>, Vec<P<Expr>>, Vec<(Ident, P<ast::Ty>)>) { + let mut self_args = Vec::new(); + let mut nonself_args = Vec::new(); + let mut arg_tys = Vec::new(); + let mut nonstatic = false; + + let ast_explicit_self = self.explicit_self.as_ref().map(|self_ptr| { + let (self_expr, explicit_self) = ty::get_explicit_self(cx, trait_.span, self_ptr); + + self_args.push(self_expr); + nonstatic = true; + + explicit_self + }); + + for (ty, name) in self.args.iter() { + let ast_ty = ty.to_ty(cx, trait_.span, type_ident, generics); + let ident = Ident::new(*name, trait_.span); + arg_tys.push((ident, ast_ty)); + + let arg_expr = cx.expr_ident(trait_.span, ident); + + match *ty { + // for static methods, just treat any Self + // arguments as a normal arg + Self_ if nonstatic => { + self_args.push(arg_expr); + } + Ptr(ref ty, _) if (if let Self_ = **ty { true } else { false }) && nonstatic => { + self_args.push(cx.expr_deref(trait_.span, arg_expr)) + } + _ => { + nonself_args.push(arg_expr); + } + } + } + + (ast_explicit_self, self_args, nonself_args, arg_tys) + } + + fn create_method( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'_>, + type_ident: Ident, + generics: &Generics, + explicit_self: Option<ast::ExplicitSelf>, + arg_types: Vec<(Ident, P<ast::Ty>)>, + body: P<Expr>, + ) -> P<ast::AssocItem> { + // Create the generics that aren't for `Self`. + let fn_generics = self.generics.to_generics(cx, trait_.span, type_ident, generics); + + let args = { + let self_args = explicit_self.map(|explicit_self| { + let ident = Ident::with_dummy_span(kw::SelfLower).with_span_pos(trait_.span); + ast::Param::from_self(ast::AttrVec::default(), explicit_self, ident) + }); + let nonself_args = + arg_types.into_iter().map(|(name, ty)| cx.param(trait_.span, name, ty)); + self_args.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, trait_.span); + let fn_decl = cx.fn_decl(args, ast::FnRetTy::Ty(ret_type)); + let body_block = cx.block_expr(body); + + let unsafety = if self.is_unsafe { ast::Unsafe::Yes(trait_.span) } else { ast::Unsafe::No }; + + let trait_lo_sp = trait_.span.shrink_to_lo(); + + let sig = ast::FnSig { + header: ast::FnHeader { unsafety, ext: ast::Extern::None, ..ast::FnHeader::default() }, + decl: fn_decl, + span: trait_.span, + }; + let def = ast::Defaultness::Final; + + // Create the method. + P(ast::AssocItem { + id: ast::DUMMY_NODE_ID, + attrs: self.attributes.clone(), + span: trait_.span, + vis: respan(trait_lo_sp, ast::VisibilityKind::Inherited), + ident: method_ident, + kind: ast::AssocItemKind::Fn(def, sig, fn_generics, Some(body_block)), + tokens: None, + }) + } + + /// ``` + /// #[derive(PartialEq)] + /// # struct Dummy; + /// struct A { x: i32, y: i32 } + /// + /// // equivalent to: + /// impl PartialEq for A { + /// fn eq(&self, other: &A) -> bool { + /// match *self { + /// A {x: ref __self_0_0, y: ref __self_0_1} => { + /// match *other { + /// A {x: ref __self_1_0, y: ref __self_1_1} => { + /// __self_0_0.eq(__self_1_0) && __self_0_1.eq(__self_1_1) + /// } + /// } + /// } + /// } + /// } + /// } + /// + /// // or if A is repr(packed) - note fields are matched by-value + /// // instead of by-reference. + /// impl PartialEq for A { + /// fn eq(&self, other: &A) -> bool { + /// match *self { + /// A {x: __self_0_0, y: __self_0_1} => { + /// match other { + /// A {x: __self_1_0, y: __self_1_1} => { + /// __self_0_0.eq(&__self_1_0) && __self_0_1.eq(&__self_1_1) + /// } + /// } + /// } + /// } + /// } + /// } + /// ``` + fn expand_struct_method_body<'b>( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'b>, + struct_def: &'b VariantData, + type_ident: Ident, + self_args: &[P<Expr>], + nonself_args: &[P<Expr>], + use_temporaries: bool, + ) -> P<Expr> { + let mut raw_fields = Vec::new(); // Vec<[fields of self], + // [fields of next Self arg], [etc]> + let mut patterns = Vec::new(); + for i in 0..self_args.len() { + let struct_path = cx.path(trait_.span, vec![type_ident]); + let (pat, ident_expr) = trait_.create_struct_pattern( + cx, + struct_path, + struct_def, + &format!("__self_{}", i), + ast::Mutability::Not, + use_temporaries, + ); + patterns.push(pat); + raw_fields.push(ident_expr); + } + + // transpose raw_fields + let fields = if !raw_fields.is_empty() { + let mut raw_fields = raw_fields.into_iter().map(|v| v.into_iter()); + let first_field = raw_fields.next().unwrap(); + let mut other_fields: Vec<vec::IntoIter<_>> = raw_fields.collect(); + first_field + .map(|(span, opt_id, field, attrs)| FieldInfo { + span, + name: opt_id, + self_: field, + other: other_fields + .iter_mut() + .map(|l| { + let (.., ex, _) = l.next().unwrap(); + ex + }) + .collect(), + attrs, + }) + .collect() + } else { + cx.span_bug(trait_.span, "no `self` parameter for method in generic `derive`") + }; + + // body of the inner most destructuring match + let mut body = self.call_substructure_method( + cx, + trait_, + type_ident, + self_args, + nonself_args, + &Struct(struct_def, fields), + ); + + // make a series of nested matches, to destructure the + // structs. This is actually right-to-left, but it shouldn't + // matter. + for (arg_expr, pat) in self_args.iter().zip(patterns) { + body = cx.expr_match( + trait_.span, + arg_expr.clone(), + vec![cx.arm(trait_.span, pat.clone(), body)], + ) + } + + body + } + + fn expand_static_struct_method_body( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'_>, + struct_def: &VariantData, + type_ident: Ident, + self_args: &[P<Expr>], + nonself_args: &[P<Expr>], + ) -> P<Expr> { + let summary = trait_.summarise_struct(cx, struct_def); + + self.call_substructure_method( + cx, + trait_, + type_ident, + self_args, + nonself_args, + &StaticStruct(struct_def, summary), + ) + } + + /// ``` + /// #[derive(PartialEq)] + /// # struct Dummy; + /// enum A { + /// A1, + /// A2(i32) + /// } + /// + /// // is equivalent to + /// + /// impl PartialEq for A { + /// fn eq(&self, other: &A) -> ::bool { + /// match (&*self, &*other) { + /// (&A1, &A1) => true, + /// (&A2(ref self_0), + /// &A2(ref __arg_1_0)) => (*self_0).eq(&(*__arg_1_0)), + /// _ => { + /// let __self_vi = match *self { A1(..) => 0, A2(..) => 1 }; + /// let __arg_1_vi = match *other { A1(..) => 0, A2(..) => 1 }; + /// false + /// } + /// } + /// } + /// } + /// ``` + /// + /// (Of course `__self_vi` and `__arg_1_vi` are unused for + /// `PartialEq`, and those subcomputations will hopefully be removed + /// as their results are unused. The point of `__self_vi` and + /// `__arg_1_vi` is for `PartialOrd`; see #15503.) + fn expand_enum_method_body<'b>( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'b>, + enum_def: &'b EnumDef, + type_ident: Ident, + self_args: Vec<P<Expr>>, + nonself_args: &[P<Expr>], + ) -> P<Expr> { + self.build_enum_match_tuple(cx, trait_, enum_def, type_ident, self_args, nonself_args) + } + + /// Creates a match for a tuple of all `self_args`, where either all + /// variants match, or it falls into a catch-all for when one variant + /// does not match. + + /// There are N + 1 cases because is a case for each of the N + /// variants where all of the variants match, and one catch-all for + /// when one does not match. + + /// As an optimization we generate code which checks whether all variants + /// match first which makes llvm see that C-like enums can be compiled into + /// a simple equality check (for PartialEq). + + /// The catch-all handler is provided access the variant index values + /// for each of the self-args, carried in precomputed variables. + + /// ```{.text} + /// let __self0_vi = unsafe { + /// std::intrinsics::discriminant_value(&self) }; + /// let __self1_vi = unsafe { + /// std::intrinsics::discriminant_value(&arg1) }; + /// let __self2_vi = unsafe { + /// std::intrinsics::discriminant_value(&arg2) }; + /// + /// if __self0_vi == __self1_vi && __self0_vi == __self2_vi && ... { + /// match (...) { + /// (Variant1, Variant1, ...) => Body1 + /// (Variant2, Variant2, ...) => Body2, + /// ... + /// _ => ::core::intrinsics::unreachable() + /// } + /// } + /// else { + /// ... // catch-all remainder can inspect above variant index values. + /// } + /// ``` + fn build_enum_match_tuple<'b>( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'b>, + enum_def: &'b EnumDef, + type_ident: Ident, + mut self_args: Vec<P<Expr>>, + nonself_args: &[P<Expr>], + ) -> P<Expr> { + let sp = trait_.span; + let variants = &enum_def.variants; + + let self_arg_names = iter::once("__self".to_string()) + .chain( + self_args + .iter() + .enumerate() + .skip(1) + .map(|(arg_count, _self_arg)| format!("__arg_{}", arg_count)), + ) + .collect::<Vec<String>>(); + + let self_arg_idents = self_arg_names + .iter() + .map(|name| Ident::from_str_and_span(name, sp)) + .collect::<Vec<Ident>>(); + + // The `vi_idents` will be bound, solely in the catch-all, to + // a series of let statements mapping each self_arg to an int + // value corresponding to its discriminant. + let vi_idents = self_arg_names + .iter() + .map(|name| { + let vi_suffix = format!("{}_vi", &name[..]); + Ident::from_str_and_span(&vi_suffix, trait_.span) + }) + .collect::<Vec<Ident>>(); + + // Builds, via callback to call_substructure_method, the + // delegated expression that handles the catch-all case, + // using `__variants_tuple` to drive logic if necessary. + let catch_all_substructure = + EnumNonMatchingCollapsed(self_arg_idents, &variants[..], &vi_idents[..]); + + let first_fieldless = variants.iter().find(|v| v.data.fields().is_empty()); + + // These arms are of the form: + // (Variant1, Variant1, ...) => Body1 + // (Variant2, Variant2, ...) => Body2 + // ... + // where each tuple has length = self_args.len() + let mut match_arms: Vec<ast::Arm> = variants + .iter() + .enumerate() + .filter(|&(_, v)| !(self.unify_fieldless_variants && v.data.fields().is_empty())) + .map(|(index, variant)| { + let mk_self_pat = |cx: &mut ExtCtxt<'_>, self_arg_name: &str| { + let (p, idents) = trait_.create_enum_variant_pattern( + cx, + type_ident, + variant, + self_arg_name, + ast::Mutability::Not, + ); + (cx.pat(sp, PatKind::Ref(p, ast::Mutability::Not)), idents) + }; + + // A single arm has form (&VariantK, &VariantK, ...) => BodyK + // (see "Final wrinkle" note below for why.) + let mut subpats = Vec::with_capacity(self_arg_names.len()); + let mut self_pats_idents = Vec::with_capacity(self_arg_names.len() - 1); + let first_self_pat_idents = { + let (p, idents) = mk_self_pat(cx, &self_arg_names[0]); + subpats.push(p); + idents + }; + for self_arg_name in &self_arg_names[1..] { + let (p, idents) = mk_self_pat(cx, &self_arg_name[..]); + subpats.push(p); + self_pats_idents.push(idents); + } + + // Here is the pat = `(&VariantK, &VariantK, ...)` + let single_pat = cx.pat_tuple(sp, subpats); + + // For the BodyK, we need to delegate to our caller, + // passing it an EnumMatching to indicate which case + // we are in. + + // All of the Self args have the same variant in these + // cases. So we transpose the info in self_pats_idents + // to gather the getter expressions together, in the + // form that EnumMatching expects. + + // The transposition is driven by walking across the + // arg fields of the variant for the first self pat. + let field_tuples = first_self_pat_idents + .into_iter() + .enumerate() + // For each arg field of self, pull out its getter expr ... + .map(|(field_index, (sp, opt_ident, self_getter_expr, attrs))| { + // ... but FieldInfo also wants getter expr + // for matching other arguments of Self type; + // so walk across the *other* self_pats_idents + // and pull out getter for same field in each + // of them (using `field_index` tracked above). + // That is the heart of the transposition. + let others = self_pats_idents + .iter() + .map(|fields| { + let (_, _opt_ident, ref other_getter_expr, _) = fields[field_index]; + + // All Self args have same variant, so + // opt_idents are the same. (Assert + // here to make it self-evident that + // it is okay to ignore `_opt_ident`.) + assert!(opt_ident == _opt_ident); + + other_getter_expr.clone() + }) + .collect::<Vec<P<Expr>>>(); + + FieldInfo { + span: sp, + name: opt_ident, + self_: self_getter_expr, + other: others, + attrs, + } + }) + .collect::<Vec<FieldInfo<'_>>>(); + + // Now, for some given VariantK, we have built up + // expressions for referencing every field of every + // Self arg, assuming all are instances of VariantK. + // Build up code associated with such a case. + let substructure = EnumMatching(index, variants.len(), variant, field_tuples); + let arm_expr = self.call_substructure_method( + cx, + trait_, + type_ident, + &self_args[..], + nonself_args, + &substructure, + ); + + cx.arm(sp, single_pat, arm_expr) + }) + .collect(); + + let default = match first_fieldless { + Some(v) if self.unify_fieldless_variants => { + // We need a default case that handles the fieldless variants. + // The index and actual variant aren't meaningful in this case, + // so just use whatever + let substructure = EnumMatching(0, variants.len(), v, Vec::new()); + Some(self.call_substructure_method( + cx, + trait_, + type_ident, + &self_args[..], + nonself_args, + &substructure, + )) + } + _ if variants.len() > 1 && self_args.len() > 1 => { + // Since we know that all the arguments will match if we reach + // the match expression we add the unreachable intrinsics as the + // result of the catch all which should help llvm in optimizing it + Some(deriving::call_intrinsic(cx, sp, sym::unreachable, vec![])) + } + _ => None, + }; + if let Some(arm) = default { + match_arms.push(cx.arm(sp, cx.pat_wild(sp), arm)); + } + + // We will usually need the catch-all after matching the + // tuples `(VariantK, VariantK, ...)` for each VariantK of the + // enum. But: + // + // * when there is only one Self arg, the arms above suffice + // (and the deriving we call back into may not be prepared to + // handle EnumNonMatchCollapsed), and, + // + // * when the enum has only one variant, the single arm that + // is already present always suffices. + // + // * In either of the two cases above, if we *did* add a + // catch-all `_` match, it would trigger the + // unreachable-pattern error. + // + if variants.len() > 1 && self_args.len() > 1 { + // Build a series of let statements mapping each self_arg + // to its discriminant value. + // + // i.e., for `enum E<T> { A, B(1), C(T, T) }`, and a deriving + // with three Self args, builds three statements: + // + // ``` + // let __self0_vi = unsafe { + // std::intrinsics::discriminant_value(&self) }; + // let __self1_vi = unsafe { + // std::intrinsics::discriminant_value(&arg1) }; + // let __self2_vi = unsafe { + // std::intrinsics::discriminant_value(&arg2) }; + // ``` + let mut index_let_stmts: Vec<ast::Stmt> = Vec::with_capacity(vi_idents.len() + 1); + + // We also build an expression which checks whether all discriminants are equal + // discriminant_test = __self0_vi == __self1_vi && __self0_vi == __self2_vi && ... + let mut discriminant_test = cx.expr_bool(sp, true); + + let mut first_ident = None; + for (&ident, self_arg) in vi_idents.iter().zip(&self_args) { + let self_addr = cx.expr_addr_of(sp, self_arg.clone()); + let variant_value = + deriving::call_intrinsic(cx, sp, sym::discriminant_value, vec![self_addr]); + let let_stmt = cx.stmt_let(sp, false, ident, variant_value); + index_let_stmts.push(let_stmt); + + match first_ident { + Some(first) => { + let first_expr = cx.expr_ident(sp, first); + let id = cx.expr_ident(sp, ident); + let test = cx.expr_binary(sp, BinOpKind::Eq, first_expr, id); + discriminant_test = + cx.expr_binary(sp, BinOpKind::And, discriminant_test, test) + } + None => { + first_ident = Some(ident); + } + } + } + + let arm_expr = self.call_substructure_method( + cx, + trait_, + type_ident, + &self_args[..], + nonself_args, + &catch_all_substructure, + ); + + // Final wrinkle: the self_args are expressions that deref + // down to desired places, but we cannot actually deref + // them when they are fed as r-values into a tuple + // expression; here add a layer of borrowing, turning + // `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`. + self_args.map_in_place(|self_arg| cx.expr_addr_of(sp, self_arg)); + let match_arg = cx.expr(sp, ast::ExprKind::Tup(self_args)); + + // Lastly we create an expression which branches on all discriminants being equal + // if discriminant_test { + // match (...) { + // (Variant1, Variant1, ...) => Body1 + // (Variant2, Variant2, ...) => Body2, + // ... + // _ => ::core::intrinsics::unreachable() + // } + // } + // else { + // <delegated expression referring to __self0_vi, et al.> + // } + let all_match = cx.expr_match(sp, match_arg, match_arms); + let arm_expr = cx.expr_if(sp, discriminant_test, all_match, Some(arm_expr)); + index_let_stmts.push(cx.stmt_expr(arm_expr)); + cx.expr_block(cx.block(sp, index_let_stmts)) + } else if variants.is_empty() { + // As an additional wrinkle, For a zero-variant enum A, + // currently the compiler + // will accept `fn (a: &Self) { match *a { } }` + // but rejects `fn (a: &Self) { match (&*a,) { } }` + // as well as `fn (a: &Self) { match ( *a,) { } }` + // + // This means that the strategy of building up a tuple of + // all Self arguments fails when Self is a zero variant + // enum: rustc rejects the expanded program, even though + // the actual code tends to be impossible to execute (at + // least safely), according to the type system. + // + // The most expedient fix for this is to just let the + // code fall through to the catch-all. But even this is + // error-prone, since the catch-all as defined above would + // generate code like this: + // + // _ => { let __self0 = match *self { }; + // let __self1 = match *__arg_0 { }; + // <catch-all-expr> } + // + // Which is yields bindings for variables which type + // inference cannot resolve to unique types. + // + // One option to the above might be to add explicit type + // annotations. But the *only* reason to go down that path + // would be to try to make the expanded output consistent + // with the case when the number of enum variants >= 1. + // + // That just isn't worth it. In fact, trying to generate + // sensible code for *any* deriving on a zero-variant enum + // does not make sense. But at the same time, for now, we + // do not want to cause a compile failure just because the + // user happened to attach a deriving to their + // zero-variant enum. + // + // Instead, just generate a failing expression for the + // zero variant case, skipping matches and also skipping + // delegating back to the end user code entirely. + // + // (See also #4499 and #12609; note that some of the + // discussions there influence what choice we make here; + // e.g., if we feature-gate `match x { ... }` when x refers + // to an uninhabited type (e.g., a zero-variant enum or a + // type holding such an enum), but do not feature-gate + // zero-variant enums themselves, then attempting to + // derive Debug on such a type could here generate code + // that needs the feature gate enabled.) + + deriving::call_intrinsic(cx, sp, sym::unreachable, vec![]) + } else { + // Final wrinkle: the self_args are expressions that deref + // down to desired places, but we cannot actually deref + // them when they are fed as r-values into a tuple + // expression; here add a layer of borrowing, turning + // `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`. + self_args.map_in_place(|self_arg| cx.expr_addr_of(sp, self_arg)); + let match_arg = cx.expr(sp, ast::ExprKind::Tup(self_args)); + cx.expr_match(sp, match_arg, match_arms) + } + } + + fn expand_static_enum_method_body( + &self, + cx: &mut ExtCtxt<'_>, + trait_: &TraitDef<'_>, + enum_def: &EnumDef, + type_ident: Ident, + self_args: &[P<Expr>], + nonself_args: &[P<Expr>], + ) -> P<Expr> { + 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, + self_args, + nonself_args, + &StaticEnum(enum_def, summary), + ) + } +} + +// general helper methods. +impl<'a> TraitDef<'a> { + fn summarise_struct(&self, cx: &mut ExtCtxt<'_>, struct_def: &VariantData) -> StaticFields { + let mut named_idents = Vec::new(); + let mut just_spans = Vec::new(); + for field in struct_def.fields() { + let sp = field.span.with_ctxt(self.span.ctxt()); + match field.ident { + Some(ident) => named_idents.push((ident, sp)), + _ => just_spans.push(sp), + } + } + + let is_tuple = if let ast::VariantData::Tuple(..) = struct_def { true } else { false }; + match (just_spans.is_empty(), named_idents.is_empty()) { + (false, false) => cx.span_bug( + self.span, + "a struct with named and unnamed \ + fields in generic `derive`", + ), + // named fields + (_, false) => Named(named_idents), + // unnamed fields + (false, _) => Unnamed(just_spans, is_tuple), + // empty + _ => Named(Vec::new()), + } + } + + fn create_subpatterns( + &self, + cx: &mut ExtCtxt<'_>, + field_paths: Vec<Ident>, + mutbl: ast::Mutability, + use_temporaries: bool, + ) -> Vec<P<ast::Pat>> { + field_paths + .iter() + .map(|path| { + let binding_mode = if use_temporaries { + ast::BindingMode::ByValue(ast::Mutability::Not) + } else { + ast::BindingMode::ByRef(mutbl) + }; + cx.pat(path.span, PatKind::Ident(binding_mode, *path, None)) + }) + .collect() + } + + fn create_struct_pattern( + &self, + cx: &mut ExtCtxt<'_>, + struct_path: ast::Path, + struct_def: &'a VariantData, + prefix: &str, + mutbl: ast::Mutability, + use_temporaries: bool, + ) -> (P<ast::Pat>, Vec<(Span, Option<Ident>, P<Expr>, &'a [ast::Attribute])>) { + let mut paths = Vec::new(); + let mut ident_exprs = Vec::new(); + for (i, struct_field) in struct_def.fields().iter().enumerate() { + let sp = struct_field.span.with_ctxt(self.span.ctxt()); + let ident = Ident::from_str_and_span(&format!("{}_{}", prefix, i), self.span); + paths.push(ident.with_span_pos(sp)); + let val = cx.expr_path(cx.path_ident(sp, ident)); + let val = if use_temporaries { val } else { cx.expr_deref(sp, val) }; + let val = cx.expr(sp, ast::ExprKind::Paren(val)); + + ident_exprs.push((sp, struct_field.ident, val, &struct_field.attrs[..])); + } + + let subpats = self.create_subpatterns(cx, paths, mutbl, use_temporaries); + let pattern = match *struct_def { + VariantData::Struct(..) => { + let field_pats = subpats + .into_iter() + .zip(&ident_exprs) + .map(|(pat, &(sp, ident, ..))| { + if ident.is_none() { + cx.span_bug(sp, "a braced struct with unnamed fields in `derive`"); + } + ast::FieldPat { + 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(..) => cx.pat_tuple_struct(self.span, struct_path, subpats), + VariantData::Unit(..) => cx.pat_path(self.span, struct_path), + }; + + (pattern, ident_exprs) + } + + fn create_enum_variant_pattern( + &self, + cx: &mut ExtCtxt<'_>, + enum_ident: Ident, + variant: &'a ast::Variant, + prefix: &str, + mutbl: ast::Mutability, + ) -> (P<ast::Pat>, Vec<(Span, Option<Ident>, P<Expr>, &'a [ast::Attribute])>) { + let sp = variant.span.with_ctxt(self.span.ctxt()); + let variant_path = cx.path(sp, vec![enum_ident, variant.ident]); + let use_temporaries = false; // enums can't be repr(packed) + self.create_struct_pattern(cx, variant_path, &variant.data, prefix, mutbl, use_temporaries) + } +} + +// helpful premade recipes + +pub fn cs_fold_fields<'a, F>( + use_foldl: bool, + mut f: F, + base: P<Expr>, + cx: &mut ExtCtxt<'_>, + all_fields: &[FieldInfo<'a>], +) -> P<Expr> +where + F: FnMut(&mut ExtCtxt<'_>, Span, P<Expr>, P<Expr>, &[P<Expr>]) -> P<Expr>, +{ + if use_foldl { + all_fields + .iter() + .fold(base, |old, field| f(cx, field.span, old, field.self_.clone(), &field.other)) + } else { + all_fields + .iter() + .rev() + .fold(base, |old, field| f(cx, field.span, old, field.self_.clone(), &field.other)) + } +} + +pub fn cs_fold_enumnonmatch( + mut enum_nonmatch_f: EnumNonMatchCollapsedFunc<'_>, + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substructure: &Substructure<'_>, +) -> P<Expr> { + match *substructure.fields { + EnumNonMatchingCollapsed(ref all_args, _, tuple) => { + enum_nonmatch_f(cx, trait_span, (&all_args[..], tuple), substructure.nonself_args) + } + _ => cx.span_bug(trait_span, "cs_fold_enumnonmatch expected an EnumNonMatchingCollapsed"), + } +} + +pub fn cs_fold_static(cx: &mut ExtCtxt<'_>, trait_span: Span) -> P<Expr> { + cx.span_bug(trait_span, "static function in `derive`") +} + +/// Fold the fields. `use_foldl` controls whether this is done +/// left-to-right (`true`) or right-to-left (`false`). +pub fn cs_fold<F>( + use_foldl: bool, + f: F, + base: P<Expr>, + enum_nonmatch_f: EnumNonMatchCollapsedFunc<'_>, + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substructure: &Substructure<'_>, +) -> P<Expr> +where + F: FnMut(&mut ExtCtxt<'_>, Span, P<Expr>, P<Expr>, &[P<Expr>]) -> P<Expr>, +{ + match *substructure.fields { + EnumMatching(.., ref all_fields) | Struct(_, ref all_fields) => { + cs_fold_fields(use_foldl, f, base, cx, all_fields) + } + EnumNonMatchingCollapsed(..) => { + cs_fold_enumnonmatch(enum_nonmatch_f, cx, trait_span, substructure) + } + StaticEnum(..) | StaticStruct(..) => cs_fold_static(cx, trait_span), + } +} + +/// Function to fold over fields, with three cases, to generate more efficient and concise code. +/// When the `substructure` has grouped fields, there are two cases: +/// Zero fields: call the base case function with `None` (like the usual base case of `cs_fold`). +/// One or more fields: call the base case function on the first value (which depends on +/// `use_fold`), and use that as the base case. Then perform `cs_fold` on the remainder of the +/// fields. +/// When the `substructure` is a `EnumNonMatchingCollapsed`, the result of `enum_nonmatch_f` +/// is returned. Statics may not be folded over. +/// See `cs_op` in `partial_ord.rs` for a model example. +pub fn cs_fold1<F, B>( + use_foldl: bool, + f: F, + mut b: B, + enum_nonmatch_f: EnumNonMatchCollapsedFunc<'_>, + cx: &mut ExtCtxt<'_>, + trait_span: Span, + substructure: &Substructure<'_>, +) -> P<Expr> +where + F: FnMut(&mut ExtCtxt<'_>, Span, P<Expr>, P<Expr>, &[P<Expr>]) -> P<Expr>, + B: FnMut(&mut ExtCtxt<'_>, Option<(Span, P<Expr>, &[P<Expr>])>) -> P<Expr>, +{ + match *substructure.fields { + EnumMatching(.., ref all_fields) | Struct(_, ref all_fields) => { + let (base, all_fields) = match (all_fields.is_empty(), use_foldl) { + (false, true) => { + let field = &all_fields[0]; + let args = (field.span, field.self_.clone(), &field.other[..]); + (b(cx, Some(args)), &all_fields[1..]) + } + (false, false) => { + let idx = all_fields.len() - 1; + let field = &all_fields[idx]; + let args = (field.span, field.self_.clone(), &field.other[..]); + (b(cx, Some(args)), &all_fields[..idx]) + } + (true, _) => (b(cx, None), &all_fields[..]), + }; + + cs_fold_fields(use_foldl, f, base, cx, all_fields) + } + EnumNonMatchingCollapsed(..) => { + cs_fold_enumnonmatch(enum_nonmatch_f, cx, trait_span, substructure) + } + StaticEnum(..) | StaticStruct(..) => cs_fold_static(cx, trait_span), + } +} + +/// Returns `true` if the type has no value fields +/// (for an enum, no variant has any fields) +pub fn is_type_without_fields(item: &Annotatable) -> bool { + if let Annotatable::Item(ref item) = *item { + match item.kind { + ast::ItemKind::Enum(ref enum_def, _) => { + enum_def.variants.iter().all(|v| v.data.fields().is_empty()) + } + ast::ItemKind::Struct(ref variant_data, _) => variant_data.fields().is_empty(), + _ => false, + } + } else { + false + } +} diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs new file mode 100644 index 00000000000..6b7d0e1f204 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs @@ -0,0 +1,280 @@ +//! A mini version of ast::Ty, which is easier to use, and features an explicit `Self` type to use +//! when specifying impls to be derived. + +pub use PtrTy::*; +pub use Ty::*; + +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind}; +use rustc_expand::base::ExtCtxt; +use rustc_span::source_map::{respan, DUMMY_SP}; +use rustc_span::symbol::{kw, Ident, Symbol}; +use rustc_span::Span; + +/// The types of pointers +#[derive(Clone)] +pub enum PtrTy { + /// &'lifetime mut + Borrowed(Option<Ident>, ast::Mutability), + /// *mut + #[allow(dead_code)] + Raw(ast::Mutability), +} + +/// A path, e.g., `::std::option::Option::<i32>` (global). Has support +/// for type parameters and a lifetime. +#[derive(Clone)] +pub struct Path { + path: Vec<Symbol>, + lifetime: Option<Ident>, + params: Vec<Box<Ty>>, + kind: PathKind, +} + +#[derive(Clone)] +pub enum PathKind { + Local, + Global, + Std, +} + +impl Path { + pub fn new(path: Vec<Symbol>) -> Path { + Path::new_(path, None, Vec::new(), PathKind::Std) + } + pub fn new_local(path: Symbol) -> Path { + Path::new_(vec![path], None, Vec::new(), PathKind::Local) + } + pub fn new_( + path: Vec<Symbol>, + lifetime: Option<Ident>, + params: Vec<Box<Ty>>, + kind: PathKind, + ) -> Path { + Path { path, lifetime, 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 lt = mk_lifetimes(cx, span, &self.lifetime); + let tys: Vec<P<ast::Ty>> = + self.params.iter().map(|t| t.to_ty(cx, span, self_ty, self_generics)).collect(); + let params = lt + .into_iter() + .map(GenericArg::Lifetime) + .chain(tys.into_iter().map(GenericArg::Type)) + .collect(); + + match self.kind { + PathKind::Global => cx.path_all(span, true, idents, params), + PathKind::Local => cx.path_all(span, false, idents, params), + PathKind::Std => { + let def_site = cx.with_def_site_ctxt(DUMMY_SP); + idents.insert(0, Ident::new(kw::DollarCrate, def_site)); + cx.path_all(span, false, idents, params) + } + } + } +} + +/// A type. Supports pointers, Self, and literals. +#[derive(Clone)] +pub enum Ty { + Self_, + /// &/Box/ Ty + Ptr(Box<Ty>, PtrTy), + /// `mod::mod::Type<[lifetime], [Params...]>`, including a plain type + /// parameter, and things like `i32` + Literal(Path), + /// includes unit + Tuple(Vec<Ty>), +} + +pub fn borrowed_ptrty() -> PtrTy { + Borrowed(None, ast::Mutability::Not) +} +pub fn borrowed(ty: Box<Ty>) -> Ty { + Ptr(ty, borrowed_ptrty()) +} + +pub fn borrowed_explicit_self() -> Option<Option<PtrTy>> { + Some(Some(borrowed_ptrty())) +} + +pub fn borrowed_self() -> Ty { + borrowed(Box::new(Self_)) +} + +pub fn nil_ty() -> Ty { + Tuple(Vec::new()) +} + +fn mk_lifetime(cx: &ExtCtxt<'_>, span: Span, lt: &Option<Ident>) -> Option<ast::Lifetime> { + lt.map(|ident| cx.lifetime(span, ident)) +} + +fn mk_lifetimes(cx: &ExtCtxt<'_>, span: Span, lt: &Option<Ident>) -> Vec<ast::Lifetime> { + mk_lifetime(cx, span, lt).into_iter().collect() +} + +impl Ty { + pub fn to_ty( + &self, + cx: &ExtCtxt<'_>, + span: Span, + self_ty: Ident, + self_generics: &Generics, + ) -> P<ast::Ty> { + match *self { + Ptr(ref ty, ref ptr) => { + let raw_ty = ty.to_ty(cx, span, self_ty, self_generics); + match *ptr { + Borrowed(ref lt, mutbl) => { + let lt = mk_lifetime(cx, span, lt); + cx.ty_rptr(span, raw_ty, lt, mutbl) + } + Raw(mutbl) => cx.ty_ptr(span, raw_ty, mutbl), + } + } + Literal(ref p) => p.to_ty(cx, span, self_ty, self_generics), + Self_ => cx.ty_path(self.to_path(cx, span, self_ty, self_generics)), + Tuple(ref fields) => { + let ty = ast::TyKind::Tup( + fields.iter().map(|f| f.to_ty(cx, span, self_ty, self_generics)).collect(), + ); + 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) + } + Literal(ref p) => p.to_path(cx, span, self_ty, generics), + Ptr(..) => cx.span_bug(span, "pointer in a path in generic `derive`"), + Tuple(..) => cx.span_bug(span, "tuple in a path in generic `derive`"), + } + } +} + +fn mk_ty_param( + cx: &ExtCtxt<'_>, + span: Span, + name: Symbol, + attrs: &[ast::Attribute], + bounds: &[Path], + self_ident: Ident, + self_generics: &Generics, +) -> ast::GenericParam { + let bounds = bounds + .iter() + .map(|b| { + let path = b.to_path(cx, span, self_ident, self_generics); + cx.trait_bound(path) + }) + .collect(); + cx.typaram(span, Ident::new(name, span), attrs.to_owned(), bounds, None) +} + +fn mk_generics(params: Vec<ast::GenericParam>, span: Span) -> Generics { + Generics { + params, + where_clause: ast::WhereClause { has_where_token: false, predicates: Vec::new(), span }, + span, + } +} + +/// Bounds on type parameters. +#[derive(Clone)] +pub struct Bounds { + pub bounds: Vec<(Symbol, Vec<Path>)>, +} + +impl Bounds { + pub fn empty() -> Bounds { + Bounds { bounds: Vec::new() } + } + pub fn to_generics( + &self, + cx: &ExtCtxt<'_>, + span: Span, + self_ty: Ident, + self_generics: &Generics, + ) -> Generics { + let generic_params = self + .bounds + .iter() + .map(|t| { + let (name, ref bounds) = *t; + mk_ty_param(cx, span, name, &[], &bounds, self_ty, self_generics) + }) + .collect(); + + mk_generics(generic_params, span) + } +} + +pub fn get_explicit_self( + cx: &ExtCtxt<'_>, + span: Span, + self_ptr: &Option<PtrTy>, +) -> (P<Expr>, ast::ExplicitSelf) { + // this constructs a fresh `self` path + let self_path = cx.expr_self(span); + match *self_ptr { + None => (self_path, respan(span, SelfKind::Value(ast::Mutability::Not))), + Some(ref ptr) => { + let self_ty = respan( + span, + match *ptr { + Borrowed(ref lt, mutbl) => { + let lt = lt.map(|s| cx.lifetime(span, s)); + SelfKind::Region(lt, mutbl) + } + Raw(_) => cx.span_bug(span, "attempted to use *self in deriving definition"), + }, + ); + let self_expr = cx.expr_deref(span, self_path); + (self_expr, 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..868f863b990 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/hash.rs @@ -0,0 +1,89 @@ +use crate::deriving::generic::ty::*; +use crate::deriving::generic::*; +use crate::deriving::{self, path_std, pathvec_std}; + +use rustc_ast::ptr::P; +use rustc_ast::{Expr, MetaItem, Mutability}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::sym; +use rustc_span::Span; + +pub fn expand_deriving_hash( + cx: &mut ExtCtxt<'_>, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), +) { + let path = Path::new_(pathvec_std!(hash::Hash), None, vec![], PathKind::Std); + + let typaram = sym::__H; + + let arg = Path::new_local(typaram); + let hash_trait_def = TraitDef { + span, + attributes: Vec::new(), + path, + additional_bounds: Vec::new(), + generics: Bounds::empty(), + is_unsafe: false, + supports_unions: false, + methods: vec![MethodDef { + name: sym::hash, + generics: Bounds { bounds: vec![(typaram, vec![path_std!(hash::Hasher)])] }, + explicit_self: borrowed_explicit_self(), + args: vec![(Ptr(Box::new(Literal(arg)), Borrowed(None, Mutability::Mut)), sym::state)], + ret_ty: nil_ty(), + attributes: vec![], + is_unsafe: false, + unify_fieldless_variants: true, + combine_substructure: combine_substructure(Box::new(|a, b, c| { + hash_substructure(a, b, c) + })), + }], + associated_types: Vec::new(), + }; + + hash_trait_def.expand(cx, mitem, item, push); +} + +fn hash_substructure(cx: &mut ExtCtxt<'_>, trait_span: Span, substr: &Substructure<'_>) -> P<Expr> { + let state_expr = match &substr.nonself_args { + &[o_f] => o_f, + _ => cx.span_bug(trait_span, "incorrect number of arguments in `derive(Hash)`"), + }; + let call_hash = |span, thing_expr| { + let hash_path = { + let strs = cx.std_path(&[sym::hash, sym::Hash, sym::hash]); + + cx.expr_path(cx.path_global(span, strs)) + }; + let ref_thing = cx.expr_addr_of(span, thing_expr); + let expr = cx.expr_call(span, hash_path, vec![ref_thing, state_expr.clone()]); + cx.stmt_expr(expr) + }; + let mut stmts = Vec::new(); + + let fields = match *substr.fields { + Struct(_, ref fs) | EnumMatching(_, 1, .., ref fs) => fs, + EnumMatching(.., ref fs) => { + let variant_value = deriving::call_intrinsic( + cx, + trait_span, + sym::discriminant_value, + vec![cx.expr_self(trait_span)], + ); + + stmts.push(call_hash(trait_span, variant_value)); + + fs + } + _ => cx.span_bug(trait_span, "impossible substructure in `derive(Hash)`"), + }; + + stmts.extend( + fields.iter().map(|FieldInfo { ref self_, span, .. }| call_hash(*span, self_.clone())), + ); + + cx.expr_block(cx.block(trait_span, stmts)) +} 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..7e3fd131d44 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs @@ -0,0 +1,173 @@ +//! The compiler code necessary to implement the `#[derive]` extensions. + +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::{ItemKind, MetaItem}; +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier}; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; + +macro path_local($x:ident) { + generic::ty::Path::new_local(sym::$x) +} + +macro pathvec_std($($rest:ident)::+) {{ + vec![ $( sym::$rest ),+ ] +}} + +macro path_std($($x:tt)*) { + generic::ty::Path::new( pathvec_std!( $($x)* ) ) +} + +pub mod bounds; +pub mod clone; +pub mod debug; +pub mod decodable; +pub mod default; +pub mod encodable; +pub mod hash; + +#[path = "cmp/eq.rs"] +pub mod eq; +#[path = "cmp/ord.rs"] +pub mod ord; +#[path = "cmp/partial_eq.rs"] +pub mod partial_eq; +#[path = "cmp/partial_ord.rs"] +pub mod partial_ord; + +pub mod generic; + +crate struct BuiltinDerive( + crate fn(&mut ExtCtxt<'_>, Span, &MetaItem, &Annotatable, &mut dyn FnMut(Annotatable)), +); + +impl MultiItemModifier for BuiltinDerive { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &MetaItem, + item: Annotatable, + ) -> ExpandResult<Vec<Annotatable>, Annotatable> { + // FIXME: Built-in derives often forget to give spans contexts, + // so we are doing it here in a centralized way. + let span = ecx.with_def_site_ctxt(span); + let mut items = Vec::new(); + (self.0)(ecx, span, meta_item, &item, &mut |a| items.push(a)); + ExpandResult::Ready(items) + } +} + +/// Constructs an expression that calls an intrinsic +fn call_intrinsic( + cx: &ExtCtxt<'_>, + span: Span, + intrinsic: Symbol, + args: Vec<P<ast::Expr>>, +) -> P<ast::Expr> { + let span = cx.with_def_site_ctxt(span); + let path = cx.std_path(&[sym::intrinsics, intrinsic]); + let call = cx.expr_call_global(span, path, args); + + cx.expr_block(P(ast::Block { + stmts: vec![cx.stmt_expr(call)], + id: ast::DUMMY_NODE_ID, + rules: ast::BlockCheckMode::Unsafe(ast::CompilerGenerated), + span, + })) +} + +// Injects `impl<...> Structural for ItemType<...> { }`. In particular, +// does *not* add `where T: Structural` for parameters `T` in `...`. +// (That's the main reason we cannot use TraitDef here.) +fn inject_impl_of_structural_trait( + cx: &mut ExtCtxt<'_>, + span: Span, + item: &Annotatable, + structural_path: generic::ty::Path, + push: &mut dyn FnMut(Annotatable), +) { + let item = match *item { + Annotatable::Item(ref item) => item, + _ => { + // Non-Item derive is an error, but it should have been + // set earlier; see + // librustc_expand/expand.rs:MacroExpander::fully_expand_fragment() + // librustc_expand/base.rs:Annotatable::derive_allowed() + return; + } + }; + + let generics = match item.kind { + ItemKind::Struct(_, ref generics) | ItemKind::Enum(_, ref generics) => generics, + // Do not inject `impl Structural for Union`. (`PartialEq` does not + // support unions, so we will see error downstream.) + ItemKind::Union(..) => return, + _ => unreachable!(), + }; + + // Create generics param list for where clauses and impl headers + let mut generics = generics.clone(); + + // Create the type of `self`. + // + // in addition, remove defaults from type params (impls cannot have them). + let self_params: Vec<_> = generics + .params + .iter_mut() + .map(|param| match &mut param.kind { + ast::GenericParamKind::Lifetime => { + ast::GenericArg::Lifetime(cx.lifetime(span, param.ident)) + } + ast::GenericParamKind::Type { default } => { + *default = None; + ast::GenericArg::Type(cx.ty_ident(span, param.ident)) + } + ast::GenericParamKind::Const { ty: _, kw_span: _ } => { + ast::GenericArg::Const(cx.const_ident(span, param.ident)) + } + }) + .collect(); + + let type_ident = item.ident; + + let trait_ref = cx.trait_ref(structural_path.to_path(cx, span, type_ident, &generics)); + let self_type = cx.ty_path(cx.path_all(span, false, vec![type_ident], self_params)); + + // It would be nice to also encode constraint `where Self: Eq` (by adding it + // onto `generics` cloned above). Unfortunately, that strategy runs afoul of + // rust-lang/rust#48214. So we perform that additional check in the compiler + // itself, instead of encoding it here. + + // Keep the lint and stability attributes of the original item, to control + // how the generated implementation is linted. + let mut attrs = Vec::new(); + attrs.extend( + item.attrs + .iter() + .filter(|a| { + [sym::allow, sym::warn, sym::deny, sym::forbid, sym::stable, sym::unstable] + .contains(&a.name_or_empty()) + }) + .cloned(), + ); + + let newitem = cx.item( + span, + Ident::invalid(), + attrs, + ItemKind::Impl { + unsafety: ast::Unsafe::No, + polarity: ast::ImplPolarity::Positive, + defaultness: ast::Defaultness::Final, + constness: ast::Const::No, + generics, + of_trait: Some(trait_ref), + self_ty: self_type, + items: Vec::new(), + }, + ); + + push(Annotatable::Item(newitem)); +} diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs new file mode 100644 index 00000000000..6de12acfb94 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -0,0 +1,93 @@ +// The compiler code necessary to support the env! extension. Eventually this +// should all get sucked into either the compiler syntax extension plugin +// interface. +// + +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{self as ast, GenericArg}; +use rustc_expand::base::{self, *}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::Span; + +use std::env; + +pub fn expand_option_env<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + let var = match get_single_str_from_tts(cx, sp, tts, "option_env!") { + None => return DummyResult::any(sp), + Some(v) => v, + }; + + let sp = cx.with_def_site_ctxt(sp); + let value = env::var(&var.as_str()).ok().as_deref().map(Symbol::intern); + cx.sess.parse_sess.env_depinfo.borrow_mut().insert((Symbol::intern(&var), value)); + let e = match value { + None => { + let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp)); + cx.expr_path(cx.path_all( + sp, + true, + cx.std_path(&[sym::option, sym::Option, sym::None]), + vec![GenericArg::Type(cx.ty_rptr( + sp, + cx.ty_ident(sp, Ident::new(sym::str, sp)), + Some(lt), + ast::Mutability::Not, + ))], + )) + } + Some(value) => cx.expr_call_global( + sp, + cx.std_path(&[sym::option, sym::Option, sym::Some]), + vec![cx.expr_str(sp, value)], + ), + }; + MacEager::expr(e) +} + +pub fn expand_env<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + let mut exprs = match get_exprs_from_tts(cx, sp, tts) { + Some(ref exprs) if exprs.is_empty() => { + cx.span_err(sp, "env! takes 1 or 2 arguments"); + return DummyResult::any(sp); + } + None => return DummyResult::any(sp), + Some(exprs) => exprs.into_iter(), + }; + + let var = match expr_to_string(cx, exprs.next().unwrap(), "expected string literal") { + None => return DummyResult::any(sp), + Some((v, _style)) => v, + }; + let msg = match exprs.next() { + None => Symbol::intern(&format!("environment variable `{}` not defined", var)), + Some(second) => match expr_to_string(cx, second, "expected string literal") { + None => return DummyResult::any(sp), + Some((s, _style)) => s, + }, + }; + + if exprs.next().is_some() { + cx.span_err(sp, "env! takes 1 or 2 arguments"); + return DummyResult::any(sp); + } + + let sp = cx.with_def_site_ctxt(sp); + let value = env::var(&*var.as_str()).ok().as_deref().map(Symbol::intern); + cx.sess.parse_sess.env_depinfo.borrow_mut().insert((var, value)); + let e = match value { + None => { + cx.span_err(sp, &msg.as_str()); + return DummyResult::any(sp); + } + Some(value) => cx.expr_str(sp, value), + }; + MacEager::expr(e) +} diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs new file mode 100644 index 00000000000..373277f525d --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -0,0 +1,1155 @@ +use ArgumentType::*; +use Position::*; + +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{pluralize, Applicability, DiagnosticBuilder}; +use rustc_expand::base::{self, *}; +use rustc_parse_format as parse; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::{MultiSpan, Span}; + +use std::borrow::Cow; +use std::collections::hash_map::Entry; + +#[derive(PartialEq)] +enum ArgumentType { + Placeholder(&'static str), + Count, +} + +enum Position { + Exact(usize), + Named(Symbol), +} + +struct Context<'a, 'b> { + ecx: &'a mut ExtCtxt<'b>, + /// The macro's call site. References to unstable formatting internals must + /// use this span to pass the stability checker. + macsp: Span, + /// The span of the format string literal. + fmtsp: Span, + + /// List of parsed argument expressions. + /// Named expressions are resolved early, and are appended to the end of + /// argument expressions. + /// + /// Example showing the various data structures in motion: + /// + /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"` + /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` + /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` + /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]` + /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]` + /// * `names` (in JSON): `{"foo": 2}` + args: Vec<P<ast::Expr>>, + /// Placeholder slot numbers indexed by argument. + arg_types: Vec<Vec<usize>>, + /// Unique format specs seen for each argument. + arg_unique_types: Vec<Vec<ArgumentType>>, + /// Map from named arguments to their resolved indices. + names: FxHashMap<Symbol, usize>, + + /// The latest consecutive literal strings, or empty if there weren't any. + literal: String, + + /// Collection of the compiled `rt::Argument` structures + pieces: Vec<P<ast::Expr>>, + /// Collection of string literals + str_pieces: Vec<P<ast::Expr>>, + /// Stays `true` if all formatting parameters are default (as in "{}{}"). + all_pieces_simple: bool, + + /// Mapping between positional argument references and indices into the + /// final generated static argument array. We record the starting indices + /// corresponding to each positional argument, and number of references + /// consumed so far for each argument, to facilitate correct `Position` + /// mapping in `build_piece`. In effect this can be seen as a "flattened" + /// version of `arg_unique_types`. + /// + /// Again with the example described above in docstring for `args`: + /// + /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]` + arg_index_map: Vec<Vec<usize>>, + + /// Starting offset of count argument slots. + count_args_index_offset: usize, + + /// Count argument slots and tracking data structures. + /// Count arguments are separately tracked for de-duplication in case + /// multiple references are made to one argument. For example, in this + /// format string: + /// + /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"` + /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"` + /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"` + /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}` + /// * `count_args`: `vec![Exact(0), Exact(5), Exact(3)]` + count_args: Vec<Position>, + /// Relative slot numbers for count arguments. + count_positions: FxHashMap<usize, usize>, + /// Number of count slots assigned. + count_positions_count: usize, + + /// Current position of the implicit positional arg pointer, as if it + /// still existed in this phase of processing. + /// Used only for `all_pieces_simple` tracking in `build_piece`. + curarg: usize, + /// Current piece being evaluated, used for error reporting. + curpiece: usize, + /// Keep track of invalid references to positional arguments. + invalid_refs: Vec<(usize, usize)>, + /// Spans of all the formatting arguments, in order. + arg_spans: Vec<Span>, + /// All the formatting arguments that have formatting flags set, in order for diagnostics. + arg_with_formatting: Vec<parse::FormatSpec<'a>>, + + /// Whether this format string came from a string literal, as opposed to a macro. + is_literal: bool, +} + +/// Parses the arguments from the given list of tokens, returning the diagnostic +/// if there's a parse error so we can continue parsing other format! +/// expressions. +/// +/// If parsing succeeds, the return value is: +/// +/// ```text +/// Some((fmtstr, parsed arguments, index map for named arguments)) +/// ``` +fn parse_args<'a>( + ecx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> Result<(P<ast::Expr>, Vec<P<ast::Expr>>, FxHashMap<Symbol, usize>), DiagnosticBuilder<'a>> { + let mut args = Vec::<P<ast::Expr>>::new(); + let mut names = FxHashMap::<Symbol, usize>::default(); + + let mut p = ecx.new_parser_from_tts(tts); + + if p.token == token::Eof { + return Err(ecx.struct_span_err(sp, "requires at least a format string argument")); + } + + let fmtstr = p.parse_expr()?; + let mut first = true; + let mut named = false; + + while p.token != token::Eof { + if !p.eat(&token::Comma) { + if first { + // After `format!(""` we always expect *only* a comma... + let mut err = ecx.struct_span_err(p.token.span, "expected token: `,`"); + err.span_label(p.token.span, "expected `,`"); + p.maybe_annotate_with_ascription(&mut err, false); + return Err(err); + } else { + // ...after that delegate to `expect` to also include the other expected tokens. + let _ = p.expect(&token::Comma)?; + } + } + first = false; + if p.token == token::Eof { + break; + } // accept trailing commas + match p.token.ident() { + Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { + named = true; + p.bump(); + p.expect(&token::Eq)?; + let e = p.parse_expr()?; + if let Some(prev) = names.get(&ident.name) { + ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", ident)) + .span_label(args[*prev].span, "previously here") + .span_label(e.span, "duplicate argument") + .emit(); + continue; + } + + // Resolve names into slots early. + // Since all the positional args are already seen at this point + // if the input is valid, we can simply append to the positional + // args. And remember the names. + let slot = args.len(); + names.insert(ident.name, slot); + args.push(e); + } + _ => { + let e = p.parse_expr()?; + if named { + let mut err = ecx.struct_span_err( + e.span, + "positional arguments cannot follow named arguments", + ); + err.span_label(e.span, "positional arguments must be before named arguments"); + for pos in names.values() { + err.span_label(args[*pos].span, "named argument"); + } + err.emit(); + } + args.push(e); + } + } + } + Ok((fmtstr, args, names)) +} + +impl<'a, 'b> Context<'a, 'b> { + fn resolve_name_inplace(&self, p: &mut parse::Piece<'_>) { + // NOTE: the `unwrap_or` branch is needed in case of invalid format + // arguments, e.g., `format_args!("{foo}")`. + let lookup = |s: Symbol| *self.names.get(&s).unwrap_or(&0); + + match *p { + parse::String(_) => {} + parse::NextArgument(ref mut arg) => { + if let parse::ArgumentNamed(s) = arg.position { + arg.position = parse::ArgumentIs(lookup(s)); + } + if let parse::CountIsName(s) = arg.format.width { + arg.format.width = parse::CountIsParam(lookup(s)); + } + if let parse::CountIsName(s) = arg.format.precision { + arg.format.precision = parse::CountIsParam(lookup(s)); + } + } + } + } + + /// Verifies one piece of a parse string, and remembers it if valid. + /// All errors are not emitted as fatal so we can continue giving errors + /// about this and possibly other format strings. + fn verify_piece(&mut self, p: &parse::Piece<'_>) { + match *p { + parse::String(..) => {} + parse::NextArgument(ref arg) => { + // width/precision first, if they have implicit positional + // parameters it makes more sense to consume them first. + self.verify_count(arg.format.width); + self.verify_count(arg.format.precision); + + // argument second, if it's an implicit positional parameter + // it's written second, so it should come after width/precision. + let pos = match arg.position { + parse::ArgumentIs(i) | parse::ArgumentImplicitlyIs(i) => Exact(i), + parse::ArgumentNamed(s) => Named(s), + }; + + let ty = Placeholder(match &arg.format.ty[..] { + "" => "Display", + "?" => "Debug", + "e" => "LowerExp", + "E" => "UpperExp", + "o" => "Octal", + "p" => "Pointer", + "b" => "Binary", + "x" => "LowerHex", + "X" => "UpperHex", + _ => { + let fmtsp = self.fmtsp; + let sp = arg.format.ty_span.map(|sp| fmtsp.from_inner(sp)); + let mut err = self.ecx.struct_span_err( + sp.unwrap_or(fmtsp), + &format!("unknown format trait `{}`", arg.format.ty), + ); + err.note( + "the only appropriate formatting traits are:\n\ + - ``, which uses the `Display` trait\n\ + - `?`, which uses the `Debug` trait\n\ + - `e`, which uses the `LowerExp` trait\n\ + - `E`, which uses the `UpperExp` trait\n\ + - `o`, which uses the `Octal` trait\n\ + - `p`, which uses the `Pointer` trait\n\ + - `b`, which uses the `Binary` trait\n\ + - `x`, which uses the `LowerHex` trait\n\ + - `X`, which uses the `UpperHex` trait", + ); + if let Some(sp) = sp { + for (fmt, name) in &[ + ("", "Display"), + ("?", "Debug"), + ("e", "LowerExp"), + ("E", "UpperExp"), + ("o", "Octal"), + ("p", "Pointer"), + ("b", "Binary"), + ("x", "LowerHex"), + ("X", "UpperHex"), + ] { + // FIXME: rustfix (`run-rustfix`) fails to apply suggestions. + // > "Cannot replace slice of data that was already replaced" + err.tool_only_span_suggestion( + sp, + &format!("use the `{}` trait", name), + (*fmt).to_string(), + Applicability::MaybeIncorrect, + ); + } + } + err.emit(); + "<invalid>" + } + }); + self.verify_arg_type(pos, ty); + self.curpiece += 1; + } + } + } + + fn verify_count(&mut self, c: parse::Count) { + match c { + parse::CountImplied | parse::CountIs(..) => {} + parse::CountIsParam(i) => { + self.verify_arg_type(Exact(i), Count); + } + parse::CountIsName(s) => { + self.verify_arg_type(Named(s), Count); + } + } + } + + fn describe_num_args(&self) -> Cow<'_, str> { + match self.args.len() { + 0 => "no arguments were given".into(), + 1 => "there is 1 argument".into(), + x => format!("there are {} arguments", x).into(), + } + } + + /// Handle invalid references to positional arguments. Output different + /// errors for the case where all arguments are positional and for when + /// there are named arguments or numbered positional arguments in the + /// format string. + fn report_invalid_references(&self, numbered_position_args: bool) { + let mut e; + let sp = if !self.arg_spans.is_empty() { + // Point at the formatting arguments. + MultiSpan::from_spans(self.arg_spans.clone()) + } else { + MultiSpan::from_span(self.fmtsp) + }; + let refs = + self.invalid_refs.iter().map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos))); + + let mut zero_based_note = false; + + let count = self.pieces.len() + + self.arg_with_formatting.iter().filter(|fmt| fmt.precision_span.is_some()).count(); + if self.names.is_empty() && !numbered_position_args && count != self.args.len() { + e = self.ecx.struct_span_err( + sp, + &format!( + "{} positional argument{} in format string, but {}", + count, + pluralize!(count), + self.describe_num_args(), + ), + ); + for arg in &self.args { + // Point at the arguments that will be formatted. + e.span_label(arg.span, ""); + } + } else { + let (mut refs, spans): (Vec<_>, Vec<_>) = refs.unzip(); + // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` + // for `println!("{7:7$}", 1);` + refs.sort(); + refs.dedup(); + let spans: Vec<_> = spans.into_iter().filter_map(|sp| sp.copied()).collect(); + let sp = if self.arg_spans.is_empty() || spans.is_empty() { + MultiSpan::from_span(self.fmtsp) + } else { + MultiSpan::from_spans(spans) + }; + let arg_list = if refs.len() == 1 { + format!("argument {}", refs[0]) + } else { + let reg = refs.pop().unwrap(); + format!("arguments {head} and {tail}", head = refs.join(", "), tail = reg) + }; + + e = self.ecx.struct_span_err( + sp, + &format!( + "invalid reference to positional {} ({})", + arg_list, + self.describe_num_args() + ), + ); + zero_based_note = true; + }; + + for fmt in &self.arg_with_formatting { + if let Some(span) = fmt.precision_span { + let span = self.fmtsp.from_inner(span); + match fmt.precision { + parse::CountIsParam(pos) if pos > self.args.len() => { + e.span_label( + span, + &format!( + "this precision flag expects an `usize` argument at position {}, \ + but {}", + pos, + self.describe_num_args(), + ), + ); + zero_based_note = true; + } + parse::CountIsParam(pos) => { + let count = self.pieces.len() + + self + .arg_with_formatting + .iter() + .filter(|fmt| fmt.precision_span.is_some()) + .count(); + e.span_label(span, &format!( + "this precision flag adds an extra required argument at position {}, \ + which is why there {} expected", + pos, + if count == 1 { + "is 1 argument".to_string() + } else { + format!("are {} arguments", count) + }, + )); + if let Some(arg) = self.args.get(pos) { + e.span_label( + arg.span, + "this parameter corresponds to the precision flag", + ); + } + zero_based_note = true; + } + _ => {} + } + } + if let Some(span) = fmt.width_span { + let span = self.fmtsp.from_inner(span); + match fmt.width { + parse::CountIsParam(pos) if pos > self.args.len() => { + e.span_label( + span, + &format!( + "this width flag expects an `usize` argument at position {}, \ + but {}", + pos, + self.describe_num_args(), + ), + ); + zero_based_note = true; + } + _ => {} + } + } + } + if zero_based_note { + e.note("positional arguments are zero-based"); + } + if !self.arg_with_formatting.is_empty() { + e.note( + "for information about formatting flags, visit \ + https://doc.rust-lang.org/std/fmt/index.html", + ); + } + + e.emit(); + } + + /// Actually verifies and tracks a given format placeholder + /// (a.k.a. argument). + fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) { + match arg { + Exact(arg) => { + if self.args.len() <= arg { + self.invalid_refs.push((arg, self.curpiece)); + return; + } + match ty { + Placeholder(_) => { + // record every (position, type) combination only once + let seen_ty = &mut self.arg_unique_types[arg]; + let i = seen_ty.iter().position(|x| *x == ty).unwrap_or_else(|| { + let i = seen_ty.len(); + seen_ty.push(ty); + i + }); + self.arg_types[arg].push(i); + } + Count => { + if let Entry::Vacant(e) = self.count_positions.entry(arg) { + let i = self.count_positions_count; + e.insert(i); + self.count_args.push(Exact(arg)); + self.count_positions_count += 1; + } + } + } + } + + Named(name) => { + match self.names.get(&name) { + Some(&idx) => { + // Treat as positional arg. + self.verify_arg_type(Exact(idx), ty) + } + None => { + let capture_feature_enabled = self + .ecx + .ecfg + .features + .map_or(false, |features| features.format_args_capture); + + // For the moment capturing variables from format strings expanded from macros is + // disabled (see RFC #2795) + let can_capture = capture_feature_enabled && self.is_literal; + + if can_capture { + // Treat this name as a variable to capture from the surrounding scope + let idx = self.args.len(); + self.arg_types.push(Vec::new()); + self.arg_unique_types.push(Vec::new()); + self.args.push( + self.ecx.expr_ident(self.fmtsp, Ident::new(name, self.fmtsp)), + ); + self.names.insert(name, idx); + self.verify_arg_type(Exact(idx), ty) + } else { + let msg = format!("there is no argument named `{}`", name); + let sp = if self.is_literal { + *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp) + } else { + self.fmtsp + }; + let mut err = self.ecx.struct_span_err(sp, &msg[..]); + + if capture_feature_enabled && !self.is_literal { + err.note(&format!( + "did you intend to capture a variable `{}` from \ + the surrounding scope?", + name + )); + err.note( + "to avoid ambiguity, `format_args!` cannot capture variables \ + when the format string is expanded from a macro", + ); + } else if self.ecx.parse_sess().unstable_features.is_nightly_build() { + err.help(&format!( + "if you intended to capture `{}` from the surrounding scope, add \ + `#![feature(format_args_capture)]` to the crate attributes", + name + )); + } + + err.emit(); + } + } + } + } + } + } + + /// Builds the mapping between format placeholders and argument objects. + fn build_index_map(&mut self) { + // NOTE: Keep the ordering the same as `into_expr`'s expansion would do! + let args_len = self.args.len(); + self.arg_index_map.reserve(args_len); + + let mut sofar = 0usize; + + // Map the arguments + for i in 0..args_len { + let arg_types = &self.arg_types[i]; + let arg_offsets = arg_types.iter().map(|offset| sofar + *offset).collect::<Vec<_>>(); + self.arg_index_map.push(arg_offsets); + sofar += self.arg_unique_types[i].len(); + } + + // Record starting index for counts, which appear just after arguments + self.count_args_index_offset = sofar; + } + + fn rtpath(ecx: &ExtCtxt<'_>, s: Symbol) -> Vec<Ident> { + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, s]) + } + + fn build_count(&self, c: parse::Count) -> P<ast::Expr> { + let sp = self.macsp; + let count = |c, arg| { + let mut path = Context::rtpath(self.ecx, sym::Count); + path.push(Ident::new(c, sp)); + match arg { + Some(arg) => self.ecx.expr_call_global(sp, path, vec![arg]), + None => self.ecx.expr_path(self.ecx.path_global(sp, path)), + } + }; + match c { + parse::CountIs(i) => count(sym::Is, Some(self.ecx.expr_usize(sp, i))), + parse::CountIsParam(i) => { + // This needs mapping too, as `i` is referring to a macro + // argument. If `i` is not found in `count_positions` then + // the error had already been emitted elsewhere. + let i = self.count_positions.get(&i).cloned().unwrap_or(0) + + self.count_args_index_offset; + count(sym::Param, Some(self.ecx.expr_usize(sp, i))) + } + parse::CountImplied => count(sym::Implied, None), + // should never be the case, names are already resolved + parse::CountIsName(_) => panic!("should never happen"), + } + } + + /// Build a literal expression from the accumulated string literals + fn build_literal_string(&mut self) -> P<ast::Expr> { + let sp = self.fmtsp; + let s = Symbol::intern(&self.literal); + self.literal.clear(); + self.ecx.expr_str(sp, s) + } + + /// Builds a static `rt::Argument` from a `parse::Piece` or append + /// to the `literal` string. + fn build_piece( + &mut self, + piece: &parse::Piece<'a>, + arg_index_consumed: &mut Vec<usize>, + ) -> Option<P<ast::Expr>> { + let sp = self.macsp; + match *piece { + parse::String(s) => { + self.literal.push_str(s); + None + } + parse::NextArgument(ref arg) => { + // Build the position + let pos = { + match arg.position { + parse::ArgumentIs(i) | parse::ArgumentImplicitlyIs(i) => { + // Map to index in final generated argument array + // in case of multiple types specified + let arg_idx = match arg_index_consumed.get_mut(i) { + None => 0, // error already emitted elsewhere + Some(offset) => { + let idx_map = &self.arg_index_map[i]; + // unwrap_or branch: error already emitted elsewhere + let arg_idx = *idx_map.get(*offset).unwrap_or(&0); + *offset += 1; + arg_idx + } + }; + self.ecx.expr_usize(sp, arg_idx) + } + + // should never be the case, because names are already + // resolved. + parse::ArgumentNamed(_) => panic!("should never happen"), + } + }; + + let simple_arg = parse::Argument { + position: { + // We don't have ArgumentNext any more, so we have to + // track the current argument ourselves. + let i = self.curarg; + self.curarg += 1; + parse::ArgumentIs(i) + }, + format: parse::FormatSpec { + fill: arg.format.fill, + align: parse::AlignUnknown, + flags: 0, + precision: parse::CountImplied, + precision_span: None, + width: parse::CountImplied, + width_span: None, + ty: arg.format.ty, + ty_span: arg.format.ty_span, + }, + }; + + let fill = arg.format.fill.unwrap_or(' '); + + let pos_simple = arg.position.index() == simple_arg.position.index(); + + if arg.format.precision_span.is_some() || arg.format.width_span.is_some() { + self.arg_with_formatting.push(arg.format); + } + if !pos_simple || arg.format != simple_arg.format || fill != ' ' { + self.all_pieces_simple = false; + } + + // Build the format + let fill = self.ecx.expr_lit(sp, ast::LitKind::Char(fill)); + let align = |name| { + let mut p = Context::rtpath(self.ecx, sym::Alignment); + p.push(Ident::new(name, sp)); + self.ecx.path_global(sp, p) + }; + let align = match arg.format.align { + parse::AlignLeft => align(sym::Left), + parse::AlignRight => align(sym::Right), + parse::AlignCenter => align(sym::Center), + parse::AlignUnknown => align(sym::Unknown), + }; + let align = self.ecx.expr_path(align); + let flags = self.ecx.expr_u32(sp, arg.format.flags); + let prec = self.build_count(arg.format.precision); + let width = self.build_count(arg.format.width); + let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::FormatSpec)); + let fmt = self.ecx.expr_struct( + sp, + path, + vec![ + self.ecx.field_imm(sp, Ident::new(sym::fill, sp), fill), + self.ecx.field_imm(sp, Ident::new(sym::align, sp), align), + self.ecx.field_imm(sp, Ident::new(sym::flags, sp), flags), + self.ecx.field_imm(sp, Ident::new(sym::precision, sp), prec), + self.ecx.field_imm(sp, Ident::new(sym::width, sp), width), + ], + ); + + let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::Argument)); + Some(self.ecx.expr_struct( + sp, + path, + vec![ + self.ecx.field_imm(sp, Ident::new(sym::position, sp), pos), + self.ecx.field_imm(sp, Ident::new(sym::format, sp), fmt), + ], + )) + } + } + } + + /// Actually builds the expression which the format_args! block will be + /// expanded to. + fn into_expr(self) -> P<ast::Expr> { + let mut locals = + Vec::with_capacity((0..self.args.len()).map(|i| self.arg_unique_types[i].len()).sum()); + let mut counts = Vec::with_capacity(self.count_args.len()); + let mut pats = Vec::with_capacity(self.args.len()); + let mut heads = Vec::with_capacity(self.args.len()); + + let names_pos: Vec<_> = (0..self.args.len()) + .map(|i| Ident::from_str_and_span(&format!("arg{}", i), self.macsp)) + .collect(); + + // First, build up the static array which will become our precompiled + // format "string" + let pieces = self.ecx.expr_vec_slice(self.fmtsp, self.str_pieces); + + // Before consuming the expressions, we have to remember spans for + // count arguments as they are now generated separate from other + // arguments, hence have no access to the `P<ast::Expr>`'s. + let spans_pos: Vec<_> = self.args.iter().map(|e| e.span).collect(); + + // Right now there is a bug such that for the expression: + // foo(bar(&1)) + // the lifetime of `1` doesn't outlast the call to `bar`, so it's not + // valid for the call to `foo`. To work around this all arguments to the + // format! string are shoved into locals. Furthermore, we shove the address + // of each variable because we don't want to move out of the arguments + // passed to this function. + for (i, e) in self.args.into_iter().enumerate() { + let name = names_pos[i]; + let span = self.ecx.with_def_site_ctxt(e.span); + pats.push(self.ecx.pat_ident(span, name)); + for arg_ty in self.arg_unique_types[i].iter() { + locals.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty, name)); + } + heads.push(self.ecx.expr_addr_of(e.span, e)); + } + for pos in self.count_args { + let index = match pos { + Exact(i) => i, + _ => panic!("should never happen"), + }; + let name = names_pos[index]; + let span = spans_pos[index]; + counts.push(Context::format_arg(self.ecx, self.macsp, span, &Count, name)); + } + + // Now create a vector containing all the arguments + let args = locals.into_iter().chain(counts.into_iter()); + + let args_array = self.ecx.expr_vec(self.macsp, args.collect()); + + // Constructs an AST equivalent to: + // + // match (&arg0, &arg1) { + // (tmp0, tmp1) => args_array + // } + // + // It was: + // + // let tmp0 = &arg0; + // let tmp1 = &arg1; + // args_array + // + // Because of #11585 the new temporary lifetime rule, the enclosing + // statements for these temporaries become the let's themselves. + // If one or more of them are RefCell's, RefCell borrow() will also + // end there; they don't last long enough for args_array to use them. + // The match expression solves the scope problem. + // + // Note, it may also very well be transformed to: + // + // match arg0 { + // ref tmp0 => { + // match arg1 => { + // ref tmp1 => args_array } } } + // + // But the nested match expression is proved to perform not as well + // as series of let's; the first approach does. + let pat = self.ecx.pat_tuple(self.macsp, pats); + let arm = self.ecx.arm(self.macsp, pat, args_array); + let head = self.ecx.expr(self.macsp, ast::ExprKind::Tup(heads)); + let result = self.ecx.expr_match(self.macsp, head, vec![arm]); + + let args_slice = self.ecx.expr_addr_of(self.macsp, result); + + // Now create the fmt::Arguments struct with all our locals we created. + let (fn_name, fn_args) = if self.all_pieces_simple { + ("new_v1", vec![pieces, args_slice]) + } else { + // Build up the static array which will store our precompiled + // nonstandard placeholders, if there are any. + let fmt = self.ecx.expr_vec_slice(self.macsp, self.pieces); + + ("new_v1_formatted", vec![pieces, args_slice, fmt]) + }; + + let path = self.ecx.std_path(&[sym::fmt, sym::Arguments, Symbol::intern(fn_name)]); + self.ecx.expr_call_global(self.macsp, path, fn_args) + } + + fn format_arg( + ecx: &ExtCtxt<'_>, + macsp: Span, + mut sp: Span, + ty: &ArgumentType, + arg: Ident, + ) -> P<ast::Expr> { + sp = ecx.with_def_site_ctxt(sp); + let arg = ecx.expr_ident(sp, arg); + let trait_ = match *ty { + Placeholder(trait_) if trait_ == "<invalid>" => return DummyResult::raw_expr(sp, true), + Placeholder(trait_) => trait_, + Count => { + let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::from_usize]); + return ecx.expr_call_global(macsp, path, vec![arg]); + } + }; + + let path = ecx.std_path(&[sym::fmt, Symbol::intern(trait_), sym::fmt]); + let format_fn = ecx.path_global(sp, path); + let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::new]); + ecx.expr_call_global(macsp, path, vec![arg, ecx.expr_path(format_fn)]) + } +} + +fn expand_format_args_impl<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + mut sp: Span, + tts: TokenStream, + nl: bool, +) -> Box<dyn base::MacResult + 'cx> { + sp = ecx.with_def_site_ctxt(sp); + match parse_args(ecx, sp, tts) { + Ok((efmt, args, names)) => { + MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, nl)) + } + Err(mut err) => { + err.emit(); + DummyResult::any(sp) + } + } +} + +pub fn expand_format_args<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + expand_format_args_impl(ecx, sp, tts, false) +} + +pub fn expand_format_args_nl<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + expand_format_args_impl(ecx, sp, tts, true) +} + +/// Take the various parts of `format_args!(efmt, args..., name=names...)` +/// and construct the appropriate formatting expression. +pub fn expand_preparsed_format_args( + ecx: &mut ExtCtxt<'_>, + sp: Span, + efmt: P<ast::Expr>, + args: Vec<P<ast::Expr>>, + names: FxHashMap<Symbol, usize>, + append_newline: bool, +) -> P<ast::Expr> { + // NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because + // `ArgumentType` does not derive `Clone`. + let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect(); + let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect(); + + let mut macsp = ecx.call_site(); + macsp = ecx.with_def_site_ctxt(macsp); + + let msg = "format argument must be a string literal"; + let fmt_sp = efmt.span; + let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) { + Ok(mut fmt) if append_newline => { + fmt.0 = Symbol::intern(&format!("{}\n", fmt.0)); + fmt + } + Ok(fmt) => fmt, + Err(err) => { + if let Some(mut err) = err { + let sugg_fmt = match args.len() { + 0 => "{}".to_string(), + _ => format!("{}{{}}", "{} ".repeat(args.len())), + }; + err.span_suggestion( + fmt_sp.shrink_to_lo(), + "you might be missing a string literal to format with", + format!("\"{}\", ", sugg_fmt), + Applicability::MaybeIncorrect, + ); + err.emit(); + } + return DummyResult::raw_expr(sp, true); + } + }; + + let str_style = match fmt_style { + ast::StrStyle::Cooked => None, + ast::StrStyle::Raw(raw) => Some(raw as usize), + }; + + let fmt_str = &fmt_str.as_str(); // for the suggestions below + let fmt_snippet = ecx.source_map().span_to_snippet(fmt_sp).ok(); + let mut parser = parse::Parser::new( + fmt_str, + str_style, + fmt_snippet, + append_newline, + parse::ParseMode::Format, + ); + + let mut unverified_pieces = Vec::new(); + while let Some(piece) = parser.next() { + if !parser.errors.is_empty() { + break; + } else { + unverified_pieces.push(piece); + } + } + + if !parser.errors.is_empty() { + let err = parser.errors.remove(0); + let sp = fmt_span.from_inner(err.span); + let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", err.description)); + e.span_label(sp, err.label + " in format string"); + if let Some(note) = err.note { + e.note(¬e); + } + if let Some((label, span)) = err.secondary_label { + let sp = fmt_span.from_inner(span); + e.span_label(sp, label); + } + e.emit(); + return DummyResult::raw_expr(sp, true); + } + + let arg_spans = parser.arg_places.iter().map(|span| fmt_span.from_inner(*span)).collect(); + + let named_pos: FxHashSet<usize> = names.values().cloned().collect(); + + let mut cx = Context { + ecx, + args, + arg_types, + arg_unique_types, + names, + curarg: 0, + curpiece: 0, + arg_index_map: Vec::new(), + count_args: Vec::new(), + count_positions: FxHashMap::default(), + count_positions_count: 0, + count_args_index_offset: 0, + literal: String::new(), + pieces: Vec::with_capacity(unverified_pieces.len()), + str_pieces: Vec::with_capacity(unverified_pieces.len()), + all_pieces_simple: true, + macsp, + fmtsp: fmt_span, + invalid_refs: Vec::new(), + arg_spans, + arg_with_formatting: Vec::new(), + is_literal: parser.is_literal, + }; + + // This needs to happen *after* the Parser has consumed all pieces to create all the spans + let pieces = unverified_pieces + .into_iter() + .map(|mut piece| { + cx.verify_piece(&piece); + cx.resolve_name_inplace(&mut piece); + piece + }) + .collect::<Vec<_>>(); + + let numbered_position_args = pieces.iter().any(|arg: &parse::Piece<'_>| match *arg { + parse::String(_) => false, + parse::NextArgument(arg) => match arg.position { + parse::Position::ArgumentIs(_) => true, + _ => false, + }, + }); + + cx.build_index_map(); + + let mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()]; + + for piece in pieces { + if let Some(piece) = cx.build_piece(&piece, &mut arg_index_consumed) { + let s = cx.build_literal_string(); + cx.str_pieces.push(s); + cx.pieces.push(piece); + } + } + + if !cx.literal.is_empty() { + let s = cx.build_literal_string(); + cx.str_pieces.push(s); + } + + if !cx.invalid_refs.is_empty() { + cx.report_invalid_references(numbered_position_args); + } + + // Make sure that all arguments were used and all arguments have types. + let errs = cx + .arg_types + .iter() + .enumerate() + .filter(|(i, ty)| ty.is_empty() && !cx.count_positions.contains_key(&i)) + .map(|(i, _)| { + let msg = if named_pos.contains(&i) { + // named argument + "named argument never used" + } else { + // positional argument + "argument never used" + }; + (cx.args[i].span, msg) + }) + .collect::<Vec<_>>(); + + let errs_len = errs.len(); + if !errs.is_empty() { + let args_used = cx.arg_types.len() - errs_len; + let args_unused = errs_len; + + let mut diag = { + if let [(sp, msg)] = &errs[..] { + let mut diag = cx.ecx.struct_span_err(*sp, *msg); + diag.span_label(*sp, *msg); + diag + } else { + let mut diag = cx.ecx.struct_span_err( + errs.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(), + "multiple unused formatting arguments", + ); + diag.span_label(cx.fmtsp, "multiple missing formatting specifiers"); + for (sp, msg) in errs { + diag.span_label(sp, msg); + } + diag + } + }; + + // Used to ensure we only report translations for *one* kind of foreign format. + let mut found_foreign = false; + // Decide if we want to look for foreign formatting directives. + if args_used < args_unused { + use super::format_foreign as foreign; + + // The set of foreign substitutions we've explained. This prevents spamming the user + // with `%d should be written as {}` over and over again. + let mut explained = FxHashSet::default(); + + macro_rules! check_foreign { + ($kind:ident) => {{ + let mut show_doc_note = false; + + let mut suggestions = vec![]; + // account for `"` and account for raw strings `r#` + let padding = str_style.map(|i| i + 2).unwrap_or(1); + for sub in foreign::$kind::iter_subs(fmt_str, padding) { + let trn = match sub.translate() { + Some(trn) => trn, + + // If it has no translation, don't call it out specifically. + None => continue, + }; + + let pos = sub.position(); + let sub = String::from(sub.as_str()); + if explained.contains(&sub) { + continue; + } + explained.insert(sub.clone()); + + if !found_foreign { + found_foreign = true; + show_doc_note = true; + } + + if let Some(inner_sp) = pos { + let sp = fmt_sp.from_inner(inner_sp); + suggestions.push((sp, trn)); + } else { + diag.help(&format!("`{}` should be written as `{}`", sub, trn)); + } + } + + if show_doc_note { + diag.note(concat!( + stringify!($kind), + " formatting not supported; see the documentation for `std::fmt`", + )); + } + if suggestions.len() > 0 { + diag.multipart_suggestion( + "format specifiers use curly braces", + suggestions, + Applicability::MachineApplicable, + ); + } + }}; + } + + check_foreign!(printf); + if !found_foreign { + check_foreign!(shell); + } + } + if !found_foreign && errs_len == 1 { + diag.span_label(cx.fmtsp, "formatting specifier missing"); + } + + diag.emit(); + } + + cx.into_expr() +} 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..85cf4c42e94 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format_foreign.rs @@ -0,0 +1,823 @@ +pub 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. + Escape, + } + + impl<'a> Substitution<'a> { + pub fn as_str(&self) -> &str { + match *self { + Substitution::Format(ref fmt) => fmt.span, + Substitution::Escape => "%%", + } + } + + pub fn position(&self) -> Option<InnerSpan> { + match *self { + Substitution::Format(ref fmt) => Some(fmt.position), + _ => None, + } + } + + pub fn set_position(&mut self, start: usize, end: usize) { + if let Substitution::Format(ref mut fmt) = self { + fmt.position = InnerSpan::new(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) -> Option<String> { + match *self { + Substitution::Format(ref fmt) => fmt.translate(), + Substitution::Escape => 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 `None` in cases where the `printf` directive does not have an exact Rust + /// equivalent, rather than guessing. + pub fn translate(&self) -> 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 None, + } + } + (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 None; + } + 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 None, + }; + + let (fill, width, precision) = match (is_int, width, precision) { + (true, Some(_), Some(_)) => { + // Rust can't duplicate this insanity. + return None; + } + (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_str("{"); + + if let Some(arg) = self.parameter { + write!(s, "{}", arg.checked_sub(1)?).ok()?; + } + + if has_options { + s.push_str(":"); + + 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_str("#"); + } + + if zero_fill { + s.push_str("0"); + } + + if let Some(width) = width { + width.translate(&mut s).ok()?; + } + + if let Some(precision) = precision { + s.push_str("."); + precision.translate(&mut s).ok()?; + } + + if let Some(type_) = type_ { + s.push_str(type_); + } + } + + s.push_str("}"); + Some(s) + } + } + + /// A general number used in a `printf` formatting directive. + #[derive(Copy, Clone, PartialEq, Debug)] + pub enum Num { + // The range of these values is technically bounded by `NL_ARGMAX`... but, at least for GNU + // libc, it apparently has no real fixed limit. A `u16` is used here on the basis that it + // is *vanishingly* unlikely that *anyone* is going to try formatting something wider, or + // with more precision, than 32 thousand positions which is so wide it couldn't possibly fit + // on a screen. + /// A specific, fixed value. + Num(u16), + /// The value is derived from a positional argument. + Arg(u16), + /// The value is derived from the "next" unconverted argument. + Next, + } + + impl Num { + fn from_str(s: &str, arg: Option<&str>) -> Self { + if let Some(arg) = arg { + Num::Arg(arg.parse().unwrap_or_else(|_| panic!("invalid format arg `{:?}`", arg))) + } else if s == "*" { + Num::Next + } else { + Num::Num(s.parse().unwrap_or_else(|_| panic!("invalid format num `{:?}`", s))) + } + } + + fn translate(&self, s: &mut String) -> std::fmt::Result { + use std::fmt::Write; + match *self { + Num::Num(n) => write!(s, "{}", n), + Num::Arg(n) => { + let n = n.checked_sub(1).ok_or(std::fmt::Error)?; + write!(s, "{}$", n) + } + Num::Next => write!(s, "*"), + } + } + } + + /// Returns an iterator over all substitutions in a given string. + pub fn iter_subs(s: &str, start_pos: usize) -> Substitutions<'_> { + Substitutions { s, pos: start_pos } + } + + /// Iterator over substitutions in a string. + pub struct Substitutions<'a> { + s: &'a str, + pos: usize, + } + + impl<'a> Iterator for Substitutions<'a> { + type Item = Substitution<'a>; + fn next(&mut self) -> Option<Self::Item> { + let (mut sub, tail) = parse_next_substitution(self.s)?; + self.s = tail; + match sub { + Substitution::Format(_) => { + if let Some(inner_span) = sub.position() { + sub.set_position(inner_span.start + self.pos, inner_span.end + self.pos); + self.pos += inner_span.end; + } + } + Substitution::Escape => self.pos += 2, + } + 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, &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, is_digit); + match end.next_cp() { + // Yes, this *is* the parameter. + Some(('$', end2)) => { + state = Flags; + parameter = Some(at.slice_between(end).unwrap().parse().unwrap()); + move_to!(end2); + } + // Wait, no, actually, it's the width. + Some(_) => { + state = Prec; + parameter = None; + flags = ""; + width = Some(Num::from_str(at.slice_between(end).unwrap(), None)); + move_to!(end); + } + // It's invalid, is what it is. + None => return fallback(), + } + } + _ => { + state = Flags; + parameter = None; + move_to!(at); + } + } + } + + if let Flags = state { + let end = at_next_cp_while(at, is_flag); + state = Width; + flags = at.slice_between(end).unwrap(); + move_to!(end); + } + + if let Width = state { + match c { + '*' => { + state = WidthArg; + move_to!(next); + } + '1'..='9' => { + let end = at_next_cp_while(next, is_digit); + state = Prec; + width = Some(Num::from_str(at.slice_between(end).unwrap(), None)); + move_to!(end); + } + _ => { + state = Prec; + width = None; + move_to!(at); + } + } + } + + if let WidthArg = state { + let end = at_next_cp_while(at, is_digit); + match end.next_cp() { + Some(('$', end2)) => { + state = Prec; + width = Some(Num::from_str("", Some(at.slice_between(end).unwrap()))); + move_to!(end2); + } + _ => { + state = Prec; + width = Some(Num::Next); + move_to!(end); + } + } + } + + if let Prec = state { + match c { + '.' => { + state = PrecInner; + move_to!(next); + } + _ => { + state = Length; + precision = None; + move_to!(at); + } + } + } + + if let PrecInner = state { + match c { + '*' => { + let end = at_next_cp_while(next, is_digit); + match end.next_cp() { + Some(('$', end2)) => { + state = Length; + precision = Some(Num::from_str("*", next.slice_between(end))); + move_to!(end2); + } + _ => { + state = Length; + precision = Some(Num::Next); + move_to!(end); + } + } + } + '0'..='9' => { + let end = at_next_cp_while(next, is_digit); + state = Length; + precision = Some(Num::from_str(at.slice_between(end).unwrap(), None)); + move_to!(end); + } + _ => return fallback(), + } + } + + if let Length = state { + let c1_next1 = next.next_cp(); + match (c, c1_next1) { + ('h', Some(('h', next1))) | ('l', Some(('l', next1))) => { + state = Type; + length = Some(at.slice_between(next1).unwrap()); + move_to!(next1); + } + + ('h' | 'l' | 'L' | 'z' | 'j' | 't' | 'q', _) => { + state = Type; + length = Some(at.slice_between(next).unwrap()); + move_to!(next); + } + + ('I', _) => { + let end = next + .at_next_cp() + .and_then(|end| end.at_next_cp()) + .map(|end| (next.slice_between(end).unwrap(), end)); + let end = match end { + Some(("32", end)) => end, + Some(("64", end)) => end, + _ => next, + }; + state = Type; + length = Some(at.slice_between(end).unwrap()); + move_to!(end); + } + + _ => { + state = Type; + length = None; + move_to!(at); + } + } + } + + if let Type = state { + drop(c); + type_ = at.slice_between(next).unwrap(); + + // Don't use `move_to!` here, as we *can* be at the end of the input. + at = next; + } + + drop(c); + drop(next); + + end = at; + let position = InnerSpan::new(start.at, end.at); + + let f = Format { + span: start.slice_between(end).unwrap(), + parameter, + flags, + width, + precision, + length, + type_, + position, + }; + Some((Substitution::Format(f), end.slice_after())) + } + + fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_> + where + F: FnMut(char) -> bool, + { + loop { + match cur.next_cp() { + Some((c, next)) => { + if pred(c) { + cur = next; + } else { + return cur; + } + } + None => return cur, + } + } + } + + fn is_digit(c: char) -> bool { + match c { + '0'..='9' => true, + _ => false, + } + } + + fn is_flag(c: char) -> bool { + match c { + '0' | '-' | '+' | ' ' | '#' | '\'' => true, + _ => false, + } + } + + #[cfg(test)] + mod tests; +} + +pub mod shell { + use super::strcursor::StrCursor as Cur; + use rustc_span::InnerSpan; + + #[derive(Clone, PartialEq, Debug)] + pub enum Substitution<'a> { + Ordinal(u8, (usize, usize)), + Name(&'a str, (usize, usize)), + Escape((usize, usize)), + } + + impl Substitution<'_> { + pub fn as_str(&self) -> String { + match self { + Substitution::Ordinal(n, _) => format!("${}", n), + Substitution::Name(n, _) => format!("${}", n), + Substitution::Escape(_) => "$$".into(), + } + } + + pub fn position(&self) -> Option<InnerSpan> { + match self { + Substitution::Ordinal(_, pos) + | Substitution::Name(_, pos) + | Substitution::Escape(pos) => Some(InnerSpan::new(pos.0, pos.1)), + } + } + + pub fn set_position(&mut self, start: usize, end: usize) { + match self { + Substitution::Ordinal(_, ref mut pos) + | Substitution::Name(_, ref mut pos) + | Substitution::Escape(ref mut pos) => *pos = (start, end), + } + } + + pub fn translate(&self) -> Option<String> { + match *self { + Substitution::Ordinal(n, _) => Some(format!("{{{}}}", n)), + Substitution::Name(n, _) => Some(format!("{{{}}}", n)), + Substitution::Escape(_) => 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> { + match parse_next_substitution(self.s) { + Some((mut sub, tail)) => { + self.s = tail; + if let Some(InnerSpan { start, end }) = sub.position() { + sub.set_position(start + self.pos, end + self.pos); + self.pos += end; + } + Some(sub) + } + None => None, + } + } + + 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 { + match c { + 'a'..='z' | 'A'..='Z' | '_' => true, + _ => false, + } + } + + fn is_ident_tail(c: char) -> bool { + match c { + '0'..='9' => true, + c => is_ident_head(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..33c54c9cee0 --- /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()), + $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, " escape"))); + assert_eq!(pns("%% leading escape"), Some((S::Escape, " leading escape"))); + assert_eq!(pns("trailing escape %%"), Some((S::Escape, ""))); +} + +#[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()).collect(); + assert_eq!( + subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(), + vec![Some("{}"), None, Some("{:.*}"), None] + ); +} + +/// Checks that the translations are what we expect. +#[test] +fn test_translation() { + assert_eq_pnsat!("%c", Some("{}")); + assert_eq_pnsat!("%d", Some("{}")); + assert_eq_pnsat!("%u", Some("{}")); + assert_eq_pnsat!("%x", Some("{:x}")); + assert_eq_pnsat!("%X", Some("{:X}")); + assert_eq_pnsat!("%e", Some("{:e}")); + assert_eq_pnsat!("%E", Some("{:E}")); + assert_eq_pnsat!("%f", Some("{}")); + assert_eq_pnsat!("%g", Some("{:e}")); + assert_eq_pnsat!("%G", Some("{:E}")); + assert_eq_pnsat!("%s", Some("{}")); + assert_eq_pnsat!("%p", Some("{:p}")); + + assert_eq_pnsat!("%06d", Some("{:06}")); + assert_eq_pnsat!("%4.2f", Some("{:4.2}")); + assert_eq_pnsat!("%#x", Some("{:#x}")); + assert_eq_pnsat!("%-10s", Some("{:<10}")); + assert_eq_pnsat!("%*s", None); + assert_eq_pnsat!("%-10.*s", Some("{:<10.*}")); + assert_eq_pnsat!("%-*.*s", None); + assert_eq_pnsat!("%.6i", Some("{:06}")); + assert_eq_pnsat!("%+i", Some("{:+}")); + assert_eq_pnsat!("%08X", Some("{:08X}")); + assert_eq_pnsat!("%lu", Some("{}")); + assert_eq_pnsat!("%Iu", Some("{}")); + assert_eq_pnsat!("%I32u", Some("{}")); + assert_eq_pnsat!("%I64u", Some("{}")); + assert_eq_pnsat!("%'d", None); + assert_eq_pnsat!("%10s", Some("{:>10}")); + assert_eq_pnsat!("%-10.10s", Some("{:<10.10}")); + assert_eq_pnsat!("%1$d", Some("{0}")); + assert_eq_pnsat!("%2$.*3$d", Some("{1:02$}")); + assert_eq_pnsat!("%1$*2$.*3$s", Some("{0:>1$.2$}")); + assert_eq_pnsat!("%-8ld", Some("{:<8}")); +} diff --git a/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs b/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs new file mode 100644 index 00000000000..ed8fe81dfcd --- /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()), + $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()).collect(); + assert_eq!( + subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(), + vec![Some("{0}"), None, Some("{WORD}")] + ); +} + +#[test] +fn test_translation() { + assert_eq_pnsat!("$0", Some("{0}")); + assert_eq_pnsat!("$9", Some("{9}")); + assert_eq_pnsat!("$1", Some("{1}")); + assert_eq_pnsat!("$10", Some("{1}")); + assert_eq_pnsat!("$stuff", Some("{stuff}")); + assert_eq_pnsat!("$NAME", Some("{NAME}")); + assert_eq_pnsat!("$PREFIX/bin", Some("{PREFIX}")); +} diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs new file mode 100644 index 00000000000..8478fcfbf09 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -0,0 +1,171 @@ +use crate::util::check_builtin_macro_attribute; + +use rustc_ast::expand::allocator::{ + AllocatorKind, AllocatorMethod, AllocatorTy, ALLOCATOR_METHODS, +}; +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, Attribute, Expr, FnHeader, FnSig, Generics, Param}; +use rustc_ast::{ItemKind, Mutability, Stmt, Ty, TyKind, Unsafe}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::Span; + +pub fn expand( + ecx: &mut ExtCtxt<'_>, + _span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec<Annotatable> { + check_builtin_macro_attribute(ecx, meta_item, sym::global_allocator); + + let not_static = |item: Annotatable| { + ecx.sess.parse_sess.span_diagnostic.span_err(item.span(), "allocators must be statics"); + vec![item] + }; + let item = match item { + Annotatable::Item(item) => match item.kind { + ItemKind::Static(..) => item, + _ => return not_static(Annotatable::Item(item)), + }, + _ => return not_static(item), + }; + + // Generate a bunch of new items using the AllocFnFactory + let span = ecx.with_def_site_ctxt(item.span); + let f = AllocFnFactory { span, kind: AllocatorKind::Global, global: item.ident, cx: ecx }; + + // Generate item statements for the allocator methods. + let stmts = ALLOCATOR_METHODS.iter().map(|method| f.allocator_fn(method)).collect(); + + // Generate anonymous constant serving as container for the allocator methods. + let const_ty = ecx.ty(span, TyKind::Tup(Vec::new())); + let const_body = ecx.expr_block(ecx.block(span, stmts)); + let const_item = ecx.item_const(span, Ident::new(kw::Underscore, span), const_ty, const_body); + + // Return the original item and the new methods. + vec![Annotatable::Item(item), Annotatable::Item(const_item)] +} + +struct AllocFnFactory<'a, 'b> { + span: Span, + kind: AllocatorKind, + global: Ident, + cx: &'b ExtCtxt<'a>, +} + +impl AllocFnFactory<'_, '_> { + fn allocator_fn(&self, method: &AllocatorMethod) -> Stmt { + let mut abi_args = Vec::new(); + let mut i = 0; + let mut mk = || { + let name = Ident::from_str_and_span(&format!("arg{}", i), self.span); + i += 1; + name + }; + let args = method.inputs.iter().map(|ty| self.arg_ty(ty, &mut abi_args, &mut mk)).collect(); + let result = self.call_allocator(method.name, args); + let (output_ty, output_expr) = self.ret_ty(&method.output, result); + let decl = self.cx.fn_decl(abi_args, ast::FnRetTy::Ty(output_ty)); + let header = FnHeader { unsafety: Unsafe::Yes(self.span), ..FnHeader::default() }; + let sig = FnSig { decl, header, span: self.span }; + let block = Some(self.cx.block_expr(output_expr)); + let kind = ItemKind::Fn(ast::Defaultness::Final, sig, Generics::default(), block); + let item = self.cx.item( + self.span, + Ident::from_str_and_span(&self.kind.fn_name(method.name), self.span), + self.attrs(), + kind, + ); + self.cx.stmt_item(self.span, item) + } + + fn call_allocator(&self, method: Symbol, mut args: Vec<P<Expr>>) -> P<Expr> { + let method = self.cx.std_path(&[sym::alloc, sym::GlobalAlloc, method]); + let method = self.cx.expr_path(self.cx.path(self.span, method)); + let allocator = self.cx.path_ident(self.span, self.global); + let allocator = self.cx.expr_path(allocator); + let allocator = self.cx.expr_addr_of(self.span, allocator); + args.insert(0, allocator); + + self.cx.expr_call(self.span, method, args) + } + + fn attrs(&self) -> Vec<Attribute> { + let special = sym::rustc_std_internal_symbol; + let special = self.cx.meta_word(self.span, special); + vec![self.cx.attribute(special)] + } + + fn arg_ty( + &self, + ty: &AllocatorTy, + args: &mut Vec<Param>, + ident: &mut dyn FnMut() -> Ident, + ) -> P<Expr> { + match *ty { + AllocatorTy::Layout => { + let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span)); + let ty_usize = self.cx.ty_path(usize); + let size = ident(); + let align = ident(); + args.push(self.cx.param(self.span, size, ty_usize.clone())); + args.push(self.cx.param(self.span, align, ty_usize)); + + let layout_new = + self.cx.std_path(&[sym::alloc, sym::Layout, sym::from_size_align_unchecked]); + let layout_new = self.cx.expr_path(self.cx.path(self.span, layout_new)); + let size = self.cx.expr_ident(self.span, size); + let align = self.cx.expr_ident(self.span, align); + let layout = self.cx.expr_call(self.span, layout_new, vec![size, align]); + layout + } + + AllocatorTy::Ptr => { + let ident = ident(); + args.push(self.cx.param(self.span, ident, self.ptr_u8())); + let arg = self.cx.expr_ident(self.span, ident); + self.cx.expr_cast(self.span, arg, self.ptr_u8()) + } + + AllocatorTy::Usize => { + let ident = ident(); + args.push(self.cx.param(self.span, ident, self.usize())); + self.cx.expr_ident(self.span, ident) + } + + AllocatorTy::ResultPtr | AllocatorTy::Unit => { + panic!("can't convert AllocatorTy to an argument") + } + } + } + + fn ret_ty(&self, ty: &AllocatorTy, expr: P<Expr>) -> (P<Ty>, P<Expr>) { + match *ty { + AllocatorTy::ResultPtr => { + // We're creating: + // + // #expr as *mut u8 + + let expr = self.cx.expr_cast(self.span, expr, self.ptr_u8()); + (self.ptr_u8(), expr) + } + + AllocatorTy::Unit => (self.cx.ty(self.span, TyKind::Tup(Vec::new())), expr), + + AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { + panic!("can't convert `AllocatorTy` to an output") + } + } + } + + fn usize(&self) -> P<Ty> { + let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span)); + self.cx.ty_path(usize) + } + + fn ptr_u8(&self) -> P<Ty> { + let u8 = self.cx.path_ident(self.span, Ident::new(sym::u8, self.span)); + let ty_u8 = self.cx.ty_path(u8); + self.cx.ty_ptr(self.span, ty_u8, Mutability::Mut) + } +} diff --git a/compiler/rustc_builtin_macros/src/global_asm.rs b/compiler/rustc_builtin_macros/src/global_asm.rs new file mode 100644 index 00000000000..2465f33622e --- /dev/null +++ b/compiler/rustc_builtin_macros/src/global_asm.rs @@ -0,0 +1,65 @@ +//! Module-level assembly support. +//! +//! The macro defined here allows you to specify "top-level", +//! "file-scoped", or "module-level" assembly. These synonyms +//! all correspond to LLVM's module-level inline assembly instruction. +//! +//! For example, `global_asm!("some assembly here")` codegens to +//! LLVM's `module asm "some assembly here"`. All of LLVM's caveats +//! therefore apply. + +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_errors::DiagnosticBuilder; +use rustc_expand::base::{self, *}; +use rustc_span::source_map::respan; +use rustc_span::symbol::Ident; +use rustc_span::Span; +use smallvec::smallvec; + +pub fn expand_global_asm<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + match parse_global_asm(cx, sp, tts) { + Ok(Some(global_asm)) => MacEager::items(smallvec![P(ast::Item { + ident: Ident::invalid(), + attrs: Vec::new(), + id: ast::DUMMY_NODE_ID, + kind: ast::ItemKind::GlobalAsm(P(global_asm)), + vis: respan(sp.shrink_to_lo(), ast::VisibilityKind::Inherited), + span: cx.with_def_site_ctxt(sp), + tokens: None, + })]), + Ok(None) => DummyResult::any(sp), + Err(mut err) => { + err.emit(); + DummyResult::any(sp) + } + } +} + +fn parse_global_asm<'a>( + cx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> Result<Option<ast::GlobalAsm>, DiagnosticBuilder<'a>> { + let mut p = cx.new_parser_from_tts(tts); + + if p.token == token::Eof { + let mut err = cx.struct_span_err(sp, "macro requires a string literal as an argument"); + err.span_label(sp, "string literal required"); + return Err(err); + } + + let expr = p.parse_expr()?; + let (asm, _) = match expr_to_string(cx, expr, "inline assembly must be a string literal") { + Some((s, st)) => (s, st), + None => return Ok(None), + }; + + Ok(Some(ast::GlobalAsm { asm })) +} diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs new file mode 100644 index 00000000000..87be6d1743a --- /dev/null +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -0,0 +1,113 @@ +//! This crate contains implementations of built-in macros and other code generating facilities +//! injecting code into the crate before it is lowered to HIR. + +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")] +#![feature(bool_to_option)] +#![feature(crate_visibility_modifier)] +#![feature(decl_macro)] +#![feature(nll)] +#![feature(or_patterns)] +#![feature(proc_macro_internals)] +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use crate::deriving::*; + +use rustc_expand::base::{MacroExpanderFn, ResolverExpand, SyntaxExtension, SyntaxExtensionKind}; +use rustc_expand::proc_macro::BangProcMacro; +use rustc_span::edition::Edition; +use rustc_span::symbol::{sym, Ident}; + +mod asm; +mod assert; +mod cfg; +mod cfg_accessible; +mod compile_error; +mod concat; +mod concat_idents; +mod deriving; +mod env; +mod format; +mod format_foreign; +mod global_allocator; +mod global_asm; +mod llvm_asm; +mod log_syntax; +mod source_util; +mod test; +mod trace_macros; +mod util; + +pub mod cmdline_attrs; +pub mod proc_macro_harness; +pub mod standard_library_imports; +pub mod test_harness; + +pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand, edition: Edition) { + let mut register = |name, kind| { + resolver.register_builtin_macro( + Ident::with_dummy_span(name), + SyntaxExtension { is_builtin: true, ..SyntaxExtension::default(kind, edition) }, + ) + }; + macro register_bang($($name:ident: $f:expr,)*) { + $(register(sym::$name, SyntaxExtensionKind::LegacyBang(Box::new($f as MacroExpanderFn)));)* + } + macro register_attr($($name:ident: $f:expr,)*) { + $(register(sym::$name, SyntaxExtensionKind::LegacyAttr(Box::new($f)));)* + } + macro register_derive($($name:ident: $f:expr,)*) { + $(register(sym::$name, SyntaxExtensionKind::LegacyDerive(Box::new(BuiltinDerive($f))));)* + } + + register_bang! { + asm: asm::expand_asm, + assert: assert::expand_assert, + cfg: cfg::expand_cfg, + column: source_util::expand_column, + compile_error: compile_error::expand_compile_error, + concat_idents: concat_idents::expand_concat_idents, + concat: concat::expand_concat, + env: env::expand_env, + file: source_util::expand_file, + format_args_nl: format::expand_format_args_nl, + format_args: format::expand_format_args, + global_asm: global_asm::expand_global_asm, + include_bytes: source_util::expand_include_bytes, + include_str: source_util::expand_include_str, + include: source_util::expand_include, + line: source_util::expand_line, + llvm_asm: llvm_asm::expand_llvm_asm, + log_syntax: log_syntax::expand_log_syntax, + module_path: source_util::expand_mod, + option_env: env::expand_option_env, + stringify: source_util::expand_stringify, + trace_macros: trace_macros::expand_trace_macros, + } + + register_attr! { + bench: test::expand_bench, + cfg_accessible: cfg_accessible::Expander, + global_allocator: global_allocator::expand, + test: test::expand_test, + test_case: test::expand_test_case, + } + + register_derive! { + Clone: clone::expand_deriving_clone, + Copy: bounds::expand_deriving_copy, + Debug: debug::expand_deriving_debug, + Default: default::expand_deriving_default, + Eq: eq::expand_deriving_eq, + Hash: hash::expand_deriving_hash, + Ord: ord::expand_deriving_ord, + PartialEq: partial_eq::expand_deriving_partial_eq, + PartialOrd: partial_ord::expand_deriving_partial_ord, + RustcDecodable: decodable::expand_deriving_rustc_decodable, + RustcEncodable: encodable::expand_deriving_rustc_encodable, + } + + let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote); + register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client }))); +} diff --git a/compiler/rustc_builtin_macros/src/llvm_asm.rs b/compiler/rustc_builtin_macros/src/llvm_asm.rs new file mode 100644 index 00000000000..db73fdbe24f --- /dev/null +++ b/compiler/rustc_builtin_macros/src/llvm_asm.rs @@ -0,0 +1,301 @@ +// Llvm-style inline assembly support. +// +use State::*; + +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::token::{self, Token}; +use rustc_ast::tokenstream::{self, TokenStream}; +use rustc_ast::LlvmAsmDialect; +use rustc_errors::{struct_span_err, DiagnosticBuilder, PResult}; +use rustc_expand::base::*; +use rustc_parse::parser::Parser; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::Span; + +enum State { + Asm, + Outputs, + Inputs, + Clobbers, + Options, + StateNone, +} + +impl State { + fn next(&self) -> State { + match *self { + Asm => Outputs, + Outputs => Inputs, + Inputs => Clobbers, + Clobbers => Options, + Options => StateNone, + StateNone => StateNone, + } + } +} + +const OPTIONS: &[Symbol] = &[sym::volatile, sym::alignstack, sym::intel]; + +pub fn expand_llvm_asm<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn MacResult + 'cx> { + let mut inline_asm = match parse_inline_asm(cx, sp, tts) { + Ok(Some(inline_asm)) => inline_asm, + Ok(None) => return DummyResult::any(sp), + Err(mut err) => { + err.emit(); + return DummyResult::any(sp); + } + }; + + // If there are no outputs, the inline assembly is executed just for its side effects, + // so ensure that it is volatile + if inline_asm.outputs.is_empty() { + inline_asm.volatile = true; + } + + MacEager::expr(P(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind: ast::ExprKind::LlvmInlineAsm(P(inline_asm)), + span: cx.with_def_site_ctxt(sp), + attrs: ast::AttrVec::new(), + tokens: None, + })) +} + +fn parse_asm_str<'a>(p: &mut Parser<'a>) -> PResult<'a, Symbol> { + match p.parse_str_lit() { + Ok(str_lit) => Ok(str_lit.symbol_unescaped), + Err(opt_lit) => { + let span = opt_lit.map_or(p.token.span, |lit| lit.span); + let mut err = p.sess.span_diagnostic.struct_span_err(span, "expected string literal"); + err.span_label(span, "not a string literal"); + Err(err) + } + } +} + +fn parse_inline_asm<'a>( + cx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> Result<Option<ast::LlvmInlineAsm>, DiagnosticBuilder<'a>> { + // Split the tts before the first colon, to avoid `llvm_asm!("x": y)` being + // parsed as `llvm_asm!(z)` with `z = "x": y` which is type ascription. + let first_colon = tts + .trees() + .position(|tt| match tt { + tokenstream::TokenTree::Token(Token { kind: token::Colon | token::ModSep, .. }) => true, + _ => false, + }) + .unwrap_or(tts.len()); + let mut p = cx.new_parser_from_tts(tts.trees().skip(first_colon).collect()); + let mut asm = kw::Invalid; + let mut asm_str_style = None; + let mut outputs = Vec::new(); + let mut inputs = Vec::new(); + let mut clobs = Vec::new(); + let mut volatile = false; + let mut alignstack = false; + let mut dialect = LlvmAsmDialect::Att; + + let mut state = Asm; + + 'statement: loop { + match state { + Asm => { + if asm_str_style.is_some() { + // If we already have a string with instructions, + // ending up in Asm state again is an error. + return Err(struct_span_err!( + cx.sess.parse_sess.span_diagnostic, + sp, + E0660, + "malformed inline assembly" + )); + } + // Nested parser, stop before the first colon (see above). + let mut p2 = cx.new_parser_from_tts(tts.trees().take(first_colon).collect()); + + if p2.token == token::Eof { + let mut err = + cx.struct_span_err(sp, "macro requires a string literal as an argument"); + err.span_label(sp, "string literal required"); + return Err(err); + } + + let expr = p2.parse_expr()?; + let (s, style) = + match expr_to_string(cx, expr, "inline assembly must be a string literal") { + Some((s, st)) => (s, st), + None => return Ok(None), + }; + + // This is most likely malformed. + if p2.token != token::Eof { + let mut extra_tts = p2.parse_all_token_trees()?; + extra_tts.extend(tts.trees().skip(first_colon)); + p = cx.new_parser_from_tts(extra_tts.into_iter().collect()); + } + + asm = s; + asm_str_style = Some(style); + } + Outputs => { + while p.token != token::Eof && p.token != token::Colon && p.token != token::ModSep { + if !outputs.is_empty() { + p.eat(&token::Comma); + } + + let constraint = parse_asm_str(&mut p)?; + + let span = p.prev_token.span; + + p.expect(&token::OpenDelim(token::Paren))?; + let expr = p.parse_expr()?; + p.expect(&token::CloseDelim(token::Paren))?; + + // Expands a read+write operand into two operands. + // + // Use '+' modifier when you want the same expression + // to be both an input and an output at the same time. + // It's the opposite of '=&' which means that the memory + // cannot be shared with any other operand (usually when + // a register is clobbered early.) + let constraint_str = constraint.as_str(); + let mut ch = constraint_str.chars(); + let output = match ch.next() { + Some('=') => None, + Some('+') => Some(Symbol::intern(&format!("={}", ch.as_str()))), + _ => { + struct_span_err!( + cx.sess.parse_sess.span_diagnostic, + span, + E0661, + "output operand constraint lacks '=' or '+'" + ) + .emit(); + None + } + }; + + let is_rw = output.is_some(); + let is_indirect = constraint_str.contains('*'); + outputs.push(ast::LlvmInlineAsmOutput { + constraint: output.unwrap_or(constraint), + expr, + is_rw, + is_indirect, + }); + } + } + Inputs => { + while p.token != token::Eof && p.token != token::Colon && p.token != token::ModSep { + if !inputs.is_empty() { + p.eat(&token::Comma); + } + + let constraint = parse_asm_str(&mut p)?; + + if constraint.as_str().starts_with('=') { + struct_span_err!( + cx.sess.parse_sess.span_diagnostic, + p.prev_token.span, + E0662, + "input operand constraint contains '='" + ) + .emit(); + } else if constraint.as_str().starts_with('+') { + struct_span_err!( + cx.sess.parse_sess.span_diagnostic, + p.prev_token.span, + E0663, + "input operand constraint contains '+'" + ) + .emit(); + } + + p.expect(&token::OpenDelim(token::Paren))?; + let input = p.parse_expr()?; + p.expect(&token::CloseDelim(token::Paren))?; + + inputs.push((constraint, input)); + } + } + Clobbers => { + while p.token != token::Eof && p.token != token::Colon && p.token != token::ModSep { + if !clobs.is_empty() { + p.eat(&token::Comma); + } + + let s = parse_asm_str(&mut p)?; + + if OPTIONS.iter().any(|&opt| s == opt) { + cx.span_warn(p.prev_token.span, "expected a clobber, found an option"); + } else if s.as_str().starts_with('{') || s.as_str().ends_with('}') { + struct_span_err!( + cx.sess.parse_sess.span_diagnostic, + p.prev_token.span, + E0664, + "clobber should not be surrounded by braces" + ) + .emit(); + } + + clobs.push(s); + } + } + Options => { + let option = parse_asm_str(&mut p)?; + + if option == sym::volatile { + // Indicates that the inline assembly has side effects + // and must not be optimized out along with its outputs. + volatile = true; + } else if option == sym::alignstack { + alignstack = true; + } else if option == sym::intel { + dialect = LlvmAsmDialect::Intel; + } else { + cx.span_warn(p.prev_token.span, "unrecognized option"); + } + + if p.token == token::Comma { + p.eat(&token::Comma); + } + } + StateNone => (), + } + + loop { + // MOD_SEP is a double colon '::' without space in between. + // When encountered, the state must be advanced twice. + match (&p.token.kind, state.next(), state.next().next()) { + (&token::Colon, StateNone, _) | (&token::ModSep, _, StateNone) => { + p.bump(); + break 'statement; + } + (&token::Colon, st, _) | (&token::ModSep, _, st) => { + p.bump(); + state = st; + } + (&token::Eof, ..) => break 'statement, + _ => break, + } + } + } + + Ok(Some(ast::LlvmInlineAsm { + asm, + asm_str_style: asm_str_style.unwrap(), + outputs, + inputs, + clobbers: clobs, + volatile, + alignstack, + dialect, + })) +} 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..ede34a76125 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/log_syntax.rs @@ -0,0 +1,14 @@ +use rustc_ast::tokenstream::TokenStream; +use rustc_ast_pretty::pprust; +use rustc_expand::base; + +pub fn expand_log_syntax<'cx>( + _cx: &'cx mut base::ExtCtxt<'_>, + sp: rustc_span::Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + println!("{}", pprust::tts_to_string(&tts)); + + // any so that `log_syntax` can be invoked as an expression and item. + base::DummyResult::any_valid(sp) +} diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs new file mode 100644 index 00000000000..0c6769906f3 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -0,0 +1,492 @@ +use std::mem; + +use rustc_ast::attr; +use rustc_ast::ptr::P; +use rustc_ast::visit::{self, Visitor}; +use rustc_ast::{self as ast, NodeId}; +use rustc_ast_pretty::pprust; +use rustc_expand::base::{ExtCtxt, ResolverExpand}; +use rustc_expand::expand::{AstFragment, ExpansionConfig}; +use rustc_session::Session; +use rustc_span::hygiene::AstPass; +use rustc_span::source_map::SourceMap; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use smallvec::smallvec; +use std::cell::RefCell; + +struct ProcMacroDerive { + id: NodeId, + trait_name: Symbol, + function_name: Ident, + span: Span, + attrs: Vec<Symbol>, +} + +enum ProcMacroDefType { + Attr, + Bang, +} + +struct ProcMacroDef { + id: NodeId, + function_name: Ident, + span: Span, + def_type: ProcMacroDefType, +} + +enum ProcMacro { + Derive(ProcMacroDerive), + Def(ProcMacroDef), +} + +struct CollectProcMacros<'a> { + sess: &'a Session, + macros: Vec<ProcMacro>, + in_root: bool, + handler: &'a rustc_errors::Handler, + source_map: &'a SourceMap, + is_proc_macro_crate: bool, + is_test_crate: bool, +} + +pub fn inject( + sess: &Session, + resolver: &mut dyn ResolverExpand, + mut krate: ast::Crate, + is_proc_macro_crate: bool, + has_proc_macro_decls: bool, + is_test_crate: bool, + num_crate_types: usize, + handler: &rustc_errors::Handler, +) -> ast::Crate { + let ecfg = ExpansionConfig::default("proc_macro".to_string()); + let mut cx = ExtCtxt::new(sess, ecfg, resolver, None); + + let mut collect = CollectProcMacros { + sess, + macros: Vec::new(), + in_root: true, + handler, + source_map: sess.source_map(), + is_proc_macro_crate, + is_test_crate, + }; + + if has_proc_macro_decls || is_proc_macro_crate { + visit::walk_crate(&mut collect, &krate); + } + let macros = collect.macros; + + if !is_proc_macro_crate { + return krate; + } + + if num_crate_types > 1 { + handler.err("cannot mix `proc-macro` crate type with others"); + } + + if is_test_crate { + return krate; + } + + let decls = mk_decls(&mut krate, &mut cx, ¯os); + krate.module.items.push(decls); + + krate +} + +impl<'a> CollectProcMacros<'a> { + fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) { + if self.is_proc_macro_crate && self.in_root && vis.node.is_pub() { + self.handler.span_err( + sp, + "`proc-macro` crate types currently cannot export any items other \ + than functions tagged with `#[proc_macro]`, `#[proc_macro_derive]`, \ + or `#[proc_macro_attribute]`", + ); + } + } + + fn collect_custom_derive(&mut self, item: &'a ast::Item, attr: &'a ast::Attribute) { + // Once we've located the `#[proc_macro_derive]` attribute, verify + // that it's of the form `#[proc_macro_derive(Foo)]` or + // `#[proc_macro_derive(Foo, attributes(A, ..))]` + let list = match attr.meta_item_list() { + Some(list) => list, + None => return, + }; + if list.len() != 1 && list.len() != 2 { + self.handler.span_err(attr.span, "attribute must have either one or two arguments"); + return; + } + let trait_attr = match list[0].meta_item() { + Some(meta_item) => meta_item, + _ => { + self.handler.span_err(list[0].span(), "not a meta item"); + return; + } + }; + let trait_ident = match trait_attr.ident() { + Some(trait_ident) if trait_attr.is_word() => trait_ident, + _ => { + self.handler.span_err(trait_attr.span, "must only be one word"); + return; + } + }; + + if !trait_ident.name.can_be_raw() { + self.handler.span_err( + trait_attr.span, + &format!("`{}` cannot be a name of derive macro", trait_ident), + ); + } + + let attributes_attr = list.get(1); + let proc_attrs: Vec<_> = if let Some(attr) = attributes_attr { + if !attr.has_name(sym::attributes) { + self.handler.span_err(attr.span(), "second argument must be `attributes`") + } + attr.meta_item_list() + .unwrap_or_else(|| { + self.handler + .span_err(attr.span(), "attribute must be of form: `attributes(foo, bar)`"); + &[] + }) + .iter() + .filter_map(|attr| { + let attr = match attr.meta_item() { + Some(meta_item) => meta_item, + _ => { + self.handler.span_err(attr.span(), "not a meta item"); + return None; + } + }; + + let ident = match attr.ident() { + Some(ident) if attr.is_word() => ident, + _ => { + self.handler.span_err(attr.span, "must only be one word"); + return None; + } + }; + if !ident.name.can_be_raw() { + self.handler.span_err( + attr.span, + &format!("`{}` cannot be a name of derive helper attribute", ident), + ); + } + + Some(ident.name) + }) + .collect() + } else { + Vec::new() + }; + + if self.in_root && item.vis.node.is_pub() { + self.macros.push(ProcMacro::Derive(ProcMacroDerive { + id: item.id, + span: item.span, + trait_name: trait_ident.name, + function_name: item.ident, + attrs: proc_attrs, + })); + } else { + let msg = if !self.in_root { + "functions tagged with `#[proc_macro_derive]` must \ + currently reside in the root of the crate" + } else { + "functions tagged with `#[proc_macro_derive]` must be `pub`" + }; + self.handler.span_err(self.source_map.guess_head_span(item.span), msg); + } + } + + fn collect_attr_proc_macro(&mut self, item: &'a ast::Item) { + if self.in_root && item.vis.node.is_pub() { + self.macros.push(ProcMacro::Def(ProcMacroDef { + id: item.id, + span: item.span, + function_name: item.ident, + def_type: ProcMacroDefType::Attr, + })); + } else { + let msg = if !self.in_root { + "functions tagged with `#[proc_macro_attribute]` must \ + currently reside in the root of the crate" + } else { + "functions tagged with `#[proc_macro_attribute]` must be `pub`" + }; + self.handler.span_err(self.source_map.guess_head_span(item.span), msg); + } + } + + fn collect_bang_proc_macro(&mut self, item: &'a ast::Item) { + if self.in_root && item.vis.node.is_pub() { + self.macros.push(ProcMacro::Def(ProcMacroDef { + id: item.id, + span: item.span, + function_name: item.ident, + def_type: ProcMacroDefType::Bang, + })); + } else { + let msg = if !self.in_root { + "functions tagged with `#[proc_macro]` must \ + currently reside in the root of the crate" + } else { + "functions tagged with `#[proc_macro]` must be `pub`" + }; + self.handler.span_err(self.source_map.guess_head_span(item.span), msg); + } + } +} + +impl<'a> Visitor<'a> for CollectProcMacros<'a> { + fn visit_item(&mut self, item: &'a ast::Item) { + if let ast::ItemKind::MacroDef(..) = item.kind { + if self.is_proc_macro_crate && self.sess.contains_name(&item.attrs, sym::macro_export) { + let msg = + "cannot export macro_rules! macros from a `proc-macro` crate type currently"; + self.handler.span_err(self.source_map.guess_head_span(item.span), msg); + } + } + + // First up, make sure we're checking a bare function. If we're not then + // we're just not interested in this item. + // + // If we find one, try to locate a `#[proc_macro_derive]` attribute on it. + let is_fn = match item.kind { + ast::ItemKind::Fn(..) => true, + _ => false, + }; + + let mut found_attr: Option<&'a ast::Attribute> = None; + + for attr in &item.attrs { + if self.sess.is_proc_macro_attr(&attr) { + if let Some(prev_attr) = found_attr { + let prev_item = prev_attr.get_normal_item(); + let item = attr.get_normal_item(); + let path_str = pprust::path_to_string(&item.path); + let msg = if item.path.segments[0].ident.name + == prev_item.path.segments[0].ident.name + { + format!( + "only one `#[{}]` attribute is allowed on any given function", + path_str, + ) + } else { + format!( + "`#[{}]` and `#[{}]` attributes cannot both be applied + to the same function", + path_str, + pprust::path_to_string(&prev_item.path), + ) + }; + + self.handler + .struct_span_err(attr.span, &msg) + .span_label(prev_attr.span, "previous attribute here") + .emit(); + + return; + } + + found_attr = Some(attr); + } + } + + let attr = match found_attr { + None => { + 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; + } + Some(attr) => attr, + }; + + if !is_fn { + let msg = format!( + "the `#[{}]` attribute may only be used on bare functions", + pprust::path_to_string(&attr.get_normal_item().path), + ); + + self.handler.span_err(attr.span, &msg); + return; + } + + if self.is_test_crate { + return; + } + + if !self.is_proc_macro_crate { + let msg = format!( + "the `#[{}]` attribute is only usable with crates of the `proc-macro` crate type", + pprust::path_to_string(&attr.get_normal_item().path), + ); + + self.handler.span_err(attr.span, &msg); + return; + } + + if self.sess.check_name(attr, sym::proc_macro_derive) { + self.collect_custom_derive(item, attr); + } else if self.sess.check_name(attr, sym::proc_macro_attribute) { + self.collect_attr_proc_macro(item); + } else if self.sess.check_name(attr, 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; + } + + fn visit_mac(&mut self, mac: &'a ast::MacCall) { + visit::walk_mac(self, mac) + } +} + +// Creates a new module which looks like: +// +// const _: () = { +// extern crate proc_macro; +// +// use proc_macro::bridge::client::ProcMacro; +// +// #[rustc_proc_macro_decls] +// #[allow(deprecated)] +// static DECLS: &[ProcMacro] = &[ +// ProcMacro::custom_derive($name_trait1, &[], ::$name1); +// ProcMacro::custom_derive($name_trait2, &["attribute_name"], ::$name2); +// // ... +// ]; +// } +fn mk_decls( + ast_krate: &mut ast::Crate, + cx: &mut ExtCtxt<'_>, + macros: &[ProcMacro], +) -> P<ast::Item> { + // We're the ones filling in this Vec, + // so it should be empty to start with + assert!(ast_krate.proc_macros.is_empty()); + + 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); + + let proc_macro = Ident::new(sym::proc_macro, span); + let krate = cx.item(span, proc_macro, Vec::new(), ast::ItemKind::ExternCrate(None)); + + let bridge = Ident::new(sym::bridge, span); + let client = Ident::new(sym::client, span); + let proc_macro_ty = Ident::new(sym::ProcMacro, span); + let custom_derive = Ident::new(sym::custom_derive, span); + let attr = Ident::new(sym::attr, span); + let bang = Ident::new(sym::bang, span); + + let krate_ref = RefCell::new(ast_krate); + + // We add NodeIds to 'krate.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 = { + let local_path = + |sp: Span, name| cx.expr_path(cx.path(sp.with_ctxt(span.ctxt()), vec![name])); + let proc_macro_ty_method_path = |method| { + cx.expr_path(cx.path(span, vec![proc_macro, bridge, client, proc_macro_ty, method])) + }; + macros + .iter() + .map(|m| match m { + ProcMacro::Derive(cd) => { + krate_ref.borrow_mut().proc_macros.push(cd.id); + cx.expr_call( + span, + proc_macro_ty_method_path(custom_derive), + vec![ + cx.expr_str(cd.span, cd.trait_name), + cx.expr_vec_slice( + span, + cd.attrs + .iter() + .map(|&s| cx.expr_str(cd.span, s)) + .collect::<Vec<_>>(), + ), + local_path(cd.span, cd.function_name), + ], + ) + } + ProcMacro::Def(ca) => { + krate_ref.borrow_mut().proc_macros.push(ca.id); + let ident = match ca.def_type { + ProcMacroDefType::Attr => attr, + ProcMacroDefType::Bang => bang, + }; + + cx.expr_call( + span, + proc_macro_ty_method_path(ident), + vec![ + cx.expr_str(ca.span, ca.function_name.name), + local_path(ca.span, ca.function_name), + ], + ) + } + }) + .collect() + }; + + let decls_static = cx + .item_static( + span, + Ident::new(sym::_DECLS, span), + cx.ty_rptr( + span, + cx.ty( + span, + ast::TyKind::Slice( + cx.ty_path(cx.path(span, vec![proc_macro, bridge, client, proc_macro_ty])), + ), + ), + None, + ast::Mutability::Not, + ), + ast::Mutability::Not, + cx.expr_vec_slice(span, decls), + ) + .map(|mut i| { + let attr = cx.meta_word(span, sym::rustc_proc_macro_decls); + i.attrs.push(cx.attribute(attr)); + + let deprecated_attr = attr::mk_nested_word_item(Ident::new(sym::deprecated, span)); + let allow_deprecated_attr = + attr::mk_list_item(Ident::new(sym::allow, span), vec![deprecated_attr]); + i.attrs.push(cx.attribute(allow_deprecated_attr)); + + i + }); + + let block = cx.expr_block( + cx.block(span, vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]), + ); + + let anon_constant = cx.item_const( + span, + Ident::new(kw::Underscore, span), + cx.ty(span, ast::TyKind::Tup(Vec::new())), + block, + ); + + // Integrate the new item into existing module structures. + let items = AstFragment::Items(smallvec![anon_constant]); + cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap() +} diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs new file mode 100644 index 00000000000..70753208af3 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -0,0 +1,225 @@ +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast_pretty::pprust; +use rustc_expand::base::{self, *}; +use rustc_expand::module::DirectoryOwnership; +use rustc_parse::{self, new_parser_from_file, parser::Parser}; +use rustc_session::lint::builtin::INCOMPLETE_INCLUDE; +use rustc_span::symbol::Symbol; +use rustc_span::{self, Pos, Span}; + +use smallvec::SmallVec; +use std::rc::Rc; + +use rustc_data_structures::sync::Lrc; + +// These macros all relate to the file system; they either return +// the column/row/filename of the expression, or they include +// a given file into the current one. + +/// line!(): expands to the current line number +pub fn expand_line( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + base::check_zero_tts(cx, sp, tts, "line!"); + + let topmost = cx.expansion_cause().unwrap_or(sp); + let loc = cx.source_map().lookup_char_pos(topmost.lo()); + + base::MacEager::expr(cx.expr_u32(topmost, loc.line as u32)) +} + +/* column!(): expands to the current column number */ +pub fn expand_column( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + base::check_zero_tts(cx, sp, tts, "column!"); + + let topmost = cx.expansion_cause().unwrap_or(sp); + let loc = cx.source_map().lookup_char_pos(topmost.lo()); + + base::MacEager::expr(cx.expr_u32(topmost, loc.col.to_usize() as u32 + 1)) +} + +/// file!(): expands to the current filename */ +/// The source_file (`loc.file`) contains a bunch more information we could spit +/// out if we wanted. +pub fn expand_file( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + base::check_zero_tts(cx, sp, tts, "file!"); + + let topmost = cx.expansion_cause().unwrap_or(sp); + let loc = cx.source_map().lookup_char_pos(topmost.lo()); + base::MacEager::expr(cx.expr_str(topmost, Symbol::intern(&loc.file.name.to_string()))) +} + +pub fn expand_stringify( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + let s = pprust::tts_to_string(&tts); + base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&s))) +} + +pub fn expand_mod( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + base::check_zero_tts(cx, sp, tts, "module_path!"); + let mod_path = &cx.current_expansion.module.mod_path; + let string = mod_path.iter().map(|x| x.to_string()).collect::<Vec<String>>().join("::"); + + base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&string))) +} + +/// include! : parse the given file as an expr +/// This is generally a bad idea because it's going to behave +/// unhygienically. +pub fn expand_include<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'cx> { + let sp = cx.with_def_site_ctxt(sp); + let file = match get_single_str_from_tts(cx, sp, tts, "include!") { + Some(f) => f, + None => return DummyResult::any(sp), + }; + // The file will be added to the code map by the parser + let mut file = match cx.resolve_path(file, sp) { + Ok(f) => f, + Err(mut err) => { + err.emit(); + return DummyResult::any(sp); + } + }; + let p = new_parser_from_file(cx.parse_sess(), &file, Some(sp)); + + // If in the included file we have e.g., `mod bar;`, + // then the path of `bar.rs` should be relative to the directory of `file`. + // See https://github.com/rust-lang/rust/pull/69838/files#r395217057 for a discussion. + // `MacroExpander::fully_expand_fragment` later restores, so "stack discipline" is maintained. + file.pop(); + cx.current_expansion.directory_ownership = DirectoryOwnership::Owned { relative: None }; + let mod_path = cx.current_expansion.module.mod_path.clone(); + cx.current_expansion.module = Rc::new(ModuleData { mod_path, directory: file }); + + struct ExpandResult<'a> { + p: Parser<'a>, + node_id: ast::NodeId, + } + impl<'a> base::MacResult for ExpandResult<'a> { + fn make_expr(mut self: Box<ExpandResult<'a>>) -> Option<P<ast::Expr>> { + let r = base::parse_expr(&mut self.p)?; + if self.p.token != token::Eof { + self.p.sess.buffer_lint( + &INCOMPLETE_INCLUDE, + self.p.token.span, + self.node_id, + "include macro expected single expression in source", + ); + } + Some(r) + } + + fn make_items(mut self: Box<ExpandResult<'a>>) -> Option<SmallVec<[P<ast::Item>; 1]>> { + let mut ret = SmallVec::new(); + while self.p.token != token::Eof { + match self.p.parse_item() { + Err(mut err) => { + err.emit(); + break; + } + Ok(Some(item)) => ret.push(item), + Ok(None) => { + let token = pprust::token_to_string(&self.p.token); + let msg = format!("expected item, found `{}`", token); + self.p.struct_span_err(self.p.token.span, &msg).emit(); + break; + } + } + } + Some(ret) + } + } + + Box::new(ExpandResult { p, node_id: cx.resolver.lint_node_id(cx.current_expansion.id) }) +} + +// include_str! : read the given file, insert it as a literal string expr +pub fn expand_include_str( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + let file = match get_single_str_from_tts(cx, sp, tts, "include_str!") { + Some(f) => f, + None => return DummyResult::any(sp), + }; + let file = match cx.resolve_path(file, sp) { + Ok(f) => f, + Err(mut err) => { + err.emit(); + return DummyResult::any(sp); + } + }; + match cx.source_map().load_binary_file(&file) { + Ok(bytes) => match std::str::from_utf8(&bytes) { + Ok(src) => { + let interned_src = Symbol::intern(&src); + base::MacEager::expr(cx.expr_str(sp, interned_src)) + } + Err(_) => { + cx.span_err(sp, &format!("{} wasn't a utf-8 file", file.display())); + DummyResult::any(sp) + } + }, + Err(e) => { + cx.span_err(sp, &format!("couldn't read {}: {}", file.display(), e)); + DummyResult::any(sp) + } + } +} + +pub fn expand_include_bytes( + cx: &mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let sp = cx.with_def_site_ctxt(sp); + let file = match get_single_str_from_tts(cx, sp, tts, "include_bytes!") { + Some(f) => f, + None => return DummyResult::any(sp), + }; + let file = match cx.resolve_path(file, sp) { + Ok(f) => f, + Err(mut err) => { + err.emit(); + return DummyResult::any(sp); + } + }; + match cx.source_map().load_binary_file(&file) { + Ok(bytes) => base::MacEager::expr(cx.expr_lit(sp, ast::LitKind::ByteStr(Lrc::new(bytes)))), + Err(e) => { + cx.span_err(sp, &format!("couldn't read {}: {}", file.display(), e)); + DummyResult::any(sp) + } + } +} diff --git a/compiler/rustc_builtin_macros/src/standard_library_imports.rs b/compiler/rustc_builtin_macros/src/standard_library_imports.rs new file mode 100644 index 00000000000..e801b5c7b0c --- /dev/null +++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs @@ -0,0 +1,85 @@ +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_expand::base::{ExtCtxt, ResolverExpand}; +use rustc_expand::expand::ExpansionConfig; +use rustc_session::Session; +use rustc_span::edition::Edition; +use rustc_span::hygiene::AstPass; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::DUMMY_SP; + +pub fn inject( + mut krate: ast::Crate, + resolver: &mut dyn ResolverExpand, + sess: &Session, + alt_std_name: Option<Symbol>, +) -> (ast::Crate, Option<Symbol>) { + let rust_2018 = sess.parse_sess.edition >= Edition::Edition2018; + + // the first name in this list is the crate name of the crate with the prelude + let names: &[Symbol] = if sess.contains_name(&krate.attrs, sym::no_core) { + return (krate, None); + } else if sess.contains_name(&krate.attrs, sym::no_std) { + if sess.contains_name(&krate.attrs, sym::compiler_builtins) { + &[sym::core] + } else { + &[sym::core, sym::compiler_builtins] + } + } else { + &[sym::std] + }; + + let expn_id = resolver.expansion_for_ast_pass( + DUMMY_SP, + AstPass::StdImports, + &[sym::prelude_import], + None, + ); + let span = DUMMY_SP.with_def_site_ctxt(expn_id); + let call_site = DUMMY_SP.with_call_site_ctxt(expn_id); + + let ecfg = ExpansionConfig::default("std_lib_injection".to_string()); + let cx = ExtCtxt::new(sess, ecfg, resolver, None); + + // .rev() to preserve ordering above in combination with insert(0, ...) + for &name in names.iter().rev() { + let ident = if rust_2018 { Ident::new(name, span) } else { Ident::new(name, call_site) }; + krate.module.items.insert( + 0, + cx.item( + span, + ident, + vec![cx.attribute(cx.meta_word(span, sym::macro_use))], + ast::ItemKind::ExternCrate(alt_std_name), + ), + ); + } + + // The crates have been injected, the assumption is that the first one is + // the one with the prelude. + let name = names[0]; + + let import_path = if rust_2018 { + [name, sym::prelude, sym::v1].iter().map(|symbol| Ident::new(*symbol, span)).collect() + } else { + [kw::PathRoot, name, sym::prelude, sym::v1] + .iter() + .map(|symbol| Ident::new(*symbol, span)) + .collect() + }; + + let use_item = cx.item( + span, + Ident::invalid(), + vec![cx.attribute(cx.meta_word(span, sym::prelude_import))], + ast::ItemKind::Use(P(ast::UseTree { + prefix: cx.path(span, import_path), + kind: ast::UseTreeKind::Glob, + span, + })), + ); + + krate.module.items.insert(0, use_item); + + (krate, Some(name)) +} diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs new file mode 100644 index 00000000000..8e56e80bba2 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -0,0 +1,471 @@ +/// The expansion from a test function to the appropriate test struct for libtest +/// Ideally, this code would be in libtest but for efficiency and error messages it lives here. +use crate::util::check_builtin_macro_attribute; + +use rustc_ast as ast; +use rustc_ast::attr; +use rustc_ast_pretty::pprust; +use rustc_expand::base::*; +use rustc_session::Session; +use rustc_span::source_map::respan; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; + +use std::iter; + +// #[test_case] is used by custom test authors to mark tests +// When building for test, it needs to make the item public and gensym the name +// Otherwise, we'll omit the item. This behavior means that any item annotated +// with #[test_case] is never addressable. +// +// We mark item with an inert attribute "rustc_test_marker" which the test generation +// logic will pick up on. +pub fn expand_test_case( + ecx: &mut ExtCtxt<'_>, + attr_sp: Span, + meta_item: &ast::MetaItem, + anno_item: Annotatable, +) -> Vec<Annotatable> { + check_builtin_macro_attribute(ecx, meta_item, sym::test_case); + + if !ecx.ecfg.should_test { + return vec![]; + } + + let sp = ecx.with_def_site_ctxt(attr_sp); + let mut item = anno_item.expect_item(); + item = item.map(|mut item| { + item.vis = respan(item.vis.span, ast::VisibilityKind::Public); + item.ident.span = item.ident.span.with_ctxt(sp.ctxt()); + item.attrs.push(ecx.attribute(ecx.meta_word(sp, sym::rustc_test_marker))); + item + }); + + return vec![Annotatable::Item(item)]; +} + +pub fn expand_test( + cx: &mut ExtCtxt<'_>, + attr_sp: Span, + meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec<Annotatable> { + check_builtin_macro_attribute(cx, meta_item, sym::test); + expand_test_or_bench(cx, attr_sp, item, false) +} + +pub fn expand_bench( + cx: &mut ExtCtxt<'_>, + attr_sp: Span, + meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec<Annotatable> { + check_builtin_macro_attribute(cx, meta_item, sym::bench); + expand_test_or_bench(cx, attr_sp, item, true) +} + +pub fn expand_test_or_bench( + cx: &mut ExtCtxt<'_>, + attr_sp: Span, + item: Annotatable, + is_bench: bool, +) -> Vec<Annotatable> { + // If we're not in test configuration, remove the annotated item + if !cx.ecfg.should_test { + return vec![]; + } + + let item = match item { + Annotatable::Item(i) => i, + other => { + cx.struct_span_err( + other.span(), + "`#[test]` attribute is only allowed on non associated functions", + ) + .emit(); + return vec![other]; + } + }; + + if let ast::ItemKind::MacCall(_) = item.kind { + cx.sess.parse_sess.span_diagnostic.span_warn( + item.span, + "`#[test]` attribute should not be used on macros. Use `#[cfg(test)]` instead.", + ); + return vec![Annotatable::Item(item)]; + } + + // has_*_signature will report any errors in the type so compilation + // will fail. We shouldn't try to expand in this case because the errors + // would be spurious. + if (!is_bench && !has_test_signature(cx, &item)) + || (is_bench && !has_bench_signature(cx, &item)) + { + return vec![Annotatable::Item(item)]; + } + + let (sp, attr_sp) = (cx.with_def_site_ctxt(item.span), cx.with_def_site_ctxt(attr_sp)); + + let test_id = Ident::new(sym::test, attr_sp); + + // creates test::$name + let test_path = |name| cx.path(sp, vec![test_id, Ident::from_str_and_span(name, sp)]); + + // creates test::ShouldPanic::$name + let should_panic_path = |name| { + cx.path( + sp, + vec![ + test_id, + Ident::from_str_and_span("ShouldPanic", sp), + Ident::from_str_and_span(name, sp), + ], + ) + }; + + // creates test::TestType::$name + let test_type_path = |name| { + cx.path( + sp, + vec![ + test_id, + Ident::from_str_and_span("TestType", sp), + Ident::from_str_and_span(name, sp), + ], + ) + }; + + // creates $name: $expr + let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr); + + let test_fn = if is_bench { + // A simple ident for a lambda + let b = Ident::from_str_and_span("b", attr_sp); + + cx.expr_call( + sp, + cx.expr_path(test_path("StaticBenchFn")), + vec![ + // |b| self::test::assert_test_result( + cx.lambda1( + sp, + cx.expr_call( + sp, + cx.expr_path(test_path("assert_test_result")), + vec![ + // super::$test_fn(b) + cx.expr_call( + sp, + cx.expr_path(cx.path(sp, vec![item.ident])), + vec![cx.expr_ident(sp, b)], + ), + ], + ), + b, + ), // ) + ], + ) + } else { + cx.expr_call( + sp, + cx.expr_path(test_path("StaticTestFn")), + vec![ + // || { + cx.lambda0( + sp, + // test::assert_test_result( + cx.expr_call( + sp, + cx.expr_path(test_path("assert_test_result")), + vec![ + // $test_fn() + cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![]), // ) + ], + ), // } + ), // ) + ], + ) + }; + + let mut test_const = cx.item( + sp, + Ident::new(item.ident.name, sp), + vec![ + // #[cfg(test)] + cx.attribute(attr::mk_list_item( + Ident::new(sym::cfg, attr_sp), + vec![attr::mk_nested_word_item(Ident::new(sym::test, attr_sp))], + )), + // #[rustc_test_marker] + cx.attribute(cx.meta_word(attr_sp, sym::rustc_test_marker)), + ], + // const $ident: test::TestDescAndFn = + ast::ItemKind::Const( + ast::Defaultness::Final, + cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), + // test::TestDescAndFn { + Some( + cx.expr_struct( + sp, + test_path("TestDescAndFn"), + vec![ + // desc: test::TestDesc { + field( + "desc", + cx.expr_struct( + sp, + test_path("TestDesc"), + vec![ + // name: "path::to::test" + field( + "name", + cx.expr_call( + sp, + cx.expr_path(test_path("StaticTestName")), + vec![cx.expr_str( + sp, + Symbol::intern(&item_path( + // skip the name of the root module + &cx.current_expansion.module.mod_path[1..], + &item.ident, + )), + )], + ), + ), + // ignore: true | false + field( + "ignore", + cx.expr_bool(sp, should_ignore(&cx.sess, &item)), + ), + // allow_fail: true | false + field( + "allow_fail", + cx.expr_bool(sp, should_fail(&cx.sess, &item)), + ), + // should_panic: ... + field( + "should_panic", + match should_panic(cx, &item) { + // test::ShouldPanic::No + ShouldPanic::No => { + cx.expr_path(should_panic_path("No")) + } + // test::ShouldPanic::Yes + ShouldPanic::Yes(None) => { + cx.expr_path(should_panic_path("Yes")) + } + // test::ShouldPanic::YesWithMessage("...") + ShouldPanic::Yes(Some(sym)) => cx.expr_call( + sp, + cx.expr_path(should_panic_path("YesWithMessage")), + vec![cx.expr_str(sp, sym)], + ), + }, + ), + // test_type: ... + field( + "test_type", + match test_type(cx) { + // test::TestType::UnitTest + TestType::UnitTest => { + cx.expr_path(test_type_path("UnitTest")) + } + // test::TestType::IntegrationTest + TestType::IntegrationTest => { + cx.expr_path(test_type_path("IntegrationTest")) + } + // test::TestPath::Unknown + TestType::Unknown => { + cx.expr_path(test_type_path("Unknown")) + } + }, + ), + // }, + ], + ), + ), + // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...) + field("testfn", test_fn), // } + ], + ), // } + ), + ), + ); + test_const = test_const.map(|mut tc| { + tc.vis.node = ast::VisibilityKind::Public; + tc + }); + + // extern crate test + let test_extern = cx.item(sp, test_id, vec![], ast::ItemKind::ExternCrate(None)); + + tracing::debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); + + vec![ + // Access to libtest under a hygienic name + Annotatable::Item(test_extern), + // The generated test case + Annotatable::Item(test_const), + // The original item + Annotatable::Item(item), + ] +} + +fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String { + mod_path + .iter() + .chain(iter::once(item_ident)) + .map(|x| x.to_string()) + .collect::<Vec<String>>() + .join("::") +} + +enum ShouldPanic { + No, + Yes(Option<Symbol>), +} + +fn should_ignore(sess: &Session, i: &ast::Item) -> bool { + sess.contains_name(&i.attrs, sym::ignore) +} + +fn should_fail(sess: &Session, i: &ast::Item) -> bool { + sess.contains_name(&i.attrs, sym::allow_fail) +} + +fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic { + match cx.sess.find_by_name(&i.attrs, sym::should_panic) { + Some(attr) => { + let sd = &cx.sess.parse_sess.span_diagnostic; + + match attr.meta_item_list() { + // Handle #[should_panic(expected = "foo")] + Some(list) => { + let msg = list + .iter() + .find(|mi| mi.has_name(sym::expected)) + .and_then(|mi| mi.meta_item()) + .and_then(|mi| mi.value_str()); + if list.len() != 1 || msg.is_none() { + sd.struct_span_warn( + attr.span, + "argument must be of the form: \ + `expected = \"error message\"`", + ) + .note( + "errors in this attribute were erroneously \ + allowed and will become a hard error in a \ + future release.", + ) + .emit(); + ShouldPanic::Yes(None) + } else { + ShouldPanic::Yes(msg) + } + } + // Handle #[should_panic] and #[should_panic = "expected"] + None => ShouldPanic::Yes(attr.value_str()), + } + } + None => ShouldPanic::No, + } +} + +enum TestType { + UnitTest, + IntegrationTest, + Unknown, +} + +/// Attempts to determine the type of test. +/// Since doctests are created without macro expanding, only possible variants here +/// are `UnitTest`, `IntegrationTest` or `Unknown`. +fn test_type(cx: &ExtCtxt<'_>) -> TestType { + // Root path from context contains the topmost sources directory of the crate. + // I.e., for `project` with sources in `src` and tests in `tests` folders + // (no matter how many nested folders lie inside), + // there will be two different root paths: `/project/src` and `/project/tests`. + let crate_path = cx.root_path.as_path(); + + if crate_path.ends_with("src") { + // `/src` folder contains unit-tests. + TestType::UnitTest + } else if crate_path.ends_with("tests") { + // `/tests` folder contains integration tests. + TestType::IntegrationTest + } else { + // Crate layout doesn't match expected one, test type is unknown. + TestType::Unknown + } +} + +fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool { + let has_should_panic_attr = cx.sess.contains_name(&i.attrs, sym::should_panic); + let sd = &cx.sess.parse_sess.span_diagnostic; + if let ast::ItemKind::Fn(_, ref sig, ref generics, _) = i.kind { + if let ast::Unsafe::Yes(span) = sig.header.unsafety { + sd.struct_span_err(i.span, "unsafe functions cannot be used for tests") + .span_label(span, "`unsafe` because of this") + .emit(); + return false; + } + if let ast::Async::Yes { span, .. } = sig.header.asyncness { + sd.struct_span_err(i.span, "async functions cannot be used for tests") + .span_label(span, "`async` because of this") + .emit(); + return false; + } + + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let has_output = match sig.decl.output { + ast::FnRetTy::Default(..) => false, + ast::FnRetTy::Ty(ref t) if t.kind.is_unit() => false, + _ => true, + }; + + if !sig.decl.inputs.is_empty() { + sd.span_err(i.span, "functions used as tests can not have any arguments"); + return false; + } + + match (has_output, has_should_panic_attr) { + (true, true) => { + sd.span_err(i.span, "functions using `#[should_panic]` must return `()`"); + false + } + (true, false) => { + if !generics.params.is_empty() { + sd.span_err(i.span, "functions used as tests must have signature fn() -> ()"); + false + } else { + true + } + } + (false, _) => true, + } + } else { + sd.span_err(i.span, "only functions may be used as tests"); + false + } +} + +fn has_bench_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool { + let has_sig = if let ast::ItemKind::Fn(_, ref sig, _, _) = i.kind { + // N.B., inadequate check, but we're running + // well before resolve, can't get too deep. + sig.decl.inputs.len() == 1 + } else { + false + }; + + if !has_sig { + cx.sess.parse_sess.span_diagnostic.span_err( + i.span, + "functions used as benches must have \ + signature `fn(&mut Bencher) -> impl Termination`", + ); + } + + has_sig +} diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs new file mode 100644 index 00000000000..0ea60665d67 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -0,0 +1,383 @@ +// Code that generates a test runner to run all the tests in a crate + +use rustc_ast as ast; +use rustc_ast::attr; +use rustc_ast::entry::EntryPointType; +use rustc_ast::mut_visit::{ExpectOne, *}; +use rustc_ast::ptr::P; +use rustc_expand::base::{ExtCtxt, ResolverExpand}; +use rustc_expand::expand::{AstFragment, ExpansionConfig}; +use rustc_feature::Features; +use rustc_session::Session; +use rustc_span::hygiene::{AstPass, SyntaxContext, Transparency}; +use rustc_span::source_map::respan; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use rustc_target::spec::PanicStrategy; +use smallvec::{smallvec, SmallVec}; +use tracing::debug; + +use std::{iter, mem}; + +struct Test { + span: Span, + ident: Ident, +} + +struct TestCtxt<'a> { + ext_cx: ExtCtxt<'a>, + panic_strategy: PanicStrategy, + def_site: Span, + test_cases: Vec<Test>, + reexport_test_harness_main: Option<Symbol>, + test_runner: Option<ast::Path>, +} + +// Traverse the crate, collecting all the test functions, eliding any +// existing main functions, and synthesizing a main test harness +pub fn inject(sess: &Session, resolver: &mut dyn ResolverExpand, krate: &mut ast::Crate) { + let span_diagnostic = sess.diagnostic(); + let panic_strategy = sess.panic_strategy(); + let platform_panic_strategy = sess.target.target.options.panic_strategy; + + // Check for #![reexport_test_harness_main = "some_name"] which gives the + // main test function the name `some_name` without hygiene. This needs to be + // unconditional, so that the attribute is still marked as used in + // non-test builds. + let reexport_test_harness_main = + sess.first_attr_value_str_by_name(&krate.attrs, sym::reexport_test_harness_main); + + // Do this here so that the test_runner crate attribute gets marked as used + // even in non-test builds + let test_runner = get_test_runner(sess, span_diagnostic, &krate); + + if sess.opts.test { + let panic_strategy = match (panic_strategy, sess.opts.debugging_opts.panic_abort_tests) { + (PanicStrategy::Abort, true) => PanicStrategy::Abort, + (PanicStrategy::Abort, false) => { + if panic_strategy == platform_panic_strategy { + // Silently allow compiling with panic=abort on these platforms, + // but with old behavior (abort if a test fails). + } else { + span_diagnostic.err( + "building tests with panic=abort is not supported \ + without `-Zpanic_abort_tests`", + ); + } + PanicStrategy::Unwind + } + (PanicStrategy::Unwind, _) => PanicStrategy::Unwind, + }; + generate_test_harness( + sess, + resolver, + reexport_test_harness_main, + krate, + &sess.features_untracked(), + panic_strategy, + test_runner, + ) + } +} + +struct TestHarnessGenerator<'a> { + cx: TestCtxt<'a>, + tests: Vec<Test>, +} + +impl<'a> MutVisitor for TestHarnessGenerator<'a> { + fn visit_crate(&mut self, c: &mut ast::Crate) { + noop_visit_crate(c, self); + + // Create a main function to run our tests + c.module.items.push(mk_main(&mut self.cx)); + } + + fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> { + let mut item = i.into_inner(); + if is_test_case(&self.cx.ext_cx.sess, &item) { + debug!("this is a test item"); + + let test = Test { span: item.span, ident: item.ident }; + self.tests.push(test); + } + + // We don't want to recurse into anything other than mods, since + // mods or tests inside of functions will break things + if let ast::ItemKind::Mod(mut module) = item.kind { + let tests = mem::take(&mut self.tests); + noop_visit_mod(&mut module, self); + let mut tests = mem::replace(&mut self.tests, tests); + + if !tests.is_empty() { + let parent = + if item.id == ast::DUMMY_NODE_ID { ast::CRATE_NODE_ID } else { item.id }; + // 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( + module.inner, + AstPass::TestHarness, + &[], + Some(parent), + ); + 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, Transparency::Opaque); + } + self.cx.test_cases.extend(tests); + } + item.kind = ast::ItemKind::Mod(module); + } + smallvec![P(item)] + } + + fn visit_mac(&mut self, _mac: &mut ast::MacCall) { + // Do nothing. + } +} + +// Beware, this is duplicated in librustc_passes/entry.rs (with +// `rustc_hir::Item`), so make sure to keep them in sync. +fn entry_point_type(sess: &Session, item: &ast::Item, depth: usize) -> EntryPointType { + match item.kind { + ast::ItemKind::Fn(..) => { + if sess.contains_name(&item.attrs, sym::start) { + EntryPointType::Start + } else if sess.contains_name(&item.attrs, sym::main) { + EntryPointType::MainAttr + } else if item.ident.name == sym::main { + if depth == 1 { + // This is a top-level function so can be 'main' + EntryPointType::MainNamed + } else { + EntryPointType::OtherMain + } + } else { + EntryPointType::None + } + } + _ => EntryPointType::None, + } +} +/// A folder used to remove any entry points (like fn main) because the harness +/// generator will provide its own +struct EntryPointCleaner<'a> { + // Current depth in the ast + sess: &'a Session, + depth: usize, + def_site: Span, +} + +impl<'a> MutVisitor for EntryPointCleaner<'a> { + fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> { + self.depth += 1; + let item = noop_flat_map_item(i, self).expect_one("noop did something"); + self.depth -= 1; + + // Remove any #[main] or #[start] from the AST so it doesn't + // clash with the one we're going to add, but mark it as + // #[allow(dead_code)] to avoid printing warnings. + let item = match entry_point_type(self.sess, &item, self.depth) { + EntryPointType::MainNamed | EntryPointType::MainAttr | EntryPointType::Start => item + .map(|ast::Item { id, ident, attrs, kind, vis, span, tokens }| { + let allow_ident = Ident::new(sym::allow, self.def_site); + let dc_nested = + attr::mk_nested_word_item(Ident::new(sym::dead_code, self.def_site)); + let allow_dead_code_item = attr::mk_list_item(allow_ident, vec![dc_nested]); + let allow_dead_code = attr::mk_attr_outer(allow_dead_code_item); + let attrs = attrs + .into_iter() + .filter(|attr| { + !self.sess.check_name(attr, sym::main) + && !self.sess.check_name(attr, 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] + } + + fn visit_mac(&mut self, _mac: &mut ast::MacCall) { + // Do nothing. + } +} + +/// Crawl over the crate, inserting test reexports and the test main function +fn generate_test_harness( + sess: &Session, + resolver: &mut dyn ResolverExpand, + reexport_test_harness_main: Option<Symbol>, + krate: &mut ast::Crate, + features: &Features, + panic_strategy: PanicStrategy, + test_runner: Option<ast::Path>, +) { + let mut econfig = ExpansionConfig::default("test".to_string()); + econfig.features = Some(features); + + let ext_cx = ExtCtxt::new(sess, econfig, resolver, None); + + let expn_id = ext_cx.resolver.expansion_for_ast_pass( + DUMMY_SP, + AstPass::TestHarness, + &[sym::main, sym::test, sym::rustc_attrs], + None, + ); + let def_site = DUMMY_SP.with_def_site_ctxt(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 +/// +/// ``` +/// #[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(ecx.path(sp, vec![test_id, Ident::from_str_and_span(runner_name, sp)])); + + test_runner.span = sp; + + let test_main_path_expr = ecx.expr_path(test_runner); + let call_test_main = ecx.expr_call(sp, test_main_path_expr, vec![mk_tests_slice(cx, sp)]); + let call_test_main = ecx.stmt_expr(call_test_main); + + // extern crate test + let test_extern_stmt = + ecx.stmt_item(sp, ecx.item(sp, test_id, vec![], ast::ItemKind::ExternCrate(None))); + + // #[main] + let main_meta = ecx.meta_word(sp, sym::main); + let main_attr = ecx.attribute(main_meta); + + // pub fn main() { ... } + let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![])); + + // If no test runner is provided we need to import the test crate + let main_body = if cx.test_runner.is_none() { + ecx.block(sp, vec![test_extern_stmt, call_test_main]) + } else { + ecx.block(sp, vec![call_test_main]) + }; + + let decl = ecx.fn_decl(vec![], ast::FnRetTy::Ty(main_ret_ty)); + let sig = ast::FnSig { decl, header: ast::FnHeader::default(), span: sp }; + let def = ast::Defaultness::Final; + let main = ast::ItemKind::Fn(def, sig, ast::Generics::default(), Some(main_body)); + + // Honor the reexport_test_harness_main attribute + let main_id = match cx.reexport_test_harness_main { + Some(sym) => Ident::new(sym, sp.with_ctxt(SyntaxContext::root())), + None => Ident::new(sym::main, sp), + }; + + let main = P(ast::Item { + ident: main_id, + attrs: vec![main_attr], + id: ast::DUMMY_NODE_ID, + kind: main, + vis: respan(sp, ast::VisibilityKind::Public), + span: sp, + tokens: None, + }); + + // Integrate the new item into existing module structures. + let main = AstFragment::Items(smallvec![main]); + cx.ext_cx.monotonic_expander().fully_expand_fragment(main).make_items().pop().unwrap() +} + +/// Creates a slice containing every test like so: +/// &[&test1, &test2] +fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> { + debug!("building test vector from {} tests", cx.test_cases.len()); + let ecx = &cx.ext_cx; + + ecx.expr_vec_slice( + sp, + cx.test_cases + .iter() + .map(|test| { + ecx.expr_addr_of(test.span, ecx.expr_path(ecx.path(test.span, vec![test.ident]))) + }) + .collect(), + ) +} + +fn is_test_case(sess: &Session, i: &ast::Item) -> bool { + sess.contains_name(&i.attrs, sym::rustc_test_marker) +} + +fn get_test_runner( + sess: &Session, + sd: &rustc_errors::Handler, + krate: &ast::Crate, +) -> Option<ast::Path> { + let test_attr = sess.find_by_name(&krate.attrs, sym::test_runner)?; + let meta_list = test_attr.meta_item_list()?; + let span = test_attr.span; + match &*meta_list { + [single] => match single.meta_item() { + Some(meta_item) if meta_item.is_word() => return Some(meta_item.path.clone()), + _ => sd.struct_span_err(span, "`test_runner` argument must be a path").emit(), + }, + _ => sd.struct_span_err(span, "`#![test_runner(..)]` accepts exactly 1 argument").emit(), + } + None +} diff --git a/compiler/rustc_builtin_macros/src/trace_macros.rs b/compiler/rustc_builtin_macros/src/trace_macros.rs new file mode 100644 index 00000000000..c17f2afe494 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/trace_macros.rs @@ -0,0 +1,29 @@ +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_expand::base::{self, ExtCtxt}; +use rustc_span::symbol::kw; +use rustc_span::Span; + +pub fn expand_trace_macros( + cx: &mut ExtCtxt<'_>, + sp: Span, + tt: TokenStream, +) -> Box<dyn base::MacResult + 'static> { + let mut cursor = tt.into_trees(); + let mut err = false; + let value = match &cursor.next() { + Some(TokenTree::Token(token)) if token.is_keyword(kw::True) => true, + Some(TokenTree::Token(token)) if token.is_keyword(kw::False) => false, + _ => { + err = true; + false + } + }; + err |= cursor.next().is_some(); + if err { + cx.span_err(sp, "trace_macros! accepts only `true` or `false`") + } else { + cx.set_trace_macros(value); + } + + base::DummyResult::any_valid(sp) +} diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs new file mode 100644 index 00000000000..01ea80c4c8a --- /dev/null +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -0,0 +1,12 @@ +use rustc_ast::MetaItem; +use rustc_expand::base::ExtCtxt; +use rustc_feature::AttributeTemplate; +use rustc_parse::validate_attr; +use rustc_span::Symbol; + +pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) { + // All the built-in macro attributes are "words" at the moment. + let template = AttributeTemplate { word: true, ..Default::default() }; + let attr = ecx.attribute(meta_item.clone()); + validate_attr::check_builtin_attribute(&ecx.sess.parse_sess, &attr, name, template); +} |
