diff options
Diffstat (limited to '')
34 files changed, 1512 insertions, 1581 deletions
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 1a0ea8f41..a1051d990 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -852,7 +852,7 @@ pub(super) fn expand_global_asm<'cx>( if let Some(inline_asm) = expand_preparsed_asm(ecx, args) { MacEager::items(smallvec![P(ast::Item { ident: Ident::empty(), - attrs: Vec::new(), + attrs: ast::AttrVec::new(), id: ast::DUMMY_NODE_ID, kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)), vis: ast::Visibility { diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs index 925c36edb..119724b50 100644 --- a/compiler/rustc_builtin_macros/src/assert.rs +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -52,7 +52,7 @@ pub fn expand_assert<'cx>( let expr = if let Some(tokens) = custom_message { let then = cx.expr( call_site_span, - ExprKind::MacCall(MacCall { + ExprKind::MacCall(P(MacCall { path: panic_path(), args: P(MacArgs::Delimited( DelimSpan::from_single(call_site_span), @@ -60,7 +60,7 @@ pub fn expand_assert<'cx>( tokens, )), prior_type_ascription: None, - }), + })), ); expr_if_not(cx, call_site_span, cond_expr, then, None) } diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index dcea883a5..bb6839360 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -13,6 +13,7 @@ use rustc_span::{ symbol::{sym, Ident, Symbol}, Span, }; +use thin_vec::thin_vec; pub(super) struct Context<'cx, 'a> { // An optimization. @@ -57,6 +58,7 @@ impl<'cx, 'a> Context<'cx, 'a> { /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to: /// /// ```rust + /// #![feature(generic_assert_internals)] /// let elem = 1; /// { /// #[allow(unused_imports)] @@ -69,7 +71,7 @@ impl<'cx, 'a> Context<'cx, 'a> { /// __local_bind0 /// } == 1 /// ) { - /// panic!("Assertion failed: elem == 1\nWith captures:\n elem = {}", __capture0) + /// panic!("Assertion failed: elem == 1\nWith captures:\n elem = {:?}", __capture0) /// } /// } /// ``` @@ -116,7 +118,7 @@ impl<'cx, 'a> Context<'cx, 'a> { self.cx.item( self.span, Ident::empty(), - vec![self.cx.attribute(attr::mk_list_item( + thin_vec![self.cx.attribute(attr::mk_list_item( Ident::new(sym::allow, self.span), vec![attr::mk_nested_word_item(Ident::new(sym::unused_imports, self.span))], ))], @@ -177,7 +179,7 @@ impl<'cx, 'a> Context<'cx, 'a> { }); self.cx.expr( self.span, - ExprKind::MacCall(MacCall { + ExprKind::MacCall(P(MacCall { path: panic_path, args: P(MacArgs::Delimited( DelimSpan::from_single(self.span), @@ -185,7 +187,7 @@ impl<'cx, 'a> Context<'cx, 'a> { initial.into_iter().chain(captures).collect::<TokenStream>(), )), prior_type_ascription: None, - }), + })), ) } @@ -240,8 +242,8 @@ impl<'cx, 'a> Context<'cx, 'a> { self.manage_cond_expr(prefix); self.manage_cond_expr(suffix); } - ExprKind::MethodCall(_, ref mut local_exprs, _) => { - for local_expr in local_exprs.iter_mut().skip(1) { + ExprKind::MethodCall(_, _,ref mut local_exprs, _) => { + for local_expr in local_exprs.iter_mut() { self.manage_cond_expr(local_expr); } } @@ -377,14 +379,12 @@ impl<'cx, 'a> Context<'cx, 'a> { id: DUMMY_NODE_ID, ident: Ident::new(sym::try_capture, self.span), }, - vec![ - expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)), - expr_addr_of_mut( - self.cx, - self.span, - self.cx.expr_path(Path::from_ident(capture)), - ), - ], + expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)), + vec![expr_addr_of_mut( + self.cx, + self.span, + self.cx.expr_path(Path::from_ident(capture)), + )], self.span, )) .add_trailing_semicolon(); @@ -442,10 +442,11 @@ fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> { fn expr_method_call( cx: &ExtCtxt<'_>, path: PathSegment, + receiver: P<Expr>, args: Vec<P<Expr>>, span: Span, ) -> P<Expr> { - cx.expr(span, ExprKind::MethodCall(path, args, span)) + cx.expr(span, ExprKind::MethodCall(path, receiver, args, span)) } fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> { diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index aa355150b..5638c2f61 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -8,7 +8,7 @@ use rustc_ast::tokenstream::TokenStream; use rustc_attr as attr; use rustc_errors::PResult; use rustc_expand::base::{self, *}; -use rustc_macros::SessionDiagnostic; +use rustc_macros::Diagnostic; use rustc_span::Span; pub fn expand_cfg( @@ -35,16 +35,16 @@ pub fn expand_cfg( } } -#[derive(SessionDiagnostic)] -#[error(builtin_macros::requires_cfg_pattern)] +#[derive(Diagnostic)] +#[diag(builtin_macros_requires_cfg_pattern)] struct RequiresCfgPattern { #[primary_span] #[label] span: Span, } -#[derive(SessionDiagnostic)] -#[error(builtin_macros::expected_one_cfg_pattern)] +#[derive(Diagnostic)] +#[diag(builtin_macros_expected_one_cfg_pattern)] struct OneCfgPattern { #[primary_span] span: Span, diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 89b2c3292..750f1fe12 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -7,6 +7,7 @@ use rustc_ast::visit::Visitor; use rustc_ast::NodeId; use rustc_ast::{mut_visit, visit}; use rustc_ast::{Attribute, HasAttrs, HasTokens}; +use rustc_errors::PResult; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_expand::config::StripUnconfigured; use rustc_expand::configure; @@ -144,33 +145,34 @@ impl CfgEval<'_, '_> { // the location of `#[cfg]` and `#[cfg_attr]` in the token stream. The tokenization // process is lossless, so this process is invisible to proc-macros. - let parse_annotatable_with: fn(&mut Parser<'_>) -> _ = match annotatable { - Annotatable::Item(_) => { - |parser| Annotatable::Item(parser.parse_item(ForceCollect::Yes).unwrap().unwrap()) - } - Annotatable::TraitItem(_) => |parser| { - Annotatable::TraitItem( - parser.parse_trait_item(ForceCollect::Yes).unwrap().unwrap().unwrap(), - ) - }, - Annotatable::ImplItem(_) => |parser| { - Annotatable::ImplItem( - parser.parse_impl_item(ForceCollect::Yes).unwrap().unwrap().unwrap(), - ) - }, - Annotatable::ForeignItem(_) => |parser| { - Annotatable::ForeignItem( - parser.parse_foreign_item(ForceCollect::Yes).unwrap().unwrap().unwrap(), - ) - }, - Annotatable::Stmt(_) => |parser| { - Annotatable::Stmt(P(parser.parse_stmt(ForceCollect::Yes).unwrap().unwrap())) - }, - Annotatable::Expr(_) => { - |parser| Annotatable::Expr(parser.parse_expr_force_collect().unwrap()) - } - _ => unreachable!(), - }; + let parse_annotatable_with: for<'a> fn(&mut Parser<'a>) -> PResult<'a, _> = + match annotatable { + Annotatable::Item(_) => { + |parser| Ok(Annotatable::Item(parser.parse_item(ForceCollect::Yes)?.unwrap())) + } + Annotatable::TraitItem(_) => |parser| { + Ok(Annotatable::TraitItem( + parser.parse_trait_item(ForceCollect::Yes)?.unwrap().unwrap(), + )) + }, + Annotatable::ImplItem(_) => |parser| { + Ok(Annotatable::ImplItem( + parser.parse_impl_item(ForceCollect::Yes)?.unwrap().unwrap(), + )) + }, + Annotatable::ForeignItem(_) => |parser| { + Ok(Annotatable::ForeignItem( + parser.parse_foreign_item(ForceCollect::Yes)?.unwrap().unwrap(), + )) + }, + Annotatable::Stmt(_) => |parser| { + Ok(Annotatable::Stmt(P(parser.parse_stmt(ForceCollect::Yes)?.unwrap()))) + }, + Annotatable::Expr(_) => { + |parser| Ok(Annotatable::Expr(parser.parse_expr_force_collect()?)) + } + _ => unreachable!(), + }; // 'Flatten' all nonterminals (i.e. `TokenKind::Interpolated`) // to `None`-delimited groups containing the corresponding tokens. This @@ -188,22 +190,35 @@ impl CfgEval<'_, '_> { let orig_tokens = annotatable.to_tokens().flattened(); // Re-parse the tokens, setting the `capture_cfg` flag to save extra information - // to the captured `AttrAnnotatedTokenStream` (specifically, we capture - // `AttrAnnotatedTokenTree::AttributesData` for all occurrences of `#[cfg]` and `#[cfg_attr]`) + // to the captured `AttrTokenStream` (specifically, we capture + // `AttrTokenTree::AttributesData` for all occurrences of `#[cfg]` and `#[cfg_attr]`) let mut parser = rustc_parse::stream_to_parser(&self.cfg.sess.parse_sess, orig_tokens, None); parser.capture_cfg = true; - annotatable = parse_annotatable_with(&mut parser); + match parse_annotatable_with(&mut parser) { + Ok(a) => annotatable = a, + Err(mut err) => { + err.emit(); + return Some(annotatable); + } + } - // Now that we have our re-parsed `AttrAnnotatedTokenStream`, recursively configuring + // Now that we have our re-parsed `AttrTokenStream`, recursively configuring // our attribute target will correctly the tokens as well. flat_map_annotatable(self, annotatable) } } impl MutVisitor for CfgEval<'_, '_> { + #[instrument(level = "trace", skip(self))] fn visit_expr(&mut self, expr: &mut P<ast::Expr>) { - self.cfg.configure_expr(expr); + self.cfg.configure_expr(expr, false); + mut_visit::noop_visit_expr(expr, self); + } + + #[instrument(level = "trace", skip(self))] + fn visit_method_receiver_expr(&mut self, expr: &mut P<ast::Expr>) { + self.cfg.configure_expr(expr, true); mut_visit::noop_visit_expr(expr, self); } diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs index 747e48ece..db05c00d2 100644 --- a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs +++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs @@ -28,7 +28,13 @@ pub fn inject(mut krate: ast::Crate, parse_sess: &ParseSess, attrs: &[String]) - continue; } - krate.attrs.push(mk_attr(AttrStyle::Inner, path, args, start_span.to(end_span))); + krate.attrs.push(mk_attr( + &parse_sess.attr_id_generator, + AttrStyle::Inner, + path, + args, + start_span.to(end_span), + )); } krate diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs index a23dd1d12..41f4e8c23 100644 --- a/compiler/rustc_builtin_macros/src/concat.rs +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -39,7 +39,7 @@ pub fn expand_concat( ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..) => { cx.span_err(e.span, "cannot concatenate a byte string literal"); } - ast::LitKind::Err(_) => { + ast::LitKind::Err => { has_errors = true; } }, diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs index a1afec410..66e86bf21 100644 --- a/compiler/rustc_builtin_macros/src/concat_bytes.rs +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -1,6 +1,5 @@ use rustc_ast as ast; use rustc_ast::{ptr::P, tokenstream::TokenStream}; -use rustc_data_structures::sync::Lrc; use rustc_errors::Applicability; use rustc_expand::base::{self, DummyResult}; @@ -43,7 +42,7 @@ fn invalid_type_err(cx: &mut base::ExtCtxt<'_>, expr: &P<rustc_ast::Expr>, is_ne ast::LitKind::Bool(_) => { cx.span_err(expr.span, "cannot concatenate boolean literals"); } - ast::LitKind::Err(_) => {} + ast::LitKind::Err => {} ast::LitKind::Int(_, _) if !is_nested => { let mut err = cx.struct_span_err(expr.span, "cannot concatenate numeric literals"); if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) { @@ -185,5 +184,5 @@ pub fn expand_concat_bytes( return base::MacEager::expr(DummyResult::raw_expr(sp, true)); } let sp = cx.with_def_site_ctxt(sp); - base::MacEager::expr(cx.expr_lit(sp, ast::LitKind::ByteStr(Lrc::from(accumulator)))) + base::MacEager::expr(cx.expr_byte_str(sp, accumulator)) } diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index d3de10ca4..e0fb7affb 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -32,7 +32,8 @@ impl MultiItemModifier for Expander { ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| { let template = AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() }; - let attr = attr::mk_attr_outer(meta_item.clone()); + let attr = + attr::mk_attr_outer(&sess.parse_sess.attr_id_generator, meta_item.clone()); validate_attr::check_builtin_attribute( &sess.parse_sess, &attr, @@ -126,9 +127,9 @@ fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool { } fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) { - let help_msg = match lit.token.kind { - token::Str if rustc_lexer::is_ident(lit.token.symbol.as_str()) => { - format!("try using `#[derive({})]`", lit.token.symbol) + let help_msg = match lit.token_lit.kind { + token::Str if rustc_lexer::is_ident(lit.token_lit.symbol.as_str()) => { + format!("try using `#[derive({})]`", lit.token_lit.symbol) } _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(), }; diff --git a/compiler/rustc_builtin_macros/src/deriving/bounds.rs b/compiler/rustc_builtin_macros/src/deriving/bounds.rs index 5ef68c6ae..7bd344467 100644 --- a/compiler/rustc_builtin_macros/src/deriving/bounds.rs +++ b/compiler/rustc_builtin_macros/src/deriving/bounds.rs @@ -15,8 +15,8 @@ pub fn expand_deriving_copy( ) { let trait_def = TraitDef { span, - attributes: Vec::new(), path: path_std!(marker::Copy), + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: true, diff --git a/compiler/rustc_builtin_macros/src/deriving/clone.rs b/compiler/rustc_builtin_macros/src/deriving/clone.rs index 7755ff779..fa8685f5f 100644 --- a/compiler/rustc_builtin_macros/src/deriving/clone.rs +++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs @@ -1,12 +1,12 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::path_std; - use rustc_ast::{self as ast, Generics, ItemKind, MetaItem, VariantData}; use rustc_data_structures::fx::FxHashSet; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::Span; +use thin_vec::thin_vec; pub fn expand_deriving_clone( cx: &mut ExtCtxt<'_>, @@ -68,11 +68,11 @@ pub fn expand_deriving_clone( } let inline = cx.meta_word(span, sym::inline); - let attrs = vec![cx.attribute(inline)]; + let attrs = thin_vec![cx.attribute(inline)]; let trait_def = TraitDef { span, - attributes: Vec::new(), path: path_std!(clone::Clone), + skip_path_as_bound: false, additional_bounds: bounds, generics: Bounds::empty(), supports_unions: true, diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index 4e798bf6a..eab67b0d3 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -7,6 +7,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; +use thin_vec::thin_vec; pub fn expand_deriving_eq( cx: &mut ExtCtxt<'_>, @@ -20,11 +21,11 @@ pub fn expand_deriving_eq( let hidden = rustc_ast::attr::mk_nested_word_item(Ident::new(sym::hidden, span)); let doc = rustc_ast::attr::mk_list_item(Ident::new(sym::doc, span), vec![hidden]); let no_coverage = cx.meta_word(span, sym::no_coverage); - let attrs = vec![cx.attribute(inline), cx.attribute(doc), cx.attribute(no_coverage)]; + let attrs = thin_vec![cx.attribute(inline), cx.attribute(doc), cx.attribute(no_coverage)]; let trait_def = TraitDef { span, - attributes: Vec::new(), path: path_std!(cmp::Eq), + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: true, diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs index 1612be862..7f117981a 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs @@ -1,11 +1,11 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::path_std; - use rustc_ast::MetaItem; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; +use thin_vec::thin_vec; pub fn expand_deriving_ord( cx: &mut ExtCtxt<'_>, @@ -15,11 +15,11 @@ pub fn expand_deriving_ord( push: &mut dyn FnMut(Annotatable), ) { let inline = cx.meta_word(span, sym::inline); - let attrs = vec![cx.attribute(inline)]; + let attrs = thin_vec![cx.attribute(inline)]; let trait_def = TraitDef { span, - attributes: Vec::new(), path: path_std!(cmp::Ord), + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: false, diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs index 0141b3377..236cbccaf 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs @@ -1,12 +1,12 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::{path_local, path_std}; - use rustc_ast::ptr::P; use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::sym; use rustc_span::Span; +use thin_vec::thin_vec; pub fn expand_deriving_partial_eq( cx: &mut ExtCtxt<'_>, @@ -15,14 +15,8 @@ pub fn expand_deriving_partial_eq( item: &Annotatable, push: &mut dyn FnMut(Annotatable), ) { - fn cs_op( - cx: &mut ExtCtxt<'_>, - span: Span, - substr: &Substructure<'_>, - op: BinOpKind, - combiner: BinOpKind, - base: bool, - ) -> BlockOrExpr { + fn cs_eq(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr { + let base = true; let expr = cs_fold( true, // use foldl cx, @@ -47,39 +41,22 @@ pub fn expand_deriving_partial_eq( cx.expr_deref(field.span, expr.clone()) } }; - cx.expr_binary(field.span, op, convert(&field.self_expr), convert(other_expr)) + cx.expr_binary( + field.span, + BinOpKind::Eq, + convert(&field.self_expr), + convert(other_expr), + ) + } + CsFold::Combine(span, expr1, expr2) => { + cx.expr_binary(span, BinOpKind::And, expr1, expr2) } - CsFold::Combine(span, expr1, expr2) => cx.expr_binary(span, combiner, expr1, expr2), CsFold::Fieldless => cx.expr_bool(span, base), }, ); BlockOrExpr::new_expr(expr) } - fn cs_eq(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr { - cs_op(cx, span, substr, BinOpKind::Eq, BinOpKind::And, true) - } - fn cs_ne(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr { - cs_op(cx, span, substr, BinOpKind::Ne, BinOpKind::Or, false) - } - - macro_rules! md { - ($name:expr, $f:ident) => {{ - let inline = cx.meta_word(span, sym::inline); - let attrs = vec![cx.attribute(inline)]; - MethodDef { - name: $name, - generics: Bounds::empty(), - explicit_self: true, - nonself_args: vec![(self_ref(), sym::other)], - ret_ty: Path(path_local!(bool)), - attributes: attrs, - unify_fieldless_variants: true, - combine_substructure: combine_substructure(Box::new(|a, b, c| $f(a, b, c))), - } - }}; - } - super::inject_impl_of_structural_trait( cx, span, @@ -88,18 +65,25 @@ pub fn expand_deriving_partial_eq( 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)); - } + // No need to generate `ne`, the default suffices, and not generating it is + // faster. + let inline = cx.meta_word(span, sym::inline); + let attrs = thin_vec![cx.attribute(inline)]; + let methods = vec![MethodDef { + name: sym::eq, + generics: Bounds::empty(), + explicit_self: true, + nonself_args: vec![(self_ref(), sym::other)], + ret_ty: Path(path_local!(bool)), + attributes: attrs, + unify_fieldless_variants: true, + combine_substructure: combine_substructure(Box::new(|a, b, c| cs_eq(a, b, c))), + }]; let trait_def = TraitDef { span, - attributes: Vec::new(), path: path_std!(cmp::PartialEq), + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: false, diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs index 2ebb01cc8..4173403a1 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs @@ -1,11 +1,11 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::{path_std, pathvec_std}; - use rustc_ast::MetaItem; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; +use thin_vec::thin_vec; pub fn expand_deriving_partial_ord( cx: &mut ExtCtxt<'_>, @@ -19,7 +19,7 @@ pub fn expand_deriving_partial_ord( Path(Path::new_(pathvec_std!(option::Option), vec![Box::new(ordering_ty)], PathKind::Std)); let inline = cx.meta_word(span, sym::inline); - let attrs = vec![cx.attribute(inline)]; + let attrs = thin_vec![cx.attribute(inline)]; let partial_cmp_def = MethodDef { name: sym::partial_cmp, @@ -36,8 +36,8 @@ pub fn expand_deriving_partial_ord( let trait_def = TraitDef { span, - attributes: vec![], path: path_std!(cmp::PartialOrd), + skip_path_as_bound: false, additional_bounds: vec![], generics: Bounds::empty(), supports_unions: false, diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index ceef893e8..2cf614ed9 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -19,8 +19,8 @@ pub fn expand_deriving_debug( let trait_def = TraitDef { span, - attributes: Vec::new(), path: path_std!(fmt::Debug), + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: false, @@ -30,7 +30,7 @@ pub fn expand_deriving_debug( explicit_self: true, nonself_args: vec![(fmtr, sym::f)], ret_ty: Path(path_std!(fmt::Result)), - attributes: Vec::new(), + attributes: ast::AttrVec::new(), unify_fieldless_variants: false, combine_substructure: combine_substructure(Box::new(|a, b, c| { show_substructure(a, b, c) @@ -52,7 +52,7 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_> // 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 name = cx.expr_str(span, ident.name); let fmt = substr.nonselflike_args[0].clone(); // Struct and tuples are similar enough that we use the same code for both, @@ -89,10 +89,7 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_> for i in 0..fields.len() { let field = &fields[i]; if is_struct { - let name = cx.expr_lit( - field.span, - ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked), - ); + let name = cx.expr_str(field.span, field.name.unwrap().name); args.push(name); } // Use an extra indirection to make sure this works for unsized types. @@ -108,10 +105,7 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_> for field in fields { if is_struct { - name_exprs.push(cx.expr_lit( - field.span, - ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked), - )); + name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name)); } // Use an extra indirection to make sure this works for unsized types. diff --git a/compiler/rustc_builtin_macros/src/deriving/decodable.rs b/compiler/rustc_builtin_macros/src/deriving/decodable.rs index d688143a2..d669f6168 100644 --- a/compiler/rustc_builtin_macros/src/deriving/decodable.rs +++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs @@ -22,8 +22,8 @@ pub fn expand_deriving_rustc_decodable( let trait_def = TraitDef { span, - attributes: Vec::new(), path: Path::new_(vec![krate, sym::Decodable], vec![], PathKind::Global), + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: false, @@ -48,7 +48,7 @@ pub fn expand_deriving_rustc_decodable( ], PathKind::Std, )), - attributes: Vec::new(), + attributes: ast::AttrVec::new(), unify_fieldless_variants: false, combine_substructure: combine_substructure(Box::new(|a, b, c| { decodable_substructure(a, b, c, krate) diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs index 517769091..17df9fb27 100644 --- a/compiler/rustc_builtin_macros/src/deriving/default.rs +++ b/compiler/rustc_builtin_macros/src/deriving/default.rs @@ -1,16 +1,14 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; - use rustc_ast as ast; -use rustc_ast::walk_list; -use rustc_ast::EnumDef; -use rustc_ast::VariantData; +use rustc_ast::{walk_list, EnumDef, VariantData}; use rustc_errors::Applicability; use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt}; use rustc_span::symbol::Ident; use rustc_span::symbol::{kw, sym}; use rustc_span::Span; use smallvec::SmallVec; +use thin_vec::thin_vec; pub fn expand_deriving_default( cx: &mut ExtCtxt<'_>, @@ -22,11 +20,11 @@ pub fn expand_deriving_default( item.visit_with(&mut DetectNonVariantDefaultAttr { cx }); let inline = cx.meta_word(span, sym::inline); - let attrs = vec![cx.attribute(inline)]; + let attrs = thin_vec![cx.attribute(inline)]; let trait_def = TraitDef { span, - attributes: Vec::new(), path: Path::new(vec![kw::Default, sym::Default]), + skip_path_as_bound: has_a_default_variant(item), additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: false, @@ -265,3 +263,22 @@ impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, ' } } } + +fn has_a_default_variant(item: &Annotatable) -> bool { + struct HasDefaultAttrOnVariant { + found: bool, + } + + impl<'ast> rustc_ast::visit::Visitor<'ast> for HasDefaultAttrOnVariant { + fn visit_variant(&mut self, v: &'ast rustc_ast::Variant) { + if v.attrs.iter().any(|attr| attr.has_name(kw::Default)) { + self.found = true; + } + // no need to subrecurse. + } + } + + let mut visitor = HasDefaultAttrOnVariant { found: false }; + item.visit_with(&mut visitor); + visitor.found +} diff --git a/compiler/rustc_builtin_macros/src/deriving/encodable.rs b/compiler/rustc_builtin_macros/src/deriving/encodable.rs index 70167cac6..f83f58b97 100644 --- a/compiler/rustc_builtin_macros/src/deriving/encodable.rs +++ b/compiler/rustc_builtin_macros/src/deriving/encodable.rs @@ -89,7 +89,7 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::pathvec_std; -use rustc_ast::{ExprKind, MetaItem, Mutability}; +use rustc_ast::{AttrVec, ExprKind, MetaItem, Mutability}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::Span; @@ -106,8 +106,8 @@ pub fn expand_deriving_rustc_encodable( let trait_def = TraitDef { span, - attributes: Vec::new(), path: Path::new_(vec![krate, sym::Encodable], vec![], PathKind::Global), + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: false, @@ -132,7 +132,7 @@ pub fn expand_deriving_rustc_encodable( ], PathKind::Std, )), - attributes: Vec::new(), + attributes: AttrVec::new(), unify_fieldless_variants: false, combine_substructure: combine_substructure(Box::new(|a, b, c| { encodable_substructure(a, b, c, krate) diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 735017aa5..16ee3aa89 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -162,33 +162,35 @@ pub use StaticFields::*; pub use SubstructureFields::*; -use std::cell::RefCell; -use std::iter; -use std::vec; - +use crate::deriving; use rustc_ast::ptr::P; -use rustc_ast::{self as ast, EnumDef, Expr, Generics, PatKind}; +use rustc_ast::{ + self as ast, BindingAnnotation, ByRef, EnumDef, Expr, Generics, Mutability, PatKind, +}; use rustc_ast::{GenericArg, GenericParamKind, VariantData}; use rustc_attr as attr; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::Span; - +use std::cell::RefCell; +use std::iter; +use std::ops::Not; +use std::vec; +use thin_vec::thin_vec; use ty::{Bounds, Path, Ref, Self_, Ty}; -use crate::deriving; - pub mod ty; pub struct TraitDef<'a> { /// The span for the current #[derive(Foo)] header. pub span: Span, - pub attributes: Vec<ast::Attribute>, - /// Path of the trait, including any type parameters pub path: Path, + /// Whether to skip adding the current trait as a bound to the type parameters of the type. + pub skip_path_as_bound: bool, + /// Additional bounds required of any type parameters of the type, /// other than the current trait pub additional_bounds: Vec<Ty>, @@ -219,7 +221,7 @@ pub struct MethodDef<'a> { /// Returns type pub ret_ty: Ty, - pub attributes: Vec<ast::Attribute>, + pub attributes: ast::AttrVec, /// Can we combine fieldless variants for enums into a single match arm? /// If true, indicates that the trait operation uses the enum tag in some @@ -383,16 +385,11 @@ fn find_type_parameters( } // Place bound generic params on a stack, to extract them when a type is encountered. - fn visit_poly_trait_ref( - &mut self, - trait_ref: &'a ast::PolyTraitRef, - modifier: &'a ast::TraitBoundModifier, - ) { + fn visit_poly_trait_ref(&mut self, trait_ref: &'a ast::PolyTraitRef) { let stack_len = self.bound_generic_params_stack.len(); - self.bound_generic_params_stack - .extend(trait_ref.bound_generic_params.clone().into_iter()); + self.bound_generic_params_stack.extend(trait_ref.bound_generic_params.iter().cloned()); - visit::walk_poly_trait_ref(self, trait_ref, modifier); + visit::walk_poly_trait_ref(self, trait_ref); self.bound_generic_params_stack.truncate(stack_len); } @@ -568,8 +565,8 @@ impl<'a> TraitDef<'a> { kind: ast::VisibilityKind::Inherited, tokens: None, }, - attrs: Vec::new(), - kind: ast::AssocItemKind::TyAlias(Box::new(ast::TyAlias { + attrs: ast::AttrVec::new(), + kind: ast::AssocItemKind::Type(Box::new(ast::TyAlias { defaultness: ast::Defaultness::Final, generics: Generics::default(), where_clauses: ( @@ -603,13 +600,13 @@ impl<'a> TraitDef<'a> { 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())) + self.skip_path_as_bound.not().then(|| cx.trait_bound(trait_path.clone())) ).chain( // also add in any bounds from the declaration param.bounds.iter().cloned() ).collect(); - cx.typaram(param.ident.span.with_ctxt(ctxt), param.ident, vec![], bounds, None) + cx.typaram(param.ident.span.with_ctxt(ctxt), param.ident, bounds, None) } GenericParamKind::Const { ty, kw_span, .. } => { let const_nodefault_kind = GenericParamKind::Const { @@ -644,11 +641,7 @@ impl<'a> TraitDef<'a> { } ast::WherePredicate::EqPredicate(we) => { let span = we.span.with_ctxt(ctxt); - ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { - id: ast::DUMMY_NODE_ID, - span, - ..we.clone() - }) + ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { span, ..we.clone() }) } } })); @@ -726,15 +719,13 @@ impl<'a> TraitDef<'a> { let self_type = cx.ty_path(path); let attr = cx.attribute(cx.meta_word(self.span, sym::automatically_derived)); + let attrs = thin_vec![attr]; let opt_trait_ref = Some(trait_ref); - let mut a = vec![attr]; - a.extend(self.attributes.iter().cloned()); - cx.item( self.span, Ident::empty(), - a, + attrs, ast::ItemKind::Impl(Box::new(ast::Impl { unsafety: ast::Unsafe::No, polarity: ast::ImplPolarity::Positive, @@ -1078,9 +1069,9 @@ impl<'a> MethodDef<'a> { let mut body = mk_body(cx, selflike_fields); let struct_path = cx.path(span, vec![Ident::new(kw::SelfUpper, type_ident.span)]); - let use_ref_pat = is_packed && !always_copy; + let by_ref = ByRef::from(is_packed && !always_copy); let patterns = - trait_.create_struct_patterns(cx, struct_path, struct_def, &prefixes, use_ref_pat); + trait_.create_struct_patterns(cx, struct_path, struct_def, &prefixes, by_ref); // Do the let-destructuring. let mut stmts: Vec<_> = iter::zip(selflike_args, patterns) @@ -1123,6 +1114,11 @@ impl<'a> MethodDef<'a> { /// ``` /// is equivalent to: /// ``` + /// #![feature(core_intrinsics)] + /// enum A { + /// A1, + /// A2(i32) + /// } /// impl ::core::cmp::PartialEq for A { /// #[inline] /// fn eq(&self, other: &A) -> bool { @@ -1262,13 +1258,13 @@ impl<'a> MethodDef<'a> { let sp = variant.span.with_ctxt(trait_.span.ctxt()); let variant_path = cx.path(sp, vec![type_ident, variant.ident]); - let use_ref_pat = false; // because enums can't be repr(packed) + let by_ref = ByRef::No; // because enums can't be repr(packed) let mut subpats: Vec<_> = trait_.create_struct_patterns( cx, variant_path, &variant.data, &prefixes, - use_ref_pat, + by_ref, ); // `(VariantK, VariantK, ...)` or just `VariantK`. @@ -1429,7 +1425,7 @@ impl<'a> TraitDef<'a> { struct_path: ast::Path, struct_def: &'a VariantData, prefixes: &[String], - use_ref_pat: bool, + by_ref: ByRef, ) -> Vec<P<ast::Pat>> { prefixes .iter() @@ -1437,17 +1433,19 @@ impl<'a> TraitDef<'a> { let pieces_iter = struct_def.fields().iter().enumerate().map(|(i, struct_field)| { let sp = struct_field.span.with_ctxt(self.span.ctxt()); - let binding_mode = if use_ref_pat { - ast::BindingMode::ByRef(ast::Mutability::Not) - } else { - ast::BindingMode::ByValue(ast::Mutability::Not) - }; let ident = self.mk_pattern_ident(prefix, i); let path = ident.with_span_pos(sp); ( sp, struct_field.ident, - cx.pat(path.span, PatKind::Ident(binding_mode, path, None)), + cx.pat( + path.span, + PatKind::Ident( + BindingAnnotation(by_ref, Mutability::Not), + path, + None, + ), + ), ) }); @@ -1637,19 +1635,3 @@ where StaticEnum(..) | StaticStruct(..) => cx.span_bug(trait_span, "static function in `derive`"), } } - -/// Returns `true` if the type has no value fields -/// (for an enum, no variant has any fields) -pub fn is_type_without_fields(item: &Annotatable) -> bool { - if let Annotatable::Item(ref item) = *item { - match item.kind { - ast::ItemKind::Enum(ref enum_def, _) => { - enum_def.variants.iter().all(|v| v.data.fields().is_empty()) - } - ast::ItemKind::Struct(ref variant_data, _) => variant_data.fields().is_empty(), - _ => false, - } - } else { - false - } -} diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs index 4d46f7cd4..36e2e2930 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs @@ -146,7 +146,6 @@ fn mk_ty_param( cx: &ExtCtxt<'_>, span: Span, name: Symbol, - attrs: &[ast::Attribute], bounds: &[Path], self_ident: Ident, self_generics: &Generics, @@ -158,7 +157,7 @@ fn mk_ty_param( cx.trait_bound(path) }) .collect(); - cx.typaram(span, Ident::new(name, span), attrs.to_owned(), bounds, None) + cx.typaram(span, Ident::new(name, span), bounds, None) } /// Bounds on type parameters. @@ -183,7 +182,7 @@ impl Bounds { .iter() .map(|t| { let (name, ref bounds) = *t; - mk_ty_param(cx, span, name, &[], &bounds, self_ty, self_generics) + mk_ty_param(cx, span, name, &bounds, self_ty, self_generics) }) .collect(); diff --git a/compiler/rustc_builtin_macros/src/deriving/hash.rs b/compiler/rustc_builtin_macros/src/deriving/hash.rs index 32ae3d344..6e9d5f08b 100644 --- a/compiler/rustc_builtin_macros/src/deriving/hash.rs +++ b/compiler/rustc_builtin_macros/src/deriving/hash.rs @@ -2,7 +2,7 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::{path_std, pathvec_std}; -use rustc_ast::{MetaItem, Mutability}; +use rustc_ast::{AttrVec, MetaItem, Mutability}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::sym; use rustc_span::Span; @@ -21,8 +21,8 @@ pub fn expand_deriving_hash( let arg = Path::new_local(typaram); let hash_trait_def = TraitDef { span, - attributes: Vec::new(), path, + skip_path_as_bound: false, additional_bounds: Vec::new(), generics: Bounds::empty(), supports_unions: false, @@ -32,7 +32,7 @@ pub fn expand_deriving_hash( explicit_self: true, nonself_args: vec![(Ref(Box::new(Path(arg)), Mutability::Mut), sym::state)], ret_ty: Unit, - attributes: vec![], + attributes: AttrVec::new(), unify_fieldless_variants: true, combine_substructure: combine_substructure(Box::new(|a, b, c| { hash_substructure(a, b, c) diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs index c1ca089da..ee346047a 100644 --- a/compiler/rustc_builtin_macros/src/deriving/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs @@ -131,6 +131,8 @@ fn inject_impl_of_structural_trait( // Create generics param list for where clauses and impl headers let mut generics = generics.clone(); + let ctxt = span.ctxt(); + // Create the type of `self`. // // in addition, remove defaults from generic params (impls cannot have them). @@ -138,16 +140,18 @@ fn inject_impl_of_structural_trait( .params .iter_mut() .map(|param| match &mut param.kind { - ast::GenericParamKind::Lifetime => { - ast::GenericArg::Lifetime(cx.lifetime(span, param.ident)) - } + ast::GenericParamKind::Lifetime => ast::GenericArg::Lifetime( + cx.lifetime(param.ident.span.with_ctxt(ctxt), param.ident), + ), ast::GenericParamKind::Type { default } => { *default = None; - ast::GenericArg::Type(cx.ty_ident(span, param.ident)) + ast::GenericArg::Type(cx.ty_ident(param.ident.span.with_ctxt(ctxt), param.ident)) } ast::GenericParamKind::Const { ty: _, kw_span: _, default } => { *default = None; - ast::GenericArg::Const(cx.const_ident(span, param.ident)) + ast::GenericArg::Const( + cx.const_ident(param.ident.span.with_ctxt(ctxt), param.ident), + ) } }) .collect(); @@ -164,7 +168,7 @@ fn inject_impl_of_structural_trait( // Keep the lint and stability attributes of the original item, to control // how the generated implementation is linted. - let mut attrs = Vec::new(); + let mut attrs = ast::AttrVec::new(); attrs.extend( item.attrs .iter() @@ -174,6 +178,8 @@ fn inject_impl_of_structural_trait( }) .cloned(), ); + // Mark as `automatically_derived` to avoid some silly lints. + attrs.push(cx.attribute(cx.meta_word(span, sym::automatically_derived))); let newitem = cx.item( span, diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs index ea0e768a5..3f1a8b3bc 100644 --- a/compiler/rustc_builtin_macros/src/edition_panic.rs +++ b/compiler/rustc_builtin_macros/src/edition_panic.rs @@ -48,7 +48,7 @@ fn expand<'cx>( MacEager::expr( cx.expr( sp, - ExprKind::MacCall(MacCall { + ExprKind::MacCall(P(MacCall { path: Path { span: sp, segments: cx @@ -64,7 +64,7 @@ fn expand<'cx>( tts, )), prior_type_ascription: None, - }), + })), ), ) } diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 9eb96ec76..8b07c1106 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -1,289 +1,42 @@ -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_ast::visit::{self, Visitor}; -use rustc_ast::{token, BlockCheckMode, UnsafeSource}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_ast::Expr; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::{pluralize, Applicability, MultiSpan, PResult}; use rustc_expand::base::{self, *}; use rustc_parse_format as parse; -use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::symbol::{Ident, Symbol}; use rustc_span::{BytePos, InnerSpan, Span}; -use smallvec::SmallVec; use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY; use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId}; -use rustc_parse_format::Count; -use std::borrow::Cow; -use std::collections::hash_map::Entry; - -#[derive(PartialEq)] -enum ArgumentType { - Placeholder(&'static str), - Count, -} -enum Position { - Exact(usize), - Capture(usize), - Named(Symbol, InnerSpan), -} - -/// Indicates how positional named argument (i.e. an named argument which is used by position -/// instead of by name) is used in format string -/// * `Arg` is the actual argument to print -/// * `Width` is width format argument -/// * `Precision` is precion format argument -/// Example: `{Arg:Width$.Precision$} -#[derive(Debug, Eq, PartialEq)] -enum PositionalNamedArgType { - Arg, - Width, - Precision, -} - -/// Contains information necessary to create a lint for a positional named argument -#[derive(Debug)] -struct PositionalNamedArg { - ty: PositionalNamedArgType, - /// The piece of the using this argument (multiple pieces can use the same argument) - cur_piece: usize, - /// The InnerSpan for in the string to be replaced with the named argument - /// This will be None when the position is implicit - inner_span_to_replace: Option<rustc_parse_format::InnerSpan>, - /// The name to use instead of the position - replacement: Symbol, - /// The span for the positional named argument (so the lint can point a message to it) - positional_named_arg_span: Span, - has_formatting: bool, -} - -impl PositionalNamedArg { - /// Determines: - /// 1) span to be replaced with the name of the named argument and - /// 2) span to be underlined for error messages - fn get_positional_arg_spans(&self, cx: &Context<'_, '_>) -> (Option<Span>, Option<Span>) { - if let Some(inner_span) = &self.inner_span_to_replace { - let span = - cx.fmtsp.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }); - (Some(span), Some(span)) - } else if self.ty == PositionalNamedArgType::Arg { - // In the case of a named argument whose position is implicit, if the argument *has* - // formatting, there will not be a span to replace. Instead, we insert the name after - // the `{`, which will be the first character of arg_span. If the argument does *not* - // have formatting, there may or may not be a span to replace. This is because - // whitespace is allowed in arguments without formatting (such as `format!("{ }", 1);`) - // but is not allowed in arguments with formatting (an error will be generated in cases - // like `format!("{ :1.1}", 1.0f32);`. - // For the message span, if there is formatting, we want to use the opening `{` and the - // next character, which will the `:` indicating the start of formatting. If there is - // not any formatting, we want to underline the entire span. - cx.arg_spans.get(self.cur_piece).map_or((None, None), |arg_span| { - if self.has_formatting { - ( - Some(arg_span.with_lo(arg_span.lo() + BytePos(1)).shrink_to_lo()), - Some(arg_span.with_hi(arg_span.lo() + BytePos(2))), - ) - } else { - let replace_start = arg_span.lo() + BytePos(1); - let replace_end = arg_span.hi() - BytePos(1); - let to_replace = arg_span.with_lo(replace_start).with_hi(replace_end); - (Some(to_replace), Some(*arg_span)) - } - }) - } else { - (None, None) - } - } -} +mod ast; +use ast::*; -/// Encapsulates all the named arguments that have been used positionally -#[derive(Debug)] -struct PositionalNamedArgsLint { - positional_named_args: Vec<PositionalNamedArg>, -} +mod expand; +use expand::expand_parsed_format_args; -impl PositionalNamedArgsLint { - /// For a given positional argument, check if the index is for a named argument. - /// - /// Since positional arguments are required to come before named arguments, if the positional - /// index is greater than or equal to the start of named arguments, we know it's a named - /// argument used positionally. - /// - /// Example: - /// println!("{} {} {2}", 0, a=1, b=2); - /// - /// In this case, the first piece (`{}`) would be ArgumentImplicitlyIs with an index of 0. The - /// total number of arguments is 3 and the number of named arguments is 2, so the start of named - /// arguments is index 1. Therefore, the index of 0 is okay. - /// - /// The second piece (`{}`) would be ArgumentImplicitlyIs with an index of 1, which is the start - /// of named arguments, and so we should add a lint to use the named argument `a`. - /// - /// The third piece (`{2}`) would be ArgumentIs with an index of 2, which is greater than the - /// start of named arguments, and so we should add a lint to use the named argument `b`. - /// - /// This same check also works for width and precision formatting when either or both are - /// CountIsParam, which contains an index into the arguments. - fn maybe_add_positional_named_arg( - &mut self, - current_positional_arg: usize, - total_args_length: usize, - format_argument_index: usize, - ty: PositionalNamedArgType, - cur_piece: usize, - inner_span_to_replace: Option<rustc_parse_format::InnerSpan>, - names: &FxHashMap<Symbol, (usize, Span)>, - has_formatting: bool, - ) { - let start_of_named_args = total_args_length - names.len(); - if current_positional_arg >= start_of_named_args { - self.maybe_push( - format_argument_index, - ty, - cur_piece, - inner_span_to_replace, - names, - has_formatting, - ) - } - } +// The format_args!() macro is expanded in three steps: +// 1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax, +// but doesn't parse the template (the literal) itself. +// 2. Second, `make_format_args` will parse the template, the format options, resolve argument references, +// produce diagnostics, and turn the whole thing into a `FormatArgs` structure. +// 3. Finally, `expand_parsed_format_args` will turn that `FormatArgs` structure +// into the expression that the macro expands to. - /// Try constructing a PositionalNamedArg struct and pushing it into the vec of positional - /// named arguments. If a named arg associated with `format_argument_index` cannot be found, - /// a new item will not be added as the lint cannot be emitted in this case. - fn maybe_push( - &mut self, - format_argument_index: usize, - ty: PositionalNamedArgType, - cur_piece: usize, - inner_span_to_replace: Option<rustc_parse_format::InnerSpan>, - names: &FxHashMap<Symbol, (usize, Span)>, - has_formatting: bool, - ) { - let named_arg = names - .iter() - .find(|&(_, &(index, _))| index == format_argument_index) - .map(|found| found.clone()); +// See format/ast.rs for the FormatArgs structure and glossary. - if let Some((&replacement, &(_, positional_named_arg_span))) = named_arg { - // In FormatSpec, `precision_span` starts at the leading `.`, which we want to keep in - // the lint suggestion, so increment `start` by 1 when `PositionalArgumentType` is - // `Precision`. - let inner_span_to_replace = if ty == PositionalNamedArgType::Precision { - inner_span_to_replace - .map(|is| rustc_parse_format::InnerSpan { start: is.start + 1, end: is.end }) - } else { - inner_span_to_replace - }; - self.positional_named_args.push(PositionalNamedArg { - ty, - cur_piece, - inner_span_to_replace, - replacement, - positional_named_arg_span, - has_formatting, - }); - } - } -} - -struct Context<'a, 'b> { - ecx: &'a mut ExtCtxt<'b>, - /// The macro's call site. References to unstable formatting internals must - /// use this span to pass the stability checker. - macsp: Span, - /// The span of the format string literal. - fmtsp: Span, - - /// List of parsed argument expressions. - /// Named expressions are resolved early, and are appended to the end of - /// argument expressions. - /// - /// Example showing the various data structures in motion: - /// - /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"` - /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` - /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"` - /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]` - /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]` - /// * `names` (in JSON): `{"foo": 2}` - args: Vec<P<ast::Expr>>, - /// The number of arguments that were added by implicit capturing. - num_captured_args: usize, - /// Placeholder slot numbers indexed by argument. - arg_types: Vec<Vec<usize>>, - /// Unique format specs seen for each argument. - arg_unique_types: Vec<Vec<ArgumentType>>, - /// Map from named arguments to their resolved indices. - names: FxHashMap<Symbol, (usize, Span)>, - - /// The latest consecutive literal strings, or empty if there weren't any. - literal: String, - - /// Collection of the compiled `rt::Argument` structures - pieces: Vec<P<ast::Expr>>, - /// Collection of string literals - str_pieces: Vec<P<ast::Expr>>, - /// Stays `true` if all formatting parameters are default (as in "{}{}"). - all_pieces_simple: bool, - - /// Mapping between positional argument references and indices into the - /// final generated static argument array. We record the starting indices - /// corresponding to each positional argument, and number of references - /// consumed so far for each argument, to facilitate correct `Position` - /// mapping in `build_piece`. In effect this can be seen as a "flattened" - /// version of `arg_unique_types`. - /// - /// Again with the example described above in docstring for `args`: - /// - /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]` - arg_index_map: Vec<Vec<usize>>, - - /// Starting offset of count argument slots. - count_args_index_offset: usize, - - /// Count argument slots and tracking data structures. - /// Count arguments are separately tracked for de-duplication in case - /// multiple references are made to one argument. For example, in this - /// format string: - /// - /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"` - /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"` - /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"` - /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}` - /// * `count_args`: `vec![0, 5, 3]` - count_args: Vec<usize>, - /// Relative slot numbers for count arguments. - count_positions: FxHashMap<usize, usize>, - /// Number of count slots assigned. - count_positions_count: usize, - - /// Current position of the implicit positional arg pointer, as if it - /// still existed in this phase of processing. - /// Used only for `all_pieces_simple` tracking in `build_piece`. - curarg: usize, - /// Current piece being evaluated, used for error reporting. - curpiece: usize, - /// Keep track of invalid references to positional arguments. - invalid_refs: Vec<(usize, usize)>, - /// Spans of all the formatting arguments, in order. - arg_spans: Vec<Span>, - /// All the formatting arguments that have formatting flags set, in order for diagnostics. - arg_with_formatting: Vec<parse::FormatSpec<'a>>, - - /// Whether this format string came from a string literal, as opposed to a macro. - is_literal: bool, - unused_names_lint: PositionalNamedArgsLint, -} - -pub struct FormatArg { - expr: P<ast::Expr>, - named: bool, +// Only used in parse_args and report_invalid_references, +// to indicate how a referred argument was used. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PositionUsedAs { + Placeholder(Option<Span>), + Precision, + Width, } +use PositionUsedAs::*; /// 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! @@ -292,15 +45,14 @@ pub struct FormatArg { /// If parsing succeeds, the return value is: /// /// ```text -/// Some((fmtstr, parsed arguments, index map for named arguments)) +/// Ok((fmtstr, parsed arguments)) /// ``` fn parse_args<'a>( ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream, -) -> PResult<'a, (P<ast::Expr>, Vec<FormatArg>, FxHashMap<Symbol, (usize, Span)>)> { - let mut args = Vec::<FormatArg>::new(); - let mut names = FxHashMap::<Symbol, (usize, Span)>::default(); +) -> PResult<'a, (P<Expr>, FormatArguments)> { + let mut args = FormatArguments::new(); let mut p = ecx.new_parser_from_tts(tts); @@ -329,7 +81,6 @@ fn parse_args<'a>( }; let mut first = true; - let mut named = false; while p.token != token::Eof { if !p.eat(&token::Comma) { @@ -361,879 +112,54 @@ fn parse_args<'a>( } // accept trailing commas match p.token.ident() { Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { - named = true; p.bump(); p.expect(&token::Eq)?; - let e = p.parse_expr()?; - if let Some((prev, _)) = names.get(&ident.name) { - ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", ident)) - .span_label(args[*prev].expr.span, "previously here") - .span_label(e.span, "duplicate argument") - .emit(); + let expr = p.parse_expr()?; + if let Some((_, prev)) = args.by_name(ident.name) { + ecx.struct_span_err( + ident.span, + &format!("duplicate argument named `{}`", ident), + ) + .span_label(prev.kind.ident().unwrap().span, "previously here") + .span_label(ident.span, "duplicate argument") + .emit(); continue; } - - // Resolve names into slots early. - // Since all the positional args are already seen at this point - // if the input is valid, we can simply append to the positional - // args. And remember the names. - let slot = args.len(); - names.insert(ident.name, (slot, ident.span)); - args.push(FormatArg { expr: e, named: true }); + args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); } _ => { - let e = p.parse_expr()?; - if named { + let expr = p.parse_expr()?; + if !args.named_args().is_empty() { let mut err = ecx.struct_span_err( - e.span, + expr.span, "positional arguments cannot follow named arguments", ); - err.span_label(e.span, "positional arguments must be before named arguments"); - for pos in names.values() { - err.span_label(args[pos.0].expr.span, "named argument"); + err.span_label( + expr.span, + "positional arguments must be before named arguments", + ); + for arg in args.named_args() { + if let Some(name) = arg.kind.ident() { + err.span_label(name.span.to(arg.expr.span), "named argument"); + } } err.emit(); } - args.push(FormatArg { expr: e, named: false }); + args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); } } } - Ok((fmtstr, args, names)) + Ok((fmtstr, args)) } -impl<'a, 'b> Context<'a, 'b> { - /// The number of arguments that were explicitly given. - fn num_args(&self) -> usize { - self.args.len() - self.num_captured_args - } - - fn resolve_name_inplace(&mut self, p: &mut parse::Piece<'_>) { - // NOTE: the `unwrap_or` branch is needed in case of invalid format - // arguments, e.g., `format_args!("{foo}")`. - let lookup = - |s: &str| self.names.get(&Symbol::intern(s)).unwrap_or(&(0, Span::default())).0; - - match *p { - parse::String(_) => {} - parse::NextArgument(ref mut arg) => { - if let parse::ArgumentNamed(s) = arg.position { - arg.position = parse::ArgumentIs(lookup(s)); - } - if let parse::CountIsName(s, _) = arg.format.width { - arg.format.width = parse::CountIsParam(lookup(s)); - } - if let parse::CountIsName(s, _) = arg.format.precision { - arg.format.precision = parse::CountIsParam(lookup(s)); - } - } - } - } - - /// Verifies one piece of a parse string, and remembers it if valid. - /// All errors are not emitted as fatal so we can continue giving errors - /// about this and possibly other format strings. - fn verify_piece(&mut self, p: &parse::Piece<'_>) { - match *p { - parse::String(..) => {} - parse::NextArgument(ref arg) => { - // width/precision first, if they have implicit positional - // parameters it makes more sense to consume them first. - self.verify_count( - arg.format.width, - &arg.format.width_span, - PositionalNamedArgType::Width, - ); - self.verify_count( - arg.format.precision, - &arg.format.precision_span, - PositionalNamedArgType::Precision, - ); - - let has_precision = arg.format.precision != Count::CountImplied; - let has_width = arg.format.width != Count::CountImplied; - - // argument second, if it's an implicit positional parameter - // it's written second, so it should come after width/precision. - let pos = match arg.position { - parse::ArgumentIs(i) => { - self.unused_names_lint.maybe_add_positional_named_arg( - i, - self.args.len(), - i, - PositionalNamedArgType::Arg, - self.curpiece, - Some(arg.position_span), - &self.names, - has_precision || has_width, - ); - - Exact(i) - } - parse::ArgumentImplicitlyIs(i) => { - self.unused_names_lint.maybe_add_positional_named_arg( - i, - self.args.len(), - i, - PositionalNamedArgType::Arg, - self.curpiece, - None, - &self.names, - has_precision || has_width, - ); - Exact(i) - } - parse::ArgumentNamed(s) => { - let symbol = Symbol::intern(s); - let span = arg.position_span; - Named(symbol, InnerSpan::new(span.start, span.end)) - } - }; - - let ty = Placeholder(match arg.format.ty { - "" => "Display", - "?" => "Debug", - "e" => "LowerExp", - "E" => "UpperExp", - "o" => "Octal", - "p" => "Pointer", - "b" => "Binary", - "x" => "LowerHex", - "X" => "UpperHex", - _ => { - let fmtsp = self.fmtsp; - let sp = arg - .format - .ty_span - .map(|sp| fmtsp.from_inner(InnerSpan::new(sp.start, sp.end))); - let mut err = self.ecx.struct_span_err( - sp.unwrap_or(fmtsp), - &format!("unknown format trait `{}`", arg.format.ty), - ); - err.note( - "the only appropriate formatting traits are:\n\ - - ``, which uses the `Display` trait\n\ - - `?`, which uses the `Debug` trait\n\ - - `e`, which uses the `LowerExp` trait\n\ - - `E`, which uses the `UpperExp` trait\n\ - - `o`, which uses the `Octal` trait\n\ - - `p`, which uses the `Pointer` trait\n\ - - `b`, which uses the `Binary` trait\n\ - - `x`, which uses the `LowerHex` trait\n\ - - `X`, which uses the `UpperHex` trait", - ); - if let Some(sp) = sp { - for (fmt, name) in &[ - ("", "Display"), - ("?", "Debug"), - ("e", "LowerExp"), - ("E", "UpperExp"), - ("o", "Octal"), - ("p", "Pointer"), - ("b", "Binary"), - ("x", "LowerHex"), - ("X", "UpperHex"), - ] { - // FIXME: rustfix (`run-rustfix`) fails to apply suggestions. - // > "Cannot replace slice of data that was already replaced" - err.tool_only_span_suggestion( - sp, - &format!("use the `{}` trait", name), - *fmt, - Applicability::MaybeIncorrect, - ); - } - } - err.emit(); - "<invalid>" - } - }); - self.verify_arg_type(pos, ty); - self.curpiece += 1; - } - } - } - - fn verify_count( - &mut self, - c: parse::Count<'_>, - inner_span: &Option<rustc_parse_format::InnerSpan>, - named_arg_type: PositionalNamedArgType, - ) { - match c { - parse::CountImplied | parse::CountIs(..) => {} - parse::CountIsParam(i) => { - self.unused_names_lint.maybe_add_positional_named_arg( - i, - self.args.len(), - i, - named_arg_type, - self.curpiece, - *inner_span, - &self.names, - true, - ); - self.verify_arg_type(Exact(i), Count); - } - parse::CountIsName(s, span) => { - self.verify_arg_type( - Named(Symbol::intern(s), InnerSpan::new(span.start, span.end)), - Count, - ); - } - } - } - - fn describe_num_args(&self) -> Cow<'_, str> { - match self.num_args() { - 0 => "no arguments were given".into(), - 1 => "there is 1 argument".into(), - x => format!("there are {} arguments", x).into(), - } - } - - /// Handle invalid references to positional arguments. Output different - /// errors for the case where all arguments are positional and for when - /// there are named arguments or numbered positional arguments in the - /// format string. - fn report_invalid_references(&self, numbered_position_args: bool) { - let mut e; - let sp = if !self.arg_spans.is_empty() { - // Point at the formatting arguments. - MultiSpan::from_spans(self.arg_spans.clone()) - } else { - MultiSpan::from_span(self.fmtsp) - }; - let refs = - self.invalid_refs.iter().map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos))); - - let mut zero_based_note = false; - - let count = self.pieces.len() - + self.arg_with_formatting.iter().filter(|fmt| fmt.precision_span.is_some()).count(); - if self.names.is_empty() && !numbered_position_args && count != self.num_args() { - e = self.ecx.struct_span_err( - sp, - &format!( - "{} positional argument{} in format string, but {}", - count, - pluralize!(count), - self.describe_num_args(), - ), - ); - for arg in &self.args { - // Point at the arguments that will be formatted. - e.span_label(arg.span, ""); - } - } else { - let (mut refs, spans): (Vec<_>, Vec<_>) = refs.unzip(); - // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` - // for `println!("{7:7$}", 1);` - refs.sort(); - refs.dedup(); - let spans: Vec<_> = spans.into_iter().filter_map(|sp| sp.copied()).collect(); - let sp = if self.arg_spans.is_empty() || spans.is_empty() { - MultiSpan::from_span(self.fmtsp) - } else { - MultiSpan::from_spans(spans) - }; - let arg_list = if refs.len() == 1 { - format!("argument {}", refs[0]) - } else { - let reg = refs.pop().unwrap(); - format!("arguments {head} and {tail}", head = refs.join(", "), tail = reg) - }; - - e = self.ecx.struct_span_err( - sp, - &format!( - "invalid reference to positional {} ({})", - arg_list, - self.describe_num_args() - ), - ); - zero_based_note = true; - }; - - for fmt in &self.arg_with_formatting { - if let Some(span) = fmt.precision_span { - let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end)); - match fmt.precision { - parse::CountIsParam(pos) if pos > self.num_args() => { - e.span_label( - span, - &format!( - "this precision flag expects an `usize` argument at position {}, \ - but {}", - pos, - self.describe_num_args(), - ), - ); - zero_based_note = true; - } - parse::CountIsParam(pos) => { - let count = self.pieces.len() - + self - .arg_with_formatting - .iter() - .filter(|fmt| fmt.precision_span.is_some()) - .count(); - e.span_label( - span, - &format!( - "this precision flag adds an extra required argument at position {}, \ - which is why there {} expected", - pos, - if count == 1 { - "is 1 argument".to_string() - } else { - format!("are {} arguments", count) - }, - ), - ); - if let Some(arg) = self.args.get(pos) { - e.span_label( - arg.span, - "this parameter corresponds to the precision flag", - ); - } - zero_based_note = true; - } - _ => {} - } - } - if let Some(span) = fmt.width_span { - let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end)); - match fmt.width { - parse::CountIsParam(pos) if pos >= self.num_args() => { - e.span_label( - span, - &format!( - "this width flag expects an `usize` argument at position {}, \ - but {}", - pos, - self.describe_num_args(), - ), - ); - zero_based_note = true; - } - _ => {} - } - } - } - if zero_based_note { - e.note("positional arguments are zero-based"); - } - if !self.arg_with_formatting.is_empty() { - e.note( - "for information about formatting flags, visit \ - https://doc.rust-lang.org/std/fmt/index.html", - ); - } - - e.emit(); - } - - /// Actually verifies and tracks a given format placeholder - /// (a.k.a. argument). - fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) { - if let Exact(arg) = arg { - if arg >= self.num_args() { - self.invalid_refs.push((arg, self.curpiece)); - return; - } - } - - match arg { - Exact(arg) | Capture(arg) => { - match ty { - Placeholder(_) => { - // record every (position, type) combination only once - let seen_ty = &mut self.arg_unique_types[arg]; - let i = seen_ty.iter().position(|x| *x == ty).unwrap_or_else(|| { - let i = seen_ty.len(); - seen_ty.push(ty); - i - }); - self.arg_types[arg].push(i); - } - Count => { - if let Entry::Vacant(e) = self.count_positions.entry(arg) { - let i = self.count_positions_count; - e.insert(i); - self.count_args.push(arg); - self.count_positions_count += 1; - } - } - } - } - - Named(name, span) => { - match self.names.get(&name) { - Some(&idx) => { - // Treat as positional arg. - self.verify_arg_type(Capture(idx.0), ty) - } - None => { - // For the moment capturing variables from format strings expanded from macros is - // disabled (see RFC #2795) - if self.is_literal { - // Treat this name as a variable to capture from the surrounding scope - let idx = self.args.len(); - self.arg_types.push(Vec::new()); - self.arg_unique_types.push(Vec::new()); - let span = if self.is_literal { - self.fmtsp.from_inner(span) - } else { - self.fmtsp - }; - self.num_captured_args += 1; - self.args.push(self.ecx.expr_ident(span, Ident::new(name, span))); - self.names.insert(name, (idx, span)); - self.verify_arg_type(Capture(idx), ty) - } else { - let msg = format!("there is no argument named `{}`", name); - let sp = if self.is_literal { - self.fmtsp.from_inner(span) - } else { - self.fmtsp - }; - let mut err = self.ecx.struct_span_err(sp, &msg); - - err.note(&format!( - "did you intend to capture a variable `{}` from \ - the surrounding scope?", - name - )); - err.note( - "to avoid ambiguity, `format_args!` cannot capture variables \ - when the format string is expanded from a macro", - ); - - err.emit(); - } - } - } - } - } - } - - /// Builds the mapping between format placeholders and argument objects. - fn build_index_map(&mut self) { - // NOTE: Keep the ordering the same as `into_expr`'s expansion would do! - let args_len = self.args.len(); - self.arg_index_map.reserve(args_len); - - let mut sofar = 0usize; - - // Map the arguments - for i in 0..args_len { - let arg_types = &self.arg_types[i]; - let arg_offsets = arg_types.iter().map(|offset| sofar + *offset).collect::<Vec<_>>(); - self.arg_index_map.push(arg_offsets); - sofar += self.arg_unique_types[i].len(); - } - - // Record starting index for counts, which appear just after arguments - self.count_args_index_offset = sofar; - } - - fn rtpath(ecx: &ExtCtxt<'_>, s: Symbol) -> Vec<Ident> { - ecx.std_path(&[sym::fmt, sym::rt, sym::v1, s]) - } - - fn build_count(&self, c: parse::Count<'_>) -> P<ast::Expr> { - let sp = self.macsp; - let count = |c, arg| { - let mut path = Context::rtpath(self.ecx, sym::Count); - path.push(Ident::new(c, sp)); - match arg { - Some(arg) => self.ecx.expr_call_global(sp, path, vec![arg]), - None => self.ecx.expr_path(self.ecx.path_global(sp, path)), - } - }; - match c { - parse::CountIs(i) => count(sym::Is, Some(self.ecx.expr_usize(sp, i))), - parse::CountIsParam(i) => { - // This needs mapping too, as `i` is referring to a macro - // argument. If `i` is not found in `count_positions` then - // the error had already been emitted elsewhere. - let i = self.count_positions.get(&i).cloned().unwrap_or(0) - + self.count_args_index_offset; - count(sym::Param, Some(self.ecx.expr_usize(sp, i))) - } - parse::CountImplied => count(sym::Implied, None), - // should never be the case, names are already resolved - parse::CountIsName(..) => panic!("should never happen"), - } - } - - /// Build a literal expression from the accumulated string literals - fn build_literal_string(&mut self) -> P<ast::Expr> { - let sp = self.fmtsp; - let s = Symbol::intern(&self.literal); - self.literal.clear(); - self.ecx.expr_str(sp, s) - } - - /// Builds a static `rt::Argument` from a `parse::Piece` or append - /// to the `literal` string. - fn build_piece( - &mut self, - piece: &parse::Piece<'a>, - arg_index_consumed: &mut Vec<usize>, - ) -> Option<P<ast::Expr>> { - let sp = self.macsp; - match *piece { - parse::String(s) => { - self.literal.push_str(s); - None - } - parse::NextArgument(ref arg) => { - // Build the position - let pos = { - match arg.position { - parse::ArgumentIs(i, ..) | parse::ArgumentImplicitlyIs(i) => { - // Map to index in final generated argument array - // in case of multiple types specified - let arg_idx = match arg_index_consumed.get_mut(i) { - None => 0, // error already emitted elsewhere - Some(offset) => { - let idx_map = &self.arg_index_map[i]; - // unwrap_or branch: error already emitted elsewhere - let arg_idx = *idx_map.get(*offset).unwrap_or(&0); - *offset += 1; - arg_idx - } - }; - self.ecx.expr_usize(sp, arg_idx) - } - - // should never be the case, because names are already - // resolved. - parse::ArgumentNamed(..) => panic!("should never happen"), - } - }; - - let simple_arg = parse::Argument { - position: { - // We don't have ArgumentNext any more, so we have to - // track the current argument ourselves. - let i = self.curarg; - self.curarg += 1; - parse::ArgumentIs(i) - }, - position_span: arg.position_span, - format: parse::FormatSpec { - fill: arg.format.fill, - align: parse::AlignUnknown, - flags: 0, - precision: parse::CountImplied, - precision_span: None, - width: parse::CountImplied, - width_span: None, - ty: arg.format.ty, - ty_span: arg.format.ty_span, - }, - }; - - let fill = arg.format.fill.unwrap_or(' '); - - let pos_simple = arg.position.index() == simple_arg.position.index(); - - if arg.format.precision_span.is_some() || arg.format.width_span.is_some() { - self.arg_with_formatting.push(arg.format); - } - if !pos_simple || arg.format != simple_arg.format || fill != ' ' { - self.all_pieces_simple = false; - } - - // Build the format - let fill = self.ecx.expr_lit(sp, ast::LitKind::Char(fill)); - let align = |name| { - let mut p = Context::rtpath(self.ecx, sym::Alignment); - p.push(Ident::new(name, sp)); - self.ecx.path_global(sp, p) - }; - let align = match arg.format.align { - parse::AlignLeft => align(sym::Left), - parse::AlignRight => align(sym::Right), - parse::AlignCenter => align(sym::Center), - parse::AlignUnknown => align(sym::Unknown), - }; - let align = self.ecx.expr_path(align); - let flags = self.ecx.expr_u32(sp, arg.format.flags); - let prec = self.build_count(arg.format.precision); - let width = self.build_count(arg.format.width); - let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::FormatSpec)); - let fmt = self.ecx.expr_struct( - sp, - path, - vec![ - self.ecx.field_imm(sp, Ident::new(sym::fill, sp), fill), - self.ecx.field_imm(sp, Ident::new(sym::align, sp), align), - self.ecx.field_imm(sp, Ident::new(sym::flags, sp), flags), - self.ecx.field_imm(sp, Ident::new(sym::precision, sp), prec), - self.ecx.field_imm(sp, Ident::new(sym::width, sp), width), - ], - ); - - let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::Argument)); - Some(self.ecx.expr_struct( - sp, - path, - vec![ - self.ecx.field_imm(sp, Ident::new(sym::position, sp), pos), - self.ecx.field_imm(sp, Ident::new(sym::format, sp), fmt), - ], - )) - } - } - } - - /// Actually builds the expression which the format_args! block will be - /// expanded to. - fn into_expr(self) -> P<ast::Expr> { - let mut original_args = self.args; - let mut fmt_args = Vec::with_capacity( - self.arg_unique_types.iter().map(|v| v.len()).sum::<usize>() + self.count_args.len(), - ); - - // First, build up the static array which will become our precompiled - // format "string" - let pieces = self.ecx.expr_array_ref(self.fmtsp, self.str_pieces); - - // We need to construct a &[ArgumentV1] to pass into the fmt::Arguments - // constructor. In general the expressions in this slice might be - // permuted from their order in original_args (such as in the case of - // "{1} {0}"), or may have multiple entries referring to the same - // element of original_args ("{0} {0}"). - // - // The following vector has one item per element of our output slice, - // identifying the index of which element of original_args it's passing, - // and that argument's type. - let mut fmt_arg_index_and_ty = SmallVec::<[(usize, &ArgumentType); 8]>::new(); - for (i, unique_types) in self.arg_unique_types.iter().enumerate() { - fmt_arg_index_and_ty.extend(unique_types.iter().map(|ty| (i, ty))); - } - fmt_arg_index_and_ty.extend(self.count_args.iter().map(|&i| (i, &Count))); - - // Figure out whether there are permuted or repeated elements. If not, - // we can generate simpler code. - // - // The sequence has no indices out of order or repeated if: for every - // adjacent pair of elements, the first one's index is less than the - // second one's index. - let nicely_ordered = - fmt_arg_index_and_ty.array_windows().all(|[(i, _i_ty), (j, _j_ty)]| i < j); - - // We want to emit: - // - // [ArgumentV1::new(&$arg0, …), ArgumentV1::new(&$arg1, …), …] - // - // However, it's only legal to do so if $arg0, $arg1, … were written in - // exactly that order by the programmer. When arguments are permuted, we - // want them evaluated in the order written by the programmer, not in - // the order provided to fmt::Arguments. When arguments are repeated, we - // want the expression evaluated only once. - // - // Further, if any arg _after the first one_ contains a yield point such - // as `await` or `yield`, the above short form is inconvenient for the - // caller because it would keep a temporary of type ArgumentV1 alive - // across the yield point. ArgumentV1 can't implement Send since it - // holds a type-erased arbitrary type. - // - // Thus in the not nicely ordered case, and in the yielding case, we - // emit the following instead: - // - // match (&$arg0, &$arg1, …) { - // args => [ArgumentV1::new(args.$i, …), ArgumentV1::new(args.$j, …), …] - // } - // - // for the sequence of indices $i, $j, … governed by fmt_arg_index_and_ty. - // This more verbose representation ensures that all arguments are - // evaluated a single time each, in the order written by the programmer, - // and that the surrounding future/generator (if any) is Send whenever - // possible. - let no_need_for_match = - nicely_ordered && !original_args.iter().skip(1).any(|e| may_contain_yield_point(e)); - - for (arg_index, arg_ty) in fmt_arg_index_and_ty { - let e = &mut original_args[arg_index]; - let span = e.span; - let arg = if no_need_for_match { - let expansion_span = e.span.with_ctxt(self.macsp.ctxt()); - // The indices are strictly ordered so e has not been taken yet. - self.ecx.expr_addr_of(expansion_span, P(e.take())) - } else { - let def_site = self.ecx.with_def_site_ctxt(span); - let args_tuple = self.ecx.expr_ident(def_site, Ident::new(sym::args, def_site)); - let member = Ident::new(sym::integer(arg_index), def_site); - self.ecx.expr(def_site, ast::ExprKind::Field(args_tuple, member)) - }; - fmt_args.push(Context::format_arg(self.ecx, self.macsp, span, arg_ty, arg)); - } - - let args_array = self.ecx.expr_array(self.macsp, fmt_args); - let args_slice = self.ecx.expr_addr_of( - self.macsp, - if no_need_for_match { - args_array - } else { - // In the !no_need_for_match case, none of the exprs were moved - // away in the previous loop. - // - // This uses the arg span for `&arg` so that borrowck errors - // point to the specific expression passed to the macro (the - // span is otherwise unavailable in the MIR used by borrowck). - let heads = original_args - .into_iter() - .map(|e| self.ecx.expr_addr_of(e.span.with_ctxt(self.macsp.ctxt()), e)) - .collect(); - - let pat = self.ecx.pat_ident(self.macsp, Ident::new(sym::args, self.macsp)); - let arm = self.ecx.arm(self.macsp, pat, args_array); - let head = self.ecx.expr(self.macsp, ast::ExprKind::Tup(heads)); - self.ecx.expr_match(self.macsp, head, vec![arm]) - }, - ); - - // Now create the fmt::Arguments struct with all our locals we created. - let (fn_name, fn_args) = if self.all_pieces_simple { - ("new_v1", vec![pieces, args_slice]) - } else { - // Build up the static array which will store our precompiled - // nonstandard placeholders, if there are any. - let fmt = self.ecx.expr_array_ref(self.macsp, self.pieces); - - let path = self.ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]); - let unsafe_arg = self.ecx.expr_call_global(self.macsp, path, Vec::new()); - let unsafe_expr = self.ecx.expr_block(P(ast::Block { - stmts: vec![self.ecx.stmt_expr(unsafe_arg)], - id: ast::DUMMY_NODE_ID, - rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated), - span: self.macsp, - tokens: None, - could_be_bare_literal: false, - })); - - ("new_v1_formatted", vec![pieces, args_slice, fmt, unsafe_expr]) - }; - - let path = self.ecx.std_path(&[sym::fmt, sym::Arguments, Symbol::intern(fn_name)]); - self.ecx.expr_call_global(self.macsp, path, fn_args) - } - - fn format_arg( - ecx: &ExtCtxt<'_>, - macsp: Span, - mut sp: Span, - ty: &ArgumentType, - arg: P<ast::Expr>, - ) -> P<ast::Expr> { - sp = ecx.with_def_site_ctxt(sp); - let trait_ = match *ty { - Placeholder(trait_) if trait_ == "<invalid>" => return DummyResult::raw_expr(sp, true), - Placeholder(trait_) => trait_, - Count => { - let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::from_usize]); - return ecx.expr_call_global(macsp, path, vec![arg]); - } - }; - let new_fn_name = match trait_ { - "Display" => "new_display", - "Debug" => "new_debug", - "LowerExp" => "new_lower_exp", - "UpperExp" => "new_upper_exp", - "Octal" => "new_octal", - "Pointer" => "new_pointer", - "Binary" => "new_binary", - "LowerHex" => "new_lower_hex", - "UpperHex" => "new_upper_hex", - _ => unreachable!(), - }; - - let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, Symbol::intern(new_fn_name)]); - ecx.expr_call_global(sp, path, vec![arg]) - } -} - -fn expand_format_args_impl<'cx>( - ecx: &'cx mut ExtCtxt<'_>, - mut sp: Span, - tts: TokenStream, - nl: bool, -) -> Box<dyn base::MacResult + 'cx> { - sp = ecx.with_def_site_ctxt(sp); - match parse_args(ecx, sp, tts) { - Ok((efmt, args, names)) => { - MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, nl)) - } - Err(mut err) => { - err.emit(); - DummyResult::any(sp) - } - } -} - -pub fn expand_format_args<'cx>( - ecx: &'cx mut ExtCtxt<'_>, - sp: Span, - tts: TokenStream, -) -> Box<dyn base::MacResult + 'cx> { - expand_format_args_impl(ecx, sp, tts, false) -} - -pub fn expand_format_args_nl<'cx>( - ecx: &'cx mut ExtCtxt<'_>, - sp: Span, - tts: TokenStream, -) -> Box<dyn base::MacResult + 'cx> { - expand_format_args_impl(ecx, sp, tts, true) -} - -fn create_lints_for_named_arguments_used_positionally(cx: &mut Context<'_, '_>) { - for named_arg in &cx.unused_names_lint.positional_named_args { - let (position_sp_to_replace, position_sp_for_msg) = named_arg.get_positional_arg_spans(cx); - - let msg = format!("named argument `{}` is not used by name", named_arg.replacement); - - cx.ecx.buffered_early_lint.push(BufferedEarlyLint { - span: MultiSpan::from_span(named_arg.positional_named_arg_span), - msg: msg.clone(), - node_id: ast::CRATE_NODE_ID, - lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY), - diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally { - position_sp_to_replace, - position_sp_for_msg, - named_arg_sp: named_arg.positional_named_arg_span, - named_arg_name: named_arg.replacement.to_string(), - is_formatting_arg: named_arg.ty != PositionalNamedArgType::Arg, - }, - }); - } -} - -/// Take the various parts of `format_args!(efmt, args..., name=names...)` -/// and construct the appropriate formatting expression. -pub fn expand_preparsed_format_args( +pub fn make_format_args( ecx: &mut ExtCtxt<'_>, - sp: Span, - efmt: P<ast::Expr>, - args: Vec<FormatArg>, - names: FxHashMap<Symbol, (usize, Span)>, + efmt: P<Expr>, + mut args: FormatArguments, 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); - +) -> Result<FormatArgs, ()> { let msg = "format argument must be a string literal"; - let fmt_sp = efmt.span; - let efmt_kind_is_lit: bool = matches!(efmt.kind, ast::ExprKind::Lit(_)); + let unexpanded_fmt_span = 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)); @@ -1242,13 +168,13 @@ pub fn expand_preparsed_format_args( Ok(fmt) => fmt, Err(err) => { if let Some((mut err, suggested)) = err { - let sugg_fmt = match args.len() { + let sugg_fmt = match args.explicit_args().len() { 0 => "{}".to_string(), - _ => format!("{}{{}}", "{} ".repeat(args.len())), + _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())), }; if !suggested { err.span_suggestion( - fmt_sp.shrink_to_lo(), + unexpanded_fmt_span.shrink_to_lo(), "you might be missing a string literal to format with", format!("\"{}\", ", sugg_fmt), Applicability::MaybeIncorrect, @@ -1256,17 +182,17 @@ pub fn expand_preparsed_format_args( } err.emit(); } - return DummyResult::raw_expr(sp, true); + return Err(()); } }; let str_style = match fmt_style { - ast::StrStyle::Cooked => None, - ast::StrStyle::Raw(raw) => Some(raw as usize), + rustc_ast::StrStyle::Cooked => None, + rustc_ast::StrStyle::Raw(raw) => Some(raw as usize), }; let fmt_str = fmt_str.as_str(); // for the suggestions below - let fmt_snippet = ecx.source_map().span_to_snippet(fmt_sp).ok(); + let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok(); let mut parser = parse::Parser::new( fmt_str, str_style, @@ -1275,18 +201,20 @@ pub fn expand_preparsed_format_args( parse::ParseMode::Format, ); - let mut unverified_pieces = Vec::new(); + let mut pieces = Vec::new(); while let Some(piece) = parser.next() { if !parser.errors.is_empty() { break; } else { - unverified_pieces.push(piece); + pieces.push(piece); } } + let is_literal = parser.is_literal; + if !parser.errors.is_empty() { let err = parser.errors.remove(0); - let sp = if efmt_kind_is_lit { + let sp = if is_literal { fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)) } else { // The format string could be another macro invocation, e.g.: @@ -1304,24 +232,21 @@ pub fn expand_preparsed_format_args( if let Some(note) = err.note { e.note(¬e); } - if let Some((label, span)) = err.secondary_label { - if efmt_kind_is_lit { - e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label); - } + if let Some((label, span)) = err.secondary_label && is_literal { + e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label); } if err.should_be_replaced_with_positional_argument { let captured_arg_span = fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)); - let positional_args = args.iter().filter(|arg| !arg.named).collect::<Vec<_>>(); if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { - let span = match positional_args.last() { + let span = match args.unnamed_args().last() { Some(arg) => arg.expr.span, - None => fmt_sp, + None => fmt_span, }; e.multipart_suggestion_verbose( "consider using a positional formatting argument instead", vec![ - (captured_arg_span, positional_args.len().to_string()), + (captured_arg_span, args.unnamed_args().len().to_string()), (span.shrink_to_hi(), format!(", {}", arg)), ], Applicability::MachineApplicable, @@ -1329,245 +254,626 @@ pub fn expand_preparsed_format_args( } } e.emit(); - return DummyResult::raw_expr(sp, true); + return Err(()); } - let arg_spans = parser - .arg_places - .iter() - .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end))) - .collect(); + let to_span = |inner_span: rustc_parse_format::InnerSpan| { + is_literal.then(|| { + fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }) + }) + }; - let named_pos: FxHashSet<usize> = names.values().cloned().map(|(i, _)| i).collect(); + let mut used = vec![false; args.explicit_args().len()]; + let mut invalid_refs = Vec::new(); + let mut numeric_refences_to_named_arg = Vec::new(); - let mut cx = Context { - ecx, - args: args.into_iter().map(|arg| arg.expr).collect(), - num_captured_args: 0, - arg_types, - arg_unique_types, - names, - curarg: 0, - curpiece: 0, - arg_index_map: Vec::new(), - count_args: Vec::new(), - count_positions: FxHashMap::default(), - count_positions_count: 0, - count_args_index_offset: 0, - literal: String::new(), - pieces: Vec::with_capacity(unverified_pieces.len()), - str_pieces: Vec::with_capacity(unverified_pieces.len()), - all_pieces_simple: true, - macsp, - fmtsp: fmt_span, - invalid_refs: Vec::new(), - arg_spans, - arg_with_formatting: Vec::new(), - is_literal: parser.is_literal, - unused_names_lint: PositionalNamedArgsLint { positional_named_args: vec![] }, + enum ArgRef<'a> { + Index(usize), + Name(&'a str, Option<Span>), + } + use ArgRef::*; + + let mut lookup_arg = |arg: ArgRef<'_>, + span: Option<Span>, + used_as: PositionUsedAs, + kind: FormatArgPositionKind| + -> FormatArgPosition { + let index = match arg { + Index(index) => { + if let Some(arg) = args.by_index(index) { + used[index] = true; + if arg.kind.ident().is_some() { + // This was a named argument, but it was used as a positional argument. + numeric_refences_to_named_arg.push((index, span, used_as)); + } + Ok(index) + } else { + // Doesn't exist as an explicit argument. + invalid_refs.push((index, span, used_as, kind)); + Err(index) + } + } + Name(name, span) => { + let name = Symbol::intern(name); + if let Some((index, _)) = args.by_name(name) { + // Name found in `args`, so we resolve it to its index. + if index < args.explicit_args().len() { + // Mark it as used, if it was an explicit argument. + used[index] = true; + } + Ok(index) + } else { + // Name not found in `args`, so we add it as an implicitly captured argument. + let span = span.unwrap_or(fmt_span); + let ident = Ident::new(name, span); + let expr = if is_literal { + ecx.expr_ident(span, ident) + } else { + // For the moment capturing variables from format strings expanded from macros is + // disabled (see RFC #2795) + ecx.struct_span_err(span, &format!("there is no argument named `{name}`")) + .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?")) + .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro") + .emit(); + DummyResult::raw_expr(span, true) + }; + Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) + } + } + }; + FormatArgPosition { index, kind, span } }; - // 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 mut template = Vec::new(); + let mut unfinished_literal = String::new(); + let mut placeholder_index = 0; - let numbered_position_args = pieces.iter().any(|arg: &parse::Piece<'_>| match *arg { - parse::String(_) => false, - parse::NextArgument(arg) => matches!(arg.position, parse::Position::ArgumentIs(..)), - }); + for piece in pieces { + match piece { + parse::Piece::String(s) => { + unfinished_literal.push_str(s); + } + parse::Piece::NextArgument(parse::Argument { position, position_span, format }) => { + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); + unfinished_literal.clear(); + } - cx.build_index_map(); + let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s)); + placeholder_index += 1; + + let position_span = to_span(position_span); + let argument = match position { + parse::ArgumentImplicitlyIs(i) => lookup_arg( + Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Implicit, + ), + parse::ArgumentIs(i) => lookup_arg( + Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Number, + ), + parse::ArgumentNamed(name) => lookup_arg( + Name(name, position_span), + position_span, + Placeholder(span), + FormatArgPositionKind::Named, + ), + }; - let mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()]; + let alignment = match format.align { + parse::AlignUnknown => None, + parse::AlignLeft => Some(FormatAlignment::Left), + parse::AlignRight => Some(FormatAlignment::Right), + parse::AlignCenter => Some(FormatAlignment::Center), + }; - 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); + let format_trait = match format.ty { + "" => FormatTrait::Display, + "?" => FormatTrait::Debug, + "e" => FormatTrait::LowerExp, + "E" => FormatTrait::UpperExp, + "o" => FormatTrait::Octal, + "p" => FormatTrait::Pointer, + "b" => FormatTrait::Binary, + "x" => FormatTrait::LowerHex, + "X" => FormatTrait::UpperHex, + _ => { + invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span); + FormatTrait::Display + } + }; + + let precision_span = format.precision_span.and_then(to_span); + let precision = match format.precision { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + Name(name, to_span(name_span)), + precision_span, + Precision, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + Index(i), + precision_span, + Precision, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg( + Index(i), + precision_span, + Precision, + FormatArgPositionKind::Implicit, + ))), + parse::CountImplied => None, + }; + + let width_span = format.width_span.and_then(to_span); + let width = match format.width { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + Name(name, to_span(name_span)), + width_span, + Width, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + Index(i), + width_span, + Width, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(_) => unreachable!(), + parse::CountImplied => None, + }; + + template.push(FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span, + format_trait, + format_options: FormatOptions { + fill: format.fill, + alignment, + flags: format.flags, + precision, + width, + }, + })); + } } } - if !cx.literal.is_empty() { - let s = cx.build_literal_string(); - cx.str_pieces.push(s); + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal))); } - if !cx.invalid_refs.is_empty() { - cx.report_invalid_references(numbered_position_args); + if !invalid_refs.is_empty() { + report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser); } - // Make sure that all arguments were used and all arguments have types. - let errs = cx - .arg_types + let unused = used .iter() .enumerate() - .filter(|(i, ty)| ty.is_empty() && !cx.count_positions.contains_key(&i)) + .filter(|&(_, used)| !used) .map(|(i, _)| { - let msg = if named_pos.contains(&i) { - // named argument + let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind { "named argument never used" } else { - // positional argument "argument never used" }; - (cx.args[i].span, msg) + (args.explicit_args()[i].expr.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; + if !unused.is_empty() { + // If there's a lot of unused arguments, + // let's check if this format arguments looks like another syntax (printf / shell). + let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2; + report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span); + } - 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); + // Only check for unused named argument names if there are no other errors to avoid causing + // too much noise in output errors, such as when a named argument is entirely unused. + if invalid_refs.is_empty() && ecx.sess.err_count() == 0 { + for &(index, span, used_as) in &numeric_refences_to_named_arg { + let (position_sp_to_replace, position_sp_for_msg) = match used_as { + Placeholder(pspan) => (span, pspan), + Precision => { + // Strip the leading `.` for precision. + let span = span.map(|span| span.with_lo(span.lo() + BytePos(1))); + (span, span) } - diag - } - }; + Width => (span, span), + }; + let arg_name = args.explicit_args()[index].kind.ident().unwrap(); + ecx.buffered_early_lint.push(BufferedEarlyLint { + span: arg_name.span.into(), + msg: format!("named argument `{}` is not used by name", arg_name.name).into(), + node_id: rustc_ast::CRATE_NODE_ID, + lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY), + diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally { + position_sp_to_replace, + position_sp_for_msg, + named_arg_sp: arg_name.span, + named_arg_name: arg_name.name.to_string(), + is_formatting_arg: matches!(used_as, Width | Precision), + }, + }); + } + } - // 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; + Ok(FormatArgs { span: fmt_span, template, arguments: args }) +} - // 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(); +fn invalid_placeholder_type_error( + ecx: &ExtCtxt<'_>, + ty: &str, + ty_span: Option<rustc_parse_format::InnerSpan>, + fmt_span: Span, +) { + let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end))); + let mut err = + ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty)); + err.note( + "the only appropriate formatting traits are:\n\ + - ``, which uses the `Display` trait\n\ + - `?`, which uses the `Debug` trait\n\ + - `e`, which uses the `LowerExp` trait\n\ + - `E`, which uses the `UpperExp` trait\n\ + - `o`, which uses the `Octal` trait\n\ + - `p`, which uses the `Pointer` trait\n\ + - `b`, which uses the `Binary` trait\n\ + - `x`, which uses the `LowerHex` trait\n\ + - `X`, which uses the `UpperHex` trait", + ); + if let Some(sp) = sp { + for (fmt, name) in &[ + ("", "Display"), + ("?", "Debug"), + ("e", "LowerExp"), + ("E", "UpperExp"), + ("o", "Octal"), + ("p", "Pointer"), + ("b", "Binary"), + ("x", "LowerHex"), + ("X", "UpperHex"), + ] { + err.tool_only_span_suggestion( + sp, + &format!("use the `{}` trait", name), + *fmt, + Applicability::MaybeIncorrect, + ); + } + } + err.emit(); +} - macro_rules! check_foreign { - ($kind:ident) => {{ - let mut show_doc_note = false; +fn report_missing_placeholders( + ecx: &mut ExtCtxt<'_>, + unused: Vec<(Span, &str)>, + detect_foreign_fmt: bool, + str_style: Option<usize>, + fmt_str: &str, + fmt_span: Span, +) { + let mut diag = if let &[(span, msg)] = &unused[..] { + let mut diag = ecx.struct_span_err(span, msg); + diag.span_label(span, msg); + diag + } else { + let mut diag = ecx.struct_span_err( + unused.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(), + "multiple unused formatting arguments", + ); + diag.span_label(fmt_span, "multiple missing formatting specifiers"); + for &(span, msg) in &unused { + diag.span_label(span, msg); + } + diag + }; - let mut suggestions = vec![]; - // account for `"` and account for raw strings `r#` - let padding = str_style.map(|i| i + 2).unwrap_or(1); - for sub in foreign::$kind::iter_subs(fmt_str, padding) { - let (trn, success) = match sub.translate() { - Ok(trn) => (trn, true), - Err(Some(msg)) => (msg, false), + // Used to ensure we only report translations for *one* kind of foreign format. + let mut found_foreign = false; + + // Decide if we want to look for foreign formatting directives. + if detect_foreign_fmt { + use super::format_foreign as foreign; + + // The set of foreign substitutions we've explained. This prevents spamming the user + // with `%d should be written as {}` over and over again. + let mut explained = FxHashSet::default(); + + macro_rules! check_foreign { + ($kind:ident) => {{ + let mut show_doc_note = false; + + let mut suggestions = vec![]; + // account for `"` and account for raw strings `r#` + let padding = str_style.map(|i| i + 2).unwrap_or(1); + for sub in foreign::$kind::iter_subs(fmt_str, padding) { + let (trn, success) = match sub.translate() { + Ok(trn) => (trn, true), + Err(Some(msg)) => (msg, false), + + // If it has no translation, don't call it out specifically. + _ => continue, + }; + + let pos = sub.position(); + let sub = String::from(sub.as_str()); + if explained.contains(&sub) { + continue; + } + explained.insert(sub.clone()); - // If it has no translation, don't call it out specifically. - _ => continue, - }; + if !found_foreign { + found_foreign = true; + show_doc_note = true; + } - let pos = sub.position(); - let sub = String::from(sub.as_str()); - if explained.contains(&sub) { - continue; - } - explained.insert(sub.clone()); + if let Some(inner_sp) = pos { + let sp = fmt_span.from_inner(inner_sp); - if !found_foreign { - found_foreign = true; - show_doc_note = true; + if success { + suggestions.push((sp, trn)); + } else { + diag.span_note( + sp, + &format!("format specifiers use curly braces, and {}", trn), + ); } - - if let Some(inner_sp) = pos { - let sp = fmt_sp.from_inner(inner_sp); - - if success { - suggestions.push((sp, trn)); - } else { - diag.span_note( - sp, - &format!("format specifiers use curly braces, and {}", trn), - ); - } + } else { + if success { + diag.help(&format!("`{}` should be written as `{}`", sub, trn)); } else { - if success { - diag.help(&format!("`{}` should be written as `{}`", sub, trn)); - } else { - diag.note(&format!( - "`{}` should use curly braces, and {}", - sub, trn - )); - } + diag.note(&format!("`{}` should use curly braces, and {}", sub, trn)); } } + } - if show_doc_note { - diag.note(concat!( - stringify!($kind), - " formatting not supported; see the documentation for `std::fmt`", - )); - } - if suggestions.len() > 0 { - diag.multipart_suggestion( - "format specifiers use curly braces", - suggestions, - Applicability::MachineApplicable, - ); - } - }}; - } - - check_foreign!(printf); - if !found_foreign { - check_foreign!(shell); - } - } - if !found_foreign && errs_len == 1 { - diag.span_label(cx.fmtsp, "formatting specifier missing"); + 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, + ); + } + }}; } - diag.emit(); - } else if cx.invalid_refs.is_empty() && cx.ecx.sess.err_count() == 0 { - // Only check for unused named argument names if there are no other errors to avoid causing - // too much noise in output errors, such as when a named argument is entirely unused. - create_lints_for_named_arguments_used_positionally(&mut cx); + check_foreign!(printf); + if !found_foreign { + check_foreign!(shell); + } + } + if !found_foreign && unused.len() == 1 { + diag.span_label(fmt_span, "formatting specifier missing"); } - cx.into_expr() + diag.emit(); } -fn may_contain_yield_point(e: &ast::Expr) -> bool { - struct MayContainYieldPoint(bool); +/// Handle invalid references to positional arguments. Output different +/// errors for the case where all arguments are positional and for when +/// there are named arguments or numbered positional arguments in the +/// format string. +fn report_invalid_references( + ecx: &mut ExtCtxt<'_>, + invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)], + template: &[FormatArgsPiece], + fmt_span: Span, + args: &FormatArguments, + parser: parse::Parser<'_>, +) { + let num_args_desc = match args.explicit_args().len() { + 0 => "no arguments were given".to_string(), + 1 => "there is 1 argument".to_string(), + n => format!("there are {} arguments", n), + }; + + let mut e; - impl Visitor<'_> for MayContainYieldPoint { - fn visit_expr(&mut self, e: &ast::Expr) { - if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind { - self.0 = true; - } else { - visit::walk_expr(self, e); + if template.iter().all(|piece| match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. }, + .. + }) => false, + FormatArgsPiece::Placeholder(FormatPlaceholder { + format_options: + FormatOptions { + precision: + Some(FormatCount::Argument(FormatArgPosition { + kind: FormatArgPositionKind::Number, + .. + })), + .. + } + | FormatOptions { + width: + Some(FormatCount::Argument(FormatArgPosition { + kind: FormatArgPositionKind::Number, + .. + })), + .. + }, + .. + }) => false, + _ => true, + }) { + // There are no numeric positions. + // Collect all the implicit positions: + let mut spans = Vec::new(); + let mut num_placeholders = 0; + for piece in template { + let mut placeholder = None; + // `{arg:.*}` + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + format_options: + FormatOptions { + precision: + Some(FormatCount::Argument(FormatArgPosition { + span, + kind: FormatArgPositionKind::Implicit, + .. + })), + .. + }, + .. + }) = piece + { + placeholder = *span; + num_placeholders += 1; } + // `{}` + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. }, + span, + .. + }) = piece + { + placeholder = *span; + num_placeholders += 1; + } + // For `{:.*}`, we only push one span. + spans.extend(placeholder); } - - fn visit_mac_call(&mut self, _: &ast::MacCall) { - self.0 = true; + let span = if spans.is_empty() { + MultiSpan::from_span(fmt_span) + } else { + MultiSpan::from_spans(spans) + }; + e = ecx.struct_span_err( + span, + &format!( + "{} positional argument{} in format string, but {}", + num_placeholders, + pluralize!(num_placeholders), + num_args_desc, + ), + ); + for arg in args.explicit_args() { + e.span_label(arg.expr.span, ""); + } + // Point out `{:.*}` placeholders: those take an extra argument. + let mut has_precision_star = false; + for piece in template { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + format_options: + FormatOptions { + precision: + Some(FormatCount::Argument(FormatArgPosition { + index, + span: Some(span), + kind: FormatArgPositionKind::Implicit, + .. + })), + .. + }, + .. + }) = piece + { + let (Ok(index) | Err(index)) = index; + has_precision_star = true; + e.span_label( + *span, + &format!( + "this precision flag adds an extra required argument at position {}, which is why there {} expected", + index, + if num_placeholders == 1 { + "is 1 argument".to_string() + } else { + format!("are {} arguments", num_placeholders) + }, + ), + ); + } + } + if has_precision_star { + e.note("positional arguments are zero-based"); } + } else { + let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect(); + // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` + // for `println!("{7:7$}", 1);` + indexes.sort(); + indexes.dedup(); + let span: MultiSpan = if !parser.is_literal || parser.arg_places.is_empty() { + MultiSpan::from_span(fmt_span) + } else { + MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect()) + }; + let arg_list = if let &[index] = &indexes[..] { + format!("argument {index}") + } else { + let tail = indexes.pop().unwrap(); + format!( + "arguments {head} and {tail}", + head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ") + ) + }; + e = ecx.struct_span_err( + span, + &format!("invalid reference to positional {} ({})", arg_list, num_args_desc), + ); + e.note("positional arguments are zero-based"); + } - fn visit_attribute(&mut self, _: &ast::Attribute) { - // Conservatively assume this may be a proc macro attribute in - // expression position. - self.0 = true; + if template.iter().any(|piece| match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => { + *f != FormatOptions::default() } + _ => false, + }) { + e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html"); + } - fn visit_item(&mut self, _: &ast::Item) { - // Do not recurse into nested items. + e.emit(); +} + +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)) => { + if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) { + MacEager::expr(expand_parsed_format_args(ecx, format_args)) + } else { + MacEager::expr(DummyResult::raw_expr(sp, true)) + } + } + Err(mut err) => { + err.emit(); + DummyResult::any(sp) } } +} - let mut visitor = MayContainYieldPoint(false); - visitor.visit_expr(e); - visitor.0 +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) } diff --git a/compiler/rustc_builtin_macros/src/format/ast.rs b/compiler/rustc_builtin_macros/src/format/ast.rs new file mode 100644 index 000000000..01dbffa21 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format/ast.rs @@ -0,0 +1,240 @@ +use rustc_ast::ptr::P; +use rustc_ast::Expr; +use rustc_data_structures::fx::FxHashMap; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::Span; + +// Definitions: +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └──────────────────────────────────────────────┘ +// FormatArgs +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └─────────┘ +// argument +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └───────────────────┘ +// template +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └────┘└─────────┘└┘ +// pieces +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └────┘ └┘ +// literal pieces +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └─────────┘ +// placeholder +// +// format_args!("hello {abc:.xyz$}!!", abc="world"); +// └─┘ └─┘ +// positions (could be names, numbers, empty, or `*`) + +/// (Parsed) format args. +/// +/// Basically the "AST" for a complete `format_args!()`. +/// +/// E.g., `format_args!("hello {name}");`. +#[derive(Clone, Debug)] +pub struct FormatArgs { + pub span: Span, + pub template: Vec<FormatArgsPiece>, + pub arguments: FormatArguments, +} + +/// A piece of a format template string. +/// +/// E.g. "hello" or "{name}". +#[derive(Clone, Debug)] +pub enum FormatArgsPiece { + Literal(Symbol), + Placeholder(FormatPlaceholder), +} + +/// The arguments to format_args!(). +/// +/// E.g. `1, 2, name="ferris", n=3`, +/// but also implicit captured arguments like `x` in `format_args!("{x}")`. +#[derive(Clone, Debug)] +pub struct FormatArguments { + arguments: Vec<FormatArgument>, + num_unnamed_args: usize, + num_explicit_args: usize, + names: FxHashMap<Symbol, usize>, +} + +impl FormatArguments { + pub fn new() -> Self { + Self { + arguments: Vec::new(), + names: FxHashMap::default(), + num_unnamed_args: 0, + num_explicit_args: 0, + } + } + + pub fn add(&mut self, arg: FormatArgument) -> usize { + let index = self.arguments.len(); + if let Some(name) = arg.kind.ident() { + self.names.insert(name.name, index); + } else if self.names.is_empty() { + // Only count the unnamed args before the first named arg. + // (Any later ones are errors.) + self.num_unnamed_args += 1; + } + if !matches!(arg.kind, FormatArgumentKind::Captured(..)) { + // This is an explicit argument. + // Make sure that all arguments so far are explcit. + assert_eq!( + self.num_explicit_args, + self.arguments.len(), + "captured arguments must be added last" + ); + self.num_explicit_args += 1; + } + self.arguments.push(arg); + index + } + + pub fn by_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> { + let i = *self.names.get(&name)?; + Some((i, &self.arguments[i])) + } + + pub fn by_index(&self, i: usize) -> Option<&FormatArgument> { + (i < self.num_explicit_args).then(|| &self.arguments[i]) + } + + pub fn unnamed_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_unnamed_args] + } + + pub fn named_args(&self) -> &[FormatArgument] { + &self.arguments[self.num_unnamed_args..self.num_explicit_args] + } + + pub fn explicit_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_explicit_args] + } + + pub fn into_vec(self) -> Vec<FormatArgument> { + self.arguments + } +} + +#[derive(Clone, Debug)] +pub struct FormatArgument { + pub kind: FormatArgumentKind, + pub expr: P<Expr>, +} + +#[derive(Clone, Debug)] +pub enum FormatArgumentKind { + /// `format_args(…, arg)` + Normal, + /// `format_args(…, arg = 1)` + Named(Ident), + /// `format_args("… {arg} …")` + Captured(Ident), +} + +impl FormatArgumentKind { + pub fn ident(&self) -> Option<Ident> { + match self { + &Self::Normal => None, + &Self::Named(id) => Some(id), + &Self::Captured(id) => Some(id), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FormatPlaceholder { + /// Index into [`FormatArgs::arguments`]. + pub argument: FormatArgPosition, + /// The span inside the format string for the full `{…}` placeholder. + pub span: Option<Span>, + /// `{}`, `{:?}`, or `{:x}`, etc. + pub format_trait: FormatTrait, + /// `{}` or `{:.5}` or `{:-^20}`, etc. + pub format_options: FormatOptions, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FormatArgPosition { + /// Which argument this position refers to (Ok), + /// or would've referred to if it existed (Err). + pub index: Result<usize, usize>, + /// What kind of position this is. See [`FormatArgPositionKind`]. + pub kind: FormatArgPositionKind, + /// The span of the name or number. + pub span: Option<Span>, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatArgPositionKind { + /// `{}` or `{:.*}` + Implicit, + /// `{1}` or `{:1$}` or `{:.1$}` + Number, + /// `{a}` or `{:a$}` or `{:.a$}` + Named, +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum FormatTrait { + /// `{}` + Display, + /// `{:?}` + Debug, + /// `{:e}` + LowerExp, + /// `{:E}` + UpperExp, + /// `{:o}` + Octal, + /// `{:p}` + Pointer, + /// `{:b}` + Binary, + /// `{:x}` + LowerHex, + /// `{:X}` + UpperHex, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct FormatOptions { + /// The width. E.g. `{:5}` or `{:width$}`. + pub width: Option<FormatCount>, + /// The precision. E.g. `{:.5}` or `{:.precision$}`. + pub precision: Option<FormatCount>, + /// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`. + pub alignment: Option<FormatAlignment>, + /// The fill character. E.g. the `.` in `{:.>10}`. + pub fill: Option<char>, + /// The `+`, `-`, `0`, `#`, `x?` and `X?` flags. + pub flags: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FormatAlignment { + /// `{:<}` + Left, + /// `{:>}` + Right, + /// `{:^}` + Center, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FormatCount { + /// `{:5}` or `{:.5}` + Literal(usize), + /// `{:.*}`, `{:.5$}`, or `{:a$}`, etc. + Argument(FormatArgPosition), +} diff --git a/compiler/rustc_builtin_macros/src/format/expand.rs b/compiler/rustc_builtin_macros/src/format/expand.rs new file mode 100644 index 000000000..9dde5efcb --- /dev/null +++ b/compiler/rustc_builtin_macros/src/format/expand.rs @@ -0,0 +1,353 @@ +use super::*; +use rustc_ast as ast; +use rustc_ast::visit::{self, Visitor}; +use rustc_ast::{BlockCheckMode, UnsafeSource}; +use rustc_data_structures::fx::FxIndexSet; +use rustc_span::{sym, symbol::kw}; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} + +fn make_argument(ecx: &ExtCtxt<'_>, sp: Span, arg: P<ast::Expr>, ty: ArgumentType) -> P<ast::Expr> { + // Generate: + // ::core::fmt::ArgumentV1::new_…(arg) + use ArgumentType::*; + use FormatTrait::*; + ecx.expr_call_global( + sp, + ecx.std_path(&[ + sym::fmt, + sym::ArgumentV1, + match ty { + Format(Display) => sym::new_display, + Format(Debug) => sym::new_debug, + Format(LowerExp) => sym::new_lower_exp, + Format(UpperExp) => sym::new_upper_exp, + Format(Octal) => sym::new_octal, + Format(Pointer) => sym::new_pointer, + Format(Binary) => sym::new_binary, + Format(LowerHex) => sym::new_lower_hex, + Format(UpperHex) => sym::new_upper_hex, + Usize => sym::from_usize, + }, + ]), + vec![arg], + ) +} + +fn make_count( + ecx: &ExtCtxt<'_>, + sp: Span, + count: &Option<FormatCount>, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, +) -> P<ast::Expr> { + // Generate: + // ::core::fmt::rt::v1::Count::…(…) + match count { + Some(FormatCount::Literal(n)) => ecx.expr_call_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Is]), + vec![ecx.expr_usize(sp, *n)], + ), + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + ecx.expr_call_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Param]), + vec![ecx.expr_usize(sp, i)], + ) + } else { + DummyResult::raw_expr(sp, true) + } + } + None => ecx.expr_path(ecx.path_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Implied]), + )), + } +} + +fn make_format_spec( + ecx: &ExtCtxt<'_>, + sp: Span, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, +) -> P<ast::Expr> { + // Generate: + // ::core::fmt::rt::v1::Argument { + // position: 0usize, + // format: ::core::fmt::rt::v1::FormatSpec { + // fill: ' ', + // align: ::core::fmt::rt::v1::Alignment::Unknown, + // flags: 0u32, + // precision: ::core::fmt::rt::v1::Count::Implied, + // width: ::core::fmt::rt::v1::Count::Implied, + // }, + // } + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + ecx.expr_usize(sp, i) + } + Err(_) => DummyResult::raw_expr(sp, true), + }; + let fill = ecx.expr_char(sp, placeholder.format_options.fill.unwrap_or(' ')); + let align = ecx.expr_path(ecx.path_global( + sp, + ecx.std_path(&[ + sym::fmt, + sym::rt, + sym::v1, + sym::Alignment, + match placeholder.format_options.alignment { + Some(FormatAlignment::Left) => sym::Left, + Some(FormatAlignment::Right) => sym::Right, + Some(FormatAlignment::Center) => sym::Center, + None => sym::Unknown, + }, + ]), + )); + let flags = ecx.expr_u32(sp, placeholder.format_options.flags); + let prec = make_count(ecx, sp, &placeholder.format_options.precision, argmap); + let width = make_count(ecx, sp, &placeholder.format_options.width, argmap); + ecx.expr_struct( + sp, + ecx.path_global(sp, ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Argument])), + vec![ + ecx.field_imm(sp, Ident::new(sym::position, sp), position), + ecx.field_imm( + sp, + Ident::new(sym::format, sp), + ecx.expr_struct( + sp, + ecx.path_global( + sp, + ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::FormatSpec]), + ), + vec![ + ecx.field_imm(sp, Ident::new(sym::fill, sp), fill), + ecx.field_imm(sp, Ident::new(sym::align, sp), align), + ecx.field_imm(sp, Ident::new(sym::flags, sp), flags), + ecx.field_imm(sp, Ident::new(sym::precision, sp), prec), + ecx.field_imm(sp, Ident::new(sym::width, sp), width), + ], + ), + ), + ], + ) +} + +pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<ast::Expr> { + let macsp = ecx.with_def_site_ctxt(ecx.call_site()); + + let lit_pieces = ecx.expr_array_ref( + fmt.span, + fmt.template + .iter() + .enumerate() + .filter_map(|(i, piece)| match piece { + &FormatArgsPiece::Literal(s) => Some(ecx.expr_str(fmt.span, s)), + &FormatArgsPiece::Placeholder(_) => { + // Inject empty string before placeholders when not already preceded by a literal piece. + if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) { + Some(ecx.expr_str(fmt.span, kw::Empty)) + } else { + None + } + } + }) + .collect(), + ); + + // Whether we'll use the `Arguments::new_v1_formatted` form (true), + // or the `Arguments::new_v1` form (false). + let mut use_format_options = false; + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + let mut argmap = FxIndexSet::default(); + for piece in &fmt.template { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if placeholder.format_options != Default::default() { + // Can't use basic form if there's any formatting options. + use_format_options = true; + } + if let Ok(index) = placeholder.argument.index { + if !argmap.insert((index, ArgumentType::Format(placeholder.format_trait))) { + // Duplicate (argument, format trait) combination, + // which we'll only put once in the args array. + use_format_options = true; + } + } + } + + let format_options = use_format_options.then(|| { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + ecx.expr_array_ref( + macsp, + fmt.template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(make_format_spec(ecx, macsp, placeholder, &mut argmap)) + }) + .collect(), + ) + }); + + let arguments = fmt.arguments.into_vec(); + + // If the args array contains exactly all the original arguments once, + // in order, we can use a simple array instead of a `match` construction. + // However, if there's a yield point in any argument except the first one, + // we don't do this, because an ArgumentV1 cannot be kept across yield points. + let use_simple_array = argmap.len() == arguments.len() + && argmap.iter().enumerate().all(|(i, &(j, _))| i == j) + && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr)); + + let args = if use_simple_array { + // Generate: + // &[ + // ::core::fmt::ArgumentV1::new_display(&arg0), + // ::core::fmt::ArgumentV1::new_lower_hex(&arg1), + // ::core::fmt::ArgumentV1::new_debug(&arg2), + // ] + ecx.expr_array_ref( + macsp, + arguments + .into_iter() + .zip(argmap) + .map(|(arg, (_, ty))| { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + make_argument(ecx, sp, ecx.expr_addr_of(sp, arg.expr), ty) + }) + .collect(), + ) + } else { + // Generate: + // match (&arg0, &arg1, &arg2) { + // args => &[ + // ::core::fmt::ArgumentV1::new_display(args.0), + // ::core::fmt::ArgumentV1::new_lower_hex(args.1), + // ::core::fmt::ArgumentV1::new_debug(args.0), + // ] + // } + let args_ident = Ident::new(sym::args, macsp); + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + if let Some(arg) = arguments.get(arg_index) { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + make_argument( + ecx, + sp, + ecx.expr_field( + sp, + ecx.expr_ident(macsp, args_ident), + Ident::new(sym::integer(arg_index), macsp), + ), + ty, + ) + } else { + DummyResult::raw_expr(macsp, true) + } + }) + .collect(); + ecx.expr_addr_of( + macsp, + ecx.expr_match( + macsp, + ecx.expr_tuple( + macsp, + arguments + .into_iter() + .map(|arg| { + ecx.expr_addr_of(arg.expr.span.with_ctxt(macsp.ctxt()), arg.expr) + }) + .collect(), + ), + vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))], + ), + ) + }; + + if let Some(format_options) = format_options { + // Generate: + // ::core::fmt::Arguments::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + ecx.expr_call_global( + macsp, + ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1_formatted]), + vec![ + lit_pieces, + args, + format_options, + ecx.expr_block(P(ast::Block { + stmts: vec![ecx.stmt_expr(ecx.expr_call_global( + macsp, + ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]), + Vec::new(), + ))], + id: ast::DUMMY_NODE_ID, + rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated), + span: macsp, + tokens: None, + could_be_bare_literal: false, + })), + ], + ) + } else { + // Generate: + // ::core::fmt::Arguments::new_v1( + // lit_pieces, + // args, + // ) + ecx.expr_call_global( + macsp, + ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1]), + vec![lit_pieces, args], + ) + } +} + +fn may_contain_yield_point(e: &ast::Expr) -> bool { + struct MayContainYieldPoint(bool); + + impl Visitor<'_> for MayContainYieldPoint { + fn visit_expr(&mut self, e: &ast::Expr) { + if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind { + self.0 = true; + } else { + visit::walk_expr(self, e); + } + } + + fn visit_mac_call(&mut self, _: &ast::MacCall) { + self.0 = true; + } + + fn visit_attribute(&mut self, _: &ast::Attribute) { + // Conservatively assume this may be a proc macro attribute in + // expression position. + self.0 = true; + } + + fn visit_item(&mut self, _: &ast::Item) { + // Do not recurse into nested items. + } + } + + let mut visitor = MayContainYieldPoint(false); + visitor.visit_expr(e); + visitor.0 +} diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs index 36cfbba45..45b9b8ab6 100644 --- a/compiler/rustc_builtin_macros/src/global_allocator.rs +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -4,11 +4,12 @@ use rustc_ast::expand::allocator::{ AllocatorKind, AllocatorMethod, AllocatorTy, ALLOCATOR_METHODS, }; use rustc_ast::ptr::P; -use rustc_ast::{self as ast, Attribute, Expr, FnHeader, FnSig, Generics, Param, StmtKind}; +use rustc_ast::{self as ast, AttrVec, Expr, FnHeader, FnSig, Generics, Param, StmtKind}; use rustc_ast::{Fn, ItemKind, Mutability, Stmt, Ty, TyKind, Unsafe}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::Span; +use thin_vec::thin_vec; pub fn expand( ecx: &mut ExtCtxt<'_>, @@ -113,10 +114,10 @@ impl AllocFnFactory<'_, '_> { self.cx.expr_call(self.ty_span, method, args) } - fn attrs(&self) -> Vec<Attribute> { + fn attrs(&self) -> AttrVec { let special = sym::rustc_std_internal_symbol; let special = self.cx.meta_word(self.span, special); - vec![self.cx.attribute(special)] + thin_vec![self.cx.attribute(special)] } fn arg_ty( diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 11565ba72..c7ea7de8f 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -7,15 +7,18 @@ #![feature(box_patterns)] #![feature(decl_macro)] #![feature(if_let_guard)] +#![feature(is_some_and)] #![feature(is_sorted)] #![feature(let_chains)] -#![feature(let_else)] #![feature(proc_macro_internals)] #![feature(proc_macro_quote)] #![recursion_limit = "256"] extern crate proc_macro; +#[macro_use] +extern crate tracing; + use crate::deriving::*; use rustc_expand::base::{MacroExpanderFn, ResolverExpand, SyntaxExtensionKind}; diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index 5cfda3349..ebe1c3663 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -281,7 +281,7 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> { let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id()); let proc_macro = Ident::new(sym::proc_macro, span); - let krate = cx.item(span, proc_macro, Vec::new(), ast::ItemKind::ExternCrate(None)); + let krate = cx.item(span, proc_macro, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)); let bridge = Ident::new(sym::bridge, span); let client = Ident::new(sym::client, span); diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index 8bf3a0799..d78bbc3c9 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -216,7 +216,7 @@ pub fn expand_include_bytes( } }; match cx.source_map().load_binary_file(&file) { - Ok(bytes) => base::MacEager::expr(cx.expr_lit(sp, ast::LitKind::ByteStr(bytes.into()))), + Ok(bytes) => base::MacEager::expr(cx.expr_byte_str(sp, 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 index 09ad5f9b3..49ef538f0 100644 --- a/compiler/rustc_builtin_macros/src/standard_library_imports.rs +++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs @@ -6,6 +6,7 @@ use rustc_span::edition::Edition::*; use rustc_span::hygiene::AstPass; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::DUMMY_SP; +use thin_vec::thin_vec; pub fn inject( mut krate: ast::Crate, @@ -51,7 +52,7 @@ pub fn inject( cx.item( span, ident, - vec![cx.attribute(cx.meta_word(span, sym::macro_use))], + thin_vec![cx.attribute(cx.meta_word(span, sym::macro_use))], ast::ItemKind::ExternCrate(None), ), ); @@ -78,7 +79,7 @@ pub fn inject( let use_item = cx.item( span, Ident::empty(), - vec![cx.attribute(cx.meta_word(span, sym::prelude_import))], + thin_vec![cx.attribute(cx.meta_word(span, sym::prelude_import))], ast::ItemKind::Use(ast::UseTree { prefix: cx.path(span, import_path), kind: ast::UseTreeKind::Glob, diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index e20375689..fee5d04cd 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -1,7 +1,6 @@ /// The expansion from a test function to the appropriate test struct for libtest /// Ideally, this code would be in libtest but for efficiency and error messages it lives here. use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; - use rustc_ast as ast; use rustc_ast::attr; use rustc_ast::ptr::P; @@ -11,8 +10,8 @@ use rustc_expand::base::*; use rustc_session::Session; use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::Span; - use std::iter; +use thin_vec::thin_vec; // #[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 @@ -37,13 +36,22 @@ pub fn expand_test_case( let sp = ecx.with_def_site_ctxt(attr_sp); let mut item = anno_item.expect_item(); item = item.map(|mut item| { + let test_path_symbol = Symbol::intern(&item_path( + // skip the name of the root module + &ecx.current_expansion.module.mod_path[1..], + &item.ident, + )); item.vis = ast::Visibility { span: item.vis.span, kind: ast::VisibilityKind::Public, tokens: None, }; item.ident.span = item.ident.span.with_ctxt(sp.ctxt()); - item.attrs.push(ecx.attribute(ecx.meta_word(sp, sym::rustc_test_marker))); + item.attrs.push(ecx.attribute(attr::mk_name_value_item_str( + Ident::new(sym::rustc_test_marker, sp), + test_path_symbol, + sp, + ))); item }); @@ -116,7 +124,7 @@ pub fn expand_test_or_bench( // reworked in the future to not need it, it'd be nice. _ => diag.struct_span_err(attr_sp, msg).forget_guarantee(), }; - err.span_label(attr_sp, "the `#[test]` macro causes a a function to be run on a test and has no effect on non-functions") + err.span_label(attr_sp, "the `#[test]` macro causes a function to be run on a test and has no effect on non-functions") .span_label(item.span, format!("expected a non-associated function, found {} {}", item.kind.article(), item.kind.descr())) .span_suggestion(attr_sp, "replace with conditional compilation to make the item only exist when tests are being run", "#[cfg(test)]", Applicability::MaybeIncorrect) .emit(); @@ -216,18 +224,29 @@ pub fn expand_test_or_bench( ) }; + let test_path_symbol = Symbol::intern(&item_path( + // skip the name of the root module + &cx.current_expansion.module.mod_path[1..], + &item.ident, + )); + let mut test_const = cx.item( sp, Ident::new(item.ident.name, sp), - vec![ + thin_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)), - ], + // #[rustc_test_marker = "test_case_sort_key"] + cx.attribute(attr::mk_name_value_item_str( + Ident::new(sym::rustc_test_marker, attr_sp), + test_path_symbol, + attr_sp, + )), + ] + .into(), // const $ident: test::TestDescAndFn = ast::ItemKind::Const( ast::Defaultness::Final, @@ -251,14 +270,7 @@ pub fn expand_test_or_bench( 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, - )), - )], + vec![cx.expr_str(sp, test_path_symbol)], ), ), // ignore: true | false @@ -334,9 +346,9 @@ pub fn expand_test_or_bench( }); // extern crate test - let test_extern = cx.item(sp, test_id, vec![], ast::ItemKind::ExternCrate(None)); + let test_extern = cx.item(sp, test_id, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)); - tracing::debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); + debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); if is_stmt { vec![ diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 0ebe29df9..b8b8351a3 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -14,13 +14,15 @@ 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 thin_vec::thin_vec; use std::{iter, mem}; +#[derive(Clone)] struct Test { span: Span, ident: Ident, + name: Symbol, } struct TestCtxt<'a> { @@ -120,10 +122,10 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> { 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) { + if let Some(name) = get_test_name(&self.cx.ext_cx.sess, &item) { debug!("this is a test item"); - let test = Test { span: item.span, ident: item.ident }; + let test = Test { span: item.span, ident: item.ident, name }; self.tests.push(test); } @@ -187,7 +189,10 @@ impl<'a> MutVisitor for EntryPointCleaner<'a> { 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 allow_dead_code = attr::mk_attr_outer( + &self.sess.parse_sess.attr_id_generator, + allow_dead_code_item, + ); let attrs = attrs .into_iter() .filter(|attr| { @@ -298,8 +303,10 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> { 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))); + let test_extern_stmt = ecx.stmt_item( + sp, + ecx.item(sp, test_id, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)), + ); // #[rustc_main] let main_meta = ecx.meta_word(sp, sym::rustc_main); @@ -333,7 +340,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> { let main = P(ast::Item { ident: main_id, - attrs: vec![main_attr], + attrs: thin_vec![main_attr], id: ast::DUMMY_NODE_ID, kind: main, vis: ast::Visibility { span: sp, kind: ast::VisibilityKind::Public, tokens: None }, @@ -352,9 +359,12 @@ fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> { debug!("building test vector from {} tests", cx.test_cases.len()); let ecx = &cx.ext_cx; + let mut tests = cx.test_cases.clone(); + tests.sort_by(|a, b| a.name.as_str().cmp(&b.name.as_str())); + ecx.expr_array_ref( sp, - cx.test_cases + tests .iter() .map(|test| { ecx.expr_addr_of(test.span, ecx.expr_path(ecx.path(test.span, vec![test.ident]))) @@ -363,8 +373,8 @@ fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> { ) } -fn is_test_case(sess: &Session, i: &ast::Item) -> bool { - sess.contains_name(&i.attrs, sym::rustc_test_marker) +fn get_test_name(sess: &Session, i: &ast::Item) -> Option<Symbol> { + sess.first_attr_value_str_by_name(&i.attrs, sym::rustc_test_marker) } fn get_test_runner( |