diff options
Diffstat (limited to 'compiler/rustc_expand')
23 files changed, 582 insertions, 2244 deletions
diff --git a/compiler/rustc_expand/Cargo.toml b/compiler/rustc_expand/Cargo.toml index 63247f9d051..ce014364b0d 100644 --- a/compiler/rustc_expand/Cargo.toml +++ b/compiler/rustc_expand/Cargo.toml @@ -25,7 +25,6 @@ rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } -termcolor = "1.2" thin-vec = "0.2.12" tracing = "0.1" # tidy-alphabetical-end diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index fdd1a87cae8..530b37aadb1 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -10,6 +10,11 @@ expand_attribute_meta_item = expand_attribute_single_word = attribute must only be a single word +expand_attributes_on_expressions_experimental = + attributes on expressions are experimental + .help_outer_doc = `///` is used for outer documentation comments; for a plain comment, use `//` + .help_inner_doc = `//!` is used for inner documentation comments; for a plain comment, use `//` by removing the `!` or inserting a space in between them: `// !` + expand_attributes_wrong_form = attribute must be of form: `attributes(foo, bar)` @@ -30,8 +35,8 @@ expand_duplicate_matcher_binding = duplicate matcher binding .label = duplicate binding .label2 = previous binding -expand_expected_comma_in_list = - expected token: `,` +expand_empty_delegation_list = + empty list delegation is not supported expand_expected_paren_or_brace = expected `(` or `{"{"}`, found `{$token}` @@ -116,9 +121,6 @@ expand_must_repeat_once = expand_not_a_meta_item = not a meta item -expand_only_one_argument = - {$name} takes 1 argument - expand_only_one_word = must only be one word @@ -146,9 +148,6 @@ expand_remove_node_not_supported = expand_resolve_relative_path = cannot resolve relative path in non-file source `{$path}` -expand_takes_no_arguments = - {$name} takes no arguments - expand_trace_macro = trace_macro expand_unsupported_key_value = diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index b25dd8fe67b..91af8758e51 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -5,27 +5,26 @@ use crate::module::DirOwnership; use rustc_ast::attr::MarkedAttrs; use rustc_ast::ptr::P; -use rustc_ast::token::{self, Nonterminal}; +use rustc_ast::token::Nonterminal; use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind}; use rustc_attr::{self as attr, Deprecation, Stability}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sync::{self, Lrc}; -use rustc_errors::{Applicability, Diag, DiagCtxt, ErrorGuaranteed, PResult}; +use rustc_errors::{DiagCtxt, ErrorGuaranteed, PResult}; use rustc_feature::Features; use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT; use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, RegisteredTools}; use rustc_parse::{parser, MACRO_ARGUMENTS}; use rustc_session::config::CollapseMacroDebuginfo; -use rustc_session::errors::report_lit_error; use rustc_session::{parse::ParseSess, Limit, Session}; use rustc_span::def_id::{CrateNum, DefId, LocalDefId}; use rustc_span::edition::Edition; -use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId}; +use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{BytePos, FileName, Span, DUMMY_SP}; +use rustc_span::{FileName, Span, DUMMY_SP}; use smallvec::{smallvec, SmallVec}; use std::default::Default; use std::iter; @@ -33,8 +32,6 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use thin_vec::ThinVec; -pub(crate) use rustc_span::hygiene::MacroKind; - // When adding new variants, make sure to // adjust the `visit_*` / `flat_map_*` calls in `InvocationCollector` // to use `assign_id!` @@ -574,35 +571,6 @@ impl DummyResult { tokens: None, }) } - - /// A plain dummy pattern. - pub fn raw_pat(sp: Span) -> ast::Pat { - ast::Pat { id: ast::DUMMY_NODE_ID, kind: PatKind::Wild, span: sp, tokens: None } - } - - /// A plain dummy type. - pub fn raw_ty(sp: Span) -> P<ast::Ty> { - // FIXME(nnethercote): you might expect `ast::TyKind::Dummy` to be used here, but some - // values produced here end up being lowered to HIR, which `ast::TyKind::Dummy` does not - // support, so we use an empty tuple instead. - P(ast::Ty { - id: ast::DUMMY_NODE_ID, - kind: ast::TyKind::Tup(ThinVec::new()), - span: sp, - tokens: None, - }) - } - - /// A plain dummy crate. - pub fn raw_crate() -> ast::Crate { - ast::Crate { - attrs: Default::default(), - items: Default::default(), - spans: Default::default(), - id: ast::DUMMY_NODE_ID, - is_placeholder: Default::default(), - } - } } impl MacResult for DummyResult { @@ -611,7 +579,12 @@ impl MacResult for DummyResult { } fn make_pat(self: Box<DummyResult>) -> Option<P<ast::Pat>> { - Some(P(DummyResult::raw_pat(self.span))) + Some(P(ast::Pat { + id: ast::DUMMY_NODE_ID, + kind: PatKind::Wild, + span: self.span, + tokens: None, + })) } fn make_items(self: Box<DummyResult>) -> Option<SmallVec<[P<ast::Item>; 1]>> { @@ -639,7 +612,15 @@ impl MacResult for DummyResult { } fn make_ty(self: Box<DummyResult>) -> Option<P<ast::Ty>> { - Some(DummyResult::raw_ty(self.span)) + // FIXME(nnethercote): you might expect `ast::TyKind::Dummy` to be used here, but some + // values produced here end up being lowered to HIR, which `ast::TyKind::Dummy` does not + // support, so we use an empty tuple instead. + Some(P(ast::Ty { + id: ast::DUMMY_NODE_ID, + kind: ast::TyKind::Tup(ThinVec::new()), + span: self.span, + tokens: None, + })) } fn make_arms(self: Box<DummyResult>) -> Option<SmallVec<[ast::Arm; 1]>> { @@ -671,7 +652,13 @@ impl MacResult for DummyResult { } fn make_crate(self: Box<DummyResult>) -> Option<ast::Crate> { - Some(DummyResult::raw_crate()) + Some(ast::Crate { + attrs: Default::default(), + items: Default::default(), + spans: Default::default(), + id: ast::DUMMY_NODE_ID, + is_placeholder: Default::default(), + }) } } @@ -789,55 +776,50 @@ impl SyntaxExtension { } } - fn collapse_debuginfo_by_name(sess: &Session, attr: &Attribute) -> CollapseMacroDebuginfo { - use crate::errors::CollapseMacroDebuginfoIllegal; - // #[collapse_debuginfo] without enum value (#[collapse_debuginfo(no/external/yes)]) - // considered as `yes` - attr.meta_item_list().map_or(CollapseMacroDebuginfo::Yes, |l| { - let [NestedMetaItem::MetaItem(item)] = &l[..] else { - sess.dcx().emit_err(CollapseMacroDebuginfoIllegal { span: attr.span }); - return CollapseMacroDebuginfo::Unspecified; - }; - if !item.is_word() { - sess.dcx().emit_err(CollapseMacroDebuginfoIllegal { span: item.span }); - CollapseMacroDebuginfo::Unspecified - } else { - match item.name_or_empty() { - sym::no => CollapseMacroDebuginfo::No, - sym::external => CollapseMacroDebuginfo::External, - sym::yes => CollapseMacroDebuginfo::Yes, - _ => { - sess.dcx().emit_err(CollapseMacroDebuginfoIllegal { span: item.span }); - CollapseMacroDebuginfo::Unspecified - } - } - } - }) + fn collapse_debuginfo_by_name(attr: &Attribute) -> Result<CollapseMacroDebuginfo, Span> { + let list = attr.meta_item_list(); + let Some([NestedMetaItem::MetaItem(item)]) = list.as_deref() else { + return Err(attr.span); + }; + if !item.is_word() { + return Err(item.span); + } + + match item.name_or_empty() { + sym::no => Ok(CollapseMacroDebuginfo::No), + sym::external => Ok(CollapseMacroDebuginfo::External), + sym::yes => Ok(CollapseMacroDebuginfo::Yes), + _ => Err(item.path.span), + } } /// if-ext - if macro from different crate (related to callsite code) /// | cmd \ attr | no | (unspecified) | external | yes | /// | no | no | no | no | no | - /// | (unspecified) | no | no | if-ext | yes | + /// | (unspecified) | no | if-ext | if-ext | yes | /// | external | no | if-ext | if-ext | yes | /// | yes | yes | yes | yes | yes | - fn get_collapse_debuginfo(sess: &Session, attrs: &[ast::Attribute], is_local: bool) -> bool { - let mut collapse_debuginfo_attr = attr::find_by_name(attrs, sym::collapse_debuginfo) - .map(|v| Self::collapse_debuginfo_by_name(sess, v)) - .unwrap_or(CollapseMacroDebuginfo::Unspecified); - if collapse_debuginfo_attr == CollapseMacroDebuginfo::Unspecified - && attr::contains_name(attrs, sym::rustc_builtin_macro) - { - collapse_debuginfo_attr = CollapseMacroDebuginfo::Yes; - } - - let flag = sess.opts.unstable_opts.collapse_macro_debuginfo; - let attr = collapse_debuginfo_attr; - let ext = !is_local; + fn get_collapse_debuginfo(sess: &Session, attrs: &[ast::Attribute], ext: bool) -> bool { + let flag = sess.opts.cg.collapse_macro_debuginfo; + let attr = attr::find_by_name(attrs, sym::collapse_debuginfo) + .and_then(|attr| { + Self::collapse_debuginfo_by_name(attr) + .map_err(|span| { + sess.dcx().emit_err(errors::CollapseMacroDebuginfoIllegal { span }) + }) + .ok() + }) + .unwrap_or_else(|| { + if attr::contains_name(attrs, sym::rustc_builtin_macro) { + CollapseMacroDebuginfo::Yes + } else { + CollapseMacroDebuginfo::Unspecified + } + }); #[rustfmt::skip] let collapse_table = [ [false, false, false, false], - [false, false, ext, true], + [false, ext, ext, true], [false, ext, ext, true], [true, true, true, true], ]; @@ -864,7 +846,7 @@ impl SyntaxExtension { let local_inner_macros = attr::find_by_name(attrs, sym::macro_export) .and_then(|macro_export| macro_export.meta_item_list()) .is_some_and(|l| attr::list_contains_name(&l, sym::local_inner_macros)); - let collapse_debuginfo = Self::get_collapse_debuginfo(sess, attrs, is_local); + let collapse_debuginfo = Self::get_collapse_debuginfo(sess, attrs, !is_local); tracing::debug!(?name, ?local_inner_macros, ?collapse_debuginfo, ?allow_internal_unsafe); let (builtin_name, helper_attrs) = attr::find_by_name(attrs, sym::rustc_builtin_macro) @@ -968,7 +950,12 @@ impl SyntaxExtension { /// Error type that denotes indeterminacy. pub struct Indeterminate; -pub type DeriveResolutions = Vec<(ast::Path, Annotatable, Option<Lrc<SyntaxExtension>>, bool)>; +pub struct DeriveResolution { + pub path: ast::Path, + pub item: Annotatable, + pub exts: Option<Lrc<SyntaxExtension>>, + pub is_const: bool, +} pub trait ResolverExpand { fn next_node_id(&mut self) -> NodeId; @@ -1011,11 +998,11 @@ pub trait ResolverExpand { &mut self, expn_id: LocalExpnId, force: bool, - derive_paths: &dyn Fn() -> DeriveResolutions, + derive_paths: &dyn Fn() -> Vec<DeriveResolution>, ) -> Result<(), Indeterminate>; /// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId` /// back from resolver. - fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<DeriveResolutions>; + fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<Vec<DeriveResolution>>; /// Path resolution logic for `#[cfg_accessible(path)]`. fn cfg_accessible( &mut self, @@ -1269,181 +1256,6 @@ pub fn resolve_path(sess: &Session, path: impl Into<PathBuf>, span: Span) -> PRe } } -/// `Ok` represents successfully retrieving the string literal at the correct -/// position, e.g., `println("abc")`. -type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>; - -/// - `Ok` is returned when the conversion to a string literal is unsuccessful, -/// but another type of expression is obtained instead. -/// - `Err` is returned when the conversion process fails. -type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>; - -/// Extracts a string literal from the macro expanded version of `expr`, -/// returning a diagnostic error of `err_msg` if `expr` is not a string literal. -/// The returned bool indicates whether an applicable suggestion has already been -/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))` -/// indicates that an ast error was encountered. -// FIXME(Nilstrieb) Make this function setup translatable -#[allow(rustc::untranslatable_diagnostic)] -pub fn expr_to_spanned_string<'a>( - cx: &'a mut ExtCtxt<'_>, - expr: P<ast::Expr>, - err_msg: &'static str, -) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> { - if !cx.force_mode - && let ast::ExprKind::MacCall(m) = &expr.kind - && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() - { - return ExpandResult::Retry(()); - } - - // Perform eager expansion on the expression. - // We want to be able to handle e.g., `concat!("foo", "bar")`. - let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); - - ExpandResult::Ready(Err(match expr.kind { - ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) { - Ok(ast::LitKind::Str(s, style)) => { - return ExpandResult::Ready(Ok((s, style, expr.span))); - } - Ok(ast::LitKind::ByteStr(..)) => { - let mut err = cx.dcx().struct_span_err(expr.span, err_msg); - let span = expr.span.shrink_to_lo(); - err.span_suggestion( - span.with_hi(span.lo() + BytePos(1)), - "consider removing the leading `b`", - "", - Applicability::MaybeIncorrect, - ); - Ok((err, true)) - } - Ok(ast::LitKind::Err(guar)) => Err(guar), - Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)), - _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), - }, - ast::ExprKind::Err(guar) => Err(guar), - ast::ExprKind::Dummy => { - cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`") - } - _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)), - })) -} - -/// Extracts a string literal from the macro expanded version of `expr`, -/// emitting `err_msg` if `expr` is not a string literal. This does not stop -/// compilation on error, merely emits a non-fatal error and returns `Err`. -pub fn expr_to_string( - cx: &mut ExtCtxt<'_>, - expr: P<ast::Expr>, - err_msg: &'static str, -) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> { - expr_to_spanned_string(cx, expr, err_msg).map(|res| { - res.map_err(|err| match err { - Ok((err, _)) => err.emit(), - Err(guar) => guar, - }) - .map(|(symbol, style, _)| (symbol, style)) - }) -} - -/// Non-fatally assert that `tts` is empty. Note that this function -/// returns even when `tts` is non-empty, macros that *need* to stop -/// compilation should call `cx.diagnostic().abort_if_errors()` -/// (this should be done as rarely as possible). -pub fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) { - if !tts.is_empty() { - cx.dcx().emit_err(errors::TakesNoArguments { span, name }); - } -} - -/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`. -pub fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> { - let guar = match p.parse_expr() { - Ok(expr) => return Ok(expr), - Err(err) => err.emit(), - }; - while p.token != token::Eof { - p.bump(); - } - Err(guar) -} - -/// Interpreting `tts` as a comma-separated sequence of expressions, -/// expect exactly one string literal, or emit an error and return `Err`. -pub fn get_single_str_from_tts( - cx: &mut ExtCtxt<'_>, - span: Span, - tts: TokenStream, - name: &str, -) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> { - get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s)) -} - -pub fn get_single_str_spanned_from_tts( - cx: &mut ExtCtxt<'_>, - span: Span, - tts: TokenStream, - name: &str, -) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> { - let mut p = cx.new_parser_from_tts(tts); - if p.token == token::Eof { - let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); - return ExpandResult::Ready(Err(guar)); - } - let ret = match parse_expr(&mut p) { - Ok(ret) => ret, - Err(guar) => return ExpandResult::Ready(Err(guar)), - }; - let _ = p.eat(&token::Comma); - - if p.token != token::Eof { - cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); - } - expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| { - res.map_err(|err| match err { - Ok((err, _)) => err.emit(), - Err(guar) => guar, - }) - .map(|(symbol, _style, span)| (symbol, span)) - }) -} - -/// Extracts comma-separated expressions from `tts`. -/// On error, emit it, and return `Err`. -pub fn get_exprs_from_tts( - cx: &mut ExtCtxt<'_>, - tts: TokenStream, -) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> { - let mut p = cx.new_parser_from_tts(tts); - let mut es = Vec::new(); - while p.token != token::Eof { - let expr = match parse_expr(&mut p) { - Ok(expr) => expr, - Err(guar) => return ExpandResult::Ready(Err(guar)), - }; - if !cx.force_mode - && let ast::ExprKind::MacCall(m) = &expr.kind - && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err() - { - return ExpandResult::Retry(()); - } - - // Perform eager expansion on the expression. - // We want to be able to handle e.g., `concat!("foo", "bar")`. - let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr(); - - es.push(expr); - if p.eat(&token::Comma) { - continue; - } - if p.token != token::Eof { - let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span }); - return ExpandResult::Ready(Err(guar)); - } - } - ExpandResult::Ready(Ok(es)) -} - pub fn parse_macro_name_and_helper_attrs( dcx: &rustc_errors::DiagCtxt, attr: &Attribute, @@ -1552,18 +1364,15 @@ fn pretty_printing_compatibility_hack(item: &Item, sess: &Session) -> bool { }; if crate_matches { - // FIXME: make this translatable - #[allow(rustc::untranslatable_diagnostic)] - sess.psess.buffer_lint_with_diagnostic( - PROC_MACRO_BACK_COMPAT, - item.ident.span, - ast::CRATE_NODE_ID, - "using an old version of `rental`", - BuiltinLintDiag::ProcMacroBackCompat( - "older versions of the `rental` crate will stop compiling in future versions of Rust; \ - please update to `rental` v0.5.6, or switch to one of the `rental` alternatives".to_string() - ) - ); + sess.psess.buffer_lint( + PROC_MACRO_BACK_COMPAT, + item.ident.span, + ast::CRATE_NODE_ID, + BuiltinLintDiag::ProcMacroBackCompat { + crate_name: "rental".to_string(), + fixed_version: "0.5.6".to_string(), + }, + ); return true; } } diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index cdcf67b26f8..1b6e191c2eb 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -175,20 +175,6 @@ impl<'a> ExtCtxt<'a> { ast::Stmt { id: ast::DUMMY_NODE_ID, span: expr.span, kind: ast::StmtKind::Expr(expr) } } - pub fn stmt_let_pat(&self, sp: Span, pat: P<ast::Pat>, ex: P<ast::Expr>) -> ast::Stmt { - let local = P(ast::Local { - pat, - ty: None, - id: ast::DUMMY_NODE_ID, - kind: LocalKind::Init(ex), - span: sp, - colon_sp: None, - attrs: AttrVec::new(), - tokens: None, - }); - self.stmt_local(local, sp) - } - pub fn stmt_let(&self, sp: Span, mutbl: bool, ident: Ident, ex: P<ast::Expr>) -> ast::Stmt { self.stmt_let_ty(sp, mutbl, ident, None, ex) } @@ -202,7 +188,7 @@ impl<'a> ExtCtxt<'a> { ex: P<ast::Expr>, ) -> ast::Stmt { let pat = if mutbl { - self.pat_ident_binding_mode(sp, ident, ast::BindingAnnotation::MUT) + self.pat_ident_binding_mode(sp, ident, ast::BindingMode::MUT) } else { self.pat_ident(sp, ident) }; @@ -278,10 +264,6 @@ impl<'a> ExtCtxt<'a> { self.expr_ident(span, Ident::with_dummy_span(kw::SelfLower)) } - pub fn expr_field(&self, span: Span, expr: P<Expr>, field: Ident) -> P<ast::Expr> { - self.expr(span, ast::ExprKind::Field(expr, field)) - } - pub fn expr_macro_call(&self, span: Span, call: P<ast::MacCall>) -> P<ast::Expr> { self.expr(span, ast::ExprKind::MacCall(call)) } @@ -394,11 +376,6 @@ impl<'a> ExtCtxt<'a> { self.expr(span, ast::ExprKind::Lit(lit)) } - pub fn expr_char(&self, span: Span, ch: char) -> P<ast::Expr> { - let lit = token::Lit::new(token::Char, literal::escape_char_symbol(ch), None); - self.expr(span, ast::ExprKind::Lit(lit)) - } - pub fn expr_byte_str(&self, span: Span, bytes: Vec<u8>) -> P<ast::Expr> { let lit = token::Lit::new(token::ByteStr, literal::escape_byte_str_symbol(&bytes), None); self.expr(span, ast::ExprKind::Lit(lit)) @@ -414,10 +391,6 @@ impl<'a> ExtCtxt<'a> { self.expr_addr_of(sp, self.expr_array(sp, exprs)) } - pub fn expr_cast(&self, sp: Span, expr: P<ast::Expr>, ty: P<ast::Ty>) -> P<ast::Expr> { - self.expr(sp, ast::ExprKind::Cast(expr, ty)) - } - pub fn expr_some(&self, sp: Span, expr: P<ast::Expr>) -> P<ast::Expr> { let some = self.std_path(&[sym::option, sym::Option, sym::Some]); self.expr_call_global(sp, some, thin_vec![expr]) @@ -490,14 +463,14 @@ impl<'a> ExtCtxt<'a> { self.pat(span, PatKind::Lit(expr)) } pub fn pat_ident(&self, span: Span, ident: Ident) -> P<ast::Pat> { - self.pat_ident_binding_mode(span, ident, ast::BindingAnnotation::NONE) + self.pat_ident_binding_mode(span, ident, ast::BindingMode::NONE) } pub fn pat_ident_binding_mode( &self, span: Span, ident: Ident, - ann: ast::BindingAnnotation, + ann: ast::BindingMode, ) -> P<ast::Pat> { let pat = PatKind::Ident(ann, ident.with_span_pos(span), None); self.pat(span, pat) diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index c95d7cdeb73..badfa6d3aa3 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -14,12 +14,14 @@ use rustc_attr as attr; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_feature::Features; use rustc_feature::{ACCEPTED_FEATURES, REMOVED_FEATURES, UNSTABLE_FEATURES}; +use rustc_lint_defs::BuiltinLintDiag; use rustc_parse::validate_attr; use rustc_session::parse::feature_err; use rustc_session::Session; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; use thin_vec::ThinVec; +use tracing::instrument; /// A folder that strips out items that do not belong in the current configuration. pub struct StripUnconfigured<'a> { @@ -98,10 +100,11 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - // If the declared feature is unstable, record it. if let Some(f) = UNSTABLE_FEATURES.iter().find(|f| name == f.feature.name) { (f.set_enabled)(&mut features); - // When the ICE comes from core, alloc or std (approximation of the standard library), there's a chance - // that the person hitting the ICE may be using -Zbuild-std or similar with an untested target. - // The bug is probably in the standard library and not the compiler in that case, but that doesn't - // really matter - we want a bug report. + // When the ICE comes from core, alloc or std (approximation of the standard + // library), there's a chance that the person hitting the ICE may be using + // -Zbuild-std or similar with an untested target. The bug is probably in the + // standard library and not the compiler in that case, but that doesn't really + // matter - we want a bug report. if features.internal(name) && ![sym::core, sym::alloc, sym::std].contains(&crate_name) { @@ -199,10 +202,17 @@ impl<'a> StripUnconfigured<'a> { inner = self.configure_tokens(&inner); Some(AttrTokenTree::Delimited(sp, spacing, delim, inner)).into_iter() } - AttrTokenTree::Token(ref token, _) - if let TokenKind::Interpolated(nt) = &token.kind => - { - panic!("Nonterminal should have been flattened at {:?}: {:?}", token.span, nt); + AttrTokenTree::Token( + Token { + kind: + TokenKind::NtIdent(..) + | TokenKind::NtLifetime(..) + | TokenKind::Interpolated(..), + .. + }, + _, + ) => { + panic!("Nonterminal should have been flattened: {:?}", tree); } AttrTokenTree::Token(token, spacing) => { Some(AttrTokenTree::Token(token, spacing)).into_iter() @@ -239,7 +249,6 @@ impl<'a> StripUnconfigured<'a> { /// Gives a compiler warning when the `cfg_attr` contains no attributes and /// is in the original source file. Gives a compiler error if the syntax of /// the attribute is incorrect. - #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable pub(crate) fn expand_cfg_attr(&self, attr: &Attribute, recursive: bool) -> Vec<Attribute> { let Some((cfg_predicate, expanded_attrs)) = rustc_parse::parse_cfg_attr(attr, &self.sess.psess) @@ -253,7 +262,7 @@ impl<'a> StripUnconfigured<'a> { rustc_lint_defs::builtin::UNUSED_ATTRIBUTES, attr.span, ast::CRATE_NODE_ID, - "`#[cfg_attr]` does not expand to any attributes", + BuiltinLintDiag::CfgAttrNoAttributes, ); } @@ -274,7 +283,6 @@ impl<'a> StripUnconfigured<'a> { } } - #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable fn expand_cfg_attr_item( &self, attr: &Attribute, @@ -337,7 +345,7 @@ impl<'a> StripUnconfigured<'a> { rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, attr.span, ast::CRATE_NODE_ID, - "`crate_type` within an `#![cfg_attr] attribute is deprecated`", + BuiltinLintDiag::CrateTypeInCfgAttr, ); } if attr.has_name(sym::crate_name) { @@ -345,7 +353,7 @@ impl<'a> StripUnconfigured<'a> { rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, attr.span, ast::CRATE_NODE_ID, - "`crate_name` within an `#![cfg_attr] attribute is deprecated`", + BuiltinLintDiag::CrateNameInCfgAttr, ); } attr @@ -373,7 +381,6 @@ impl<'a> StripUnconfigured<'a> { } /// If attributes are not allowed on expressions, emit an error for `attr` - #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable #[instrument(level = "trace", skip(self))] pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) { if self.features.is_some_and(|features| !features.stmt_expr_attributes) @@ -383,11 +390,15 @@ impl<'a> StripUnconfigured<'a> { &self.sess, sym::stmt_expr_attributes, attr.span, - "attributes on expressions are experimental", + crate::fluent_generated::expand_attributes_on_expressions_experimental, ); if attr.is_doc_comment() { - err.help("`///` is for documentation comments. For a plain comment, use `//`."); + err.help(if attr.style == AttrStyle::Outer { + crate::fluent_generated::expand_help_outer_doc + } else { + crate::fluent_generated::expand_help_inner_doc + }); } err.emit(); diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index 21ce5e1d81e..a5fc9e9d89c 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -1,6 +1,6 @@ use rustc_ast::ast; use rustc_errors::codes::*; -use rustc_macros::Diagnostic; +use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_session::Limit; use rustc_span::symbol::{Ident, MacroRulesNormalizedIdent}; use rustc_span::{Span, Symbol}; @@ -153,29 +153,6 @@ pub(crate) struct HelperAttributeNameInvalid { } #[derive(Diagnostic)] -#[diag(expand_expected_comma_in_list)] -pub(crate) struct ExpectedCommaInList { - #[primary_span] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag(expand_only_one_argument)] -pub(crate) struct OnlyOneArgument<'a> { - #[primary_span] - pub span: Span, - pub name: &'a str, -} - -#[derive(Diagnostic)] -#[diag(expand_takes_no_arguments)] -pub(crate) struct TakesNoArguments<'a> { - #[primary_span] - pub span: Span, - pub name: &'a str, -} - -#[derive(Diagnostic)] #[diag(expand_feature_removed, code = E0557)] pub(crate) struct FeatureRemoved<'a> { #[primary_span] @@ -456,3 +433,10 @@ pub struct ExpectedParenOrBrace<'a> { pub span: Span, pub token: Cow<'a, str>, } + +#[derive(Diagnostic)] +#[diag(expand_empty_delegation_list)] +pub(crate) struct EmptyDelegationList { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 6029caa965c..d8f0f221189 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1,10 +1,9 @@ use crate::base::*; use crate::config::StripUnconfigured; use crate::errors::{ - IncompleteParse, RecursionLimitReached, RemoveExprNotSupported, RemoveNodeNotSupported, - UnsupportedKeyValue, WrongFragmentKind, + EmptyDelegationList, IncompleteParse, RecursionLimitReached, RemoveExprNotSupported, + RemoveNodeNotSupported, UnsupportedKeyValue, WrongFragmentKind, }; -use crate::hygiene::SyntaxContext; use crate::mbe::diagnostics::annotate_err_with_kind; use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod}; use crate::placeholders::{placeholder, PlaceholderExpander}; @@ -32,6 +31,7 @@ use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS}; use rustc_session::lint::BuiltinLintDiag; use rustc_session::parse::feature_err; use rustc_session::{Limit, Session}; +use rustc_span::hygiene::SyntaxContext; use rustc_span::symbol::{sym, Ident}; use rustc_span::{ErrorGuaranteed, FileName, LocalExpnId, Span}; @@ -87,7 +87,7 @@ macro_rules! ast_fragments { } impl AstFragment { - pub fn add_placeholders(&mut self, placeholders: &[NodeId]) { + fn add_placeholders(&mut self, placeholders: &[NodeId]) { if placeholders.is_empty() { return; } @@ -100,14 +100,14 @@ macro_rules! ast_fragments { } } - pub fn make_opt_expr(self) -> Option<P<ast::Expr>> { + pub(crate) fn make_opt_expr(self) -> Option<P<ast::Expr>> { match self { AstFragment::OptExpr(expr) => expr, _ => panic!("AstFragment::make_* called on the wrong kind of fragment"), } } - pub fn make_method_receiver_expr(self) -> P<ast::Expr> { + pub(crate) fn make_method_receiver_expr(self) -> P<ast::Expr> { match self { AstFragment::MethodReceiverExpr(expr) => expr, _ => panic!("AstFragment::make_* called on the wrong kind of fragment"), @@ -125,7 +125,7 @@ macro_rules! ast_fragments { T::fragment_to_output(self) } - pub fn mut_visit_with<F: MutVisitor>(&mut self, vis: &mut F) { + pub(crate) fn mut_visit_with<F: MutVisitor>(&mut self, vis: &mut F) { match self { AstFragment::OptExpr(opt_expr) => { visit_clobber(opt_expr, |opt_expr| { @@ -372,6 +372,14 @@ impl Invocation { InvocationKind::Derive { path, .. } => path.span, } } + + fn span_mut(&mut self) -> &mut Span { + match &mut self.kind { + InvocationKind::Bang { span, .. } => span, + InvocationKind::Attr { attr, .. } => &mut attr.span, + InvocationKind::Derive { path, .. } => &mut path.span, + } + } } pub struct MacroExpander<'a, 'b> { @@ -432,7 +440,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { break; } invocations = mem::take(&mut undetermined_invocations); - force = !mem::replace(&mut progress, false); + force = !progress; + progress = false; if force && self.monotonic { self.cx.dcx().span_delayed_bug( invocations.last().unwrap().0.span(), @@ -471,7 +480,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.force_mode = force; let fragment_kind = invoc.fragment_kind; - let (expanded_fragment, new_invocations) = match self.expand_invoc(invoc, &ext.kind) { + match self.expand_invoc(invoc, &ext.kind) { ExpandResult::Ready(fragment) => { let mut derive_invocations = Vec::new(); let derive_placeholders = self @@ -482,7 +491,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { derive_invocations.reserve(derives.len()); derives .into_iter() - .map(|(path, item, _exts, is_const)| { + .map(|DeriveResolution { path, item, exts: _, is_const }| { // FIXME: Consider using the derive resolutions (`_exts`) // instead of enqueuing the derives to be resolved again later. let expn_id = LocalExpnId::fresh_empty(); @@ -503,12 +512,19 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }) .unwrap_or_default(); - let (fragment, collected_invocations) = + let (expanded_fragment, collected_invocations) = self.collect_invocations(fragment, &derive_placeholders); - // We choose to expand any derive invocations associated with this macro invocation - // *before* any macro invocations collected from the output fragment + // We choose to expand any derive invocations associated with this macro + // invocation *before* any macro invocations collected from the output + // fragment. derive_invocations.extend(collected_invocations); - (fragment, derive_invocations) + + progress = true; + if expanded_fragments.len() < depth { + expanded_fragments.push(Vec::new()); + } + expanded_fragments[depth - 1].push((expn_id, expanded_fragment)); + invocations.extend(derive_invocations.into_iter().rev()); } ExpandResult::Retry(invoc) => { if force { @@ -519,17 +535,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> { } else { // Cannot expand, will retry this invocation later. undetermined_invocations.push((invoc, Some(ext))); - continue; } } - }; - - progress = true; - if expanded_fragments.len() < depth { - expanded_fragments.push(Vec::new()); } - expanded_fragments[depth - 1].push((expn_id, expanded_fragment)); - invocations.extend(new_invocations.into_iter().rev()); } self.cx.current_expansion = orig_expansion_data; @@ -590,11 +598,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { for (invoc, _) in invocations.iter_mut() { let expn_id = invoc.expansion_data.id; let parent_def = self.cx.resolver.invocation_parent(expn_id); - let span = match &mut invoc.kind { - InvocationKind::Bang { span, .. } => span, - InvocationKind::Attr { attr, .. } => &mut attr.span, - InvocationKind::Derive { path, .. } => &mut path.span, - }; + let span = invoc.span_mut(); *span = span.with_parent(Some(parent_def)); } } @@ -957,7 +961,7 @@ pub fn parse_ast_fragment<'a>( }) } -pub fn ensure_complete_parse<'a>( +pub(crate) fn ensure_complete_parse<'a>( parser: &Parser<'a>, macro_path: &ast::Path, kind_name: &str, @@ -1037,6 +1041,7 @@ enum AddSemicolon { trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { type OutputTy = SmallVec<[Self; 1]>; type AttrsTy: Deref<Target = [ast::Attribute]> = ast::AttrVec; + type ItemKind = ItemKind; const KIND: AstFragmentKind; fn to_annotatable(self) -> Annotatable; fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy; @@ -1055,6 +1060,18 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { fn take_mac_call(self) -> (P<ast::MacCall>, Self::AttrsTy, AddSemicolon) { unreachable!() } + fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> { + None + } + fn delegation_item_kind(_deleg: Box<ast::Delegation>) -> Self::ItemKind { + unreachable!() + } + fn from_item(_item: ast::Item<Self::ItemKind>) -> Self { + unreachable!() + } + fn flatten_outputs(_outputs: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy { + unreachable!() + } fn pre_flat_map_node_collect_attr(_cfg: &StripUnconfigured<'_>, _attr: &ast::Attribute) {} fn post_flat_map_node_collect_bang(_output: &mut Self::OutputTy, _add_semicolon: AddSemicolon) { } @@ -1102,6 +1119,21 @@ impl InvocationCollectorNode for P<ast::Item> { _ => unreachable!(), } } + fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> { + match &self.kind { + ItemKind::DelegationMac(deleg) => Some((deleg, self)), + _ => None, + } + } + fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind { + ItemKind::Delegation(deleg) + } + fn from_item(item: ast::Item<Self::ItemKind>) -> Self { + P(item) + } + fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy { + items.flatten().collect() + } fn wrap_flat_map_node_noop_flat_map( mut node: Self, collector: &mut InvocationCollector<'_, '_>, @@ -1190,8 +1222,8 @@ impl InvocationCollectorNode for P<ast::Item> { match &ut.kind { ast::UseTreeKind::Glob => {} ast::UseTreeKind::Simple(_) => idents.push(ut.ident()), - ast::UseTreeKind::Nested(nested) => { - for (ut, _) in nested { + ast::UseTreeKind::Nested { items, .. } => { + for (ut, _) in items { collect_use_tree_leaves(ut, idents); } } @@ -1210,6 +1242,7 @@ impl InvocationCollectorNode for P<ast::Item> { struct TraitItemTag; impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag> { type OutputTy = SmallVec<[P<ast::AssocItem>; 1]>; + type ItemKind = AssocItemKind; const KIND: AstFragmentKind = AstFragmentKind::TraitItems; fn to_annotatable(self) -> Annotatable { Annotatable::TraitItem(self.wrapped) @@ -1218,7 +1251,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag> fragment.make_trait_items() } fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy { - noop_flat_map_assoc_item(self.wrapped, visitor) + noop_flat_map_item(self.wrapped, visitor) } fn is_mac_call(&self) -> bool { matches!(self.wrapped.kind, AssocItemKind::MacCall(..)) @@ -1230,11 +1263,27 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag> _ => unreachable!(), } } + fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> { + match &self.wrapped.kind { + AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)), + _ => None, + } + } + fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind { + AssocItemKind::Delegation(deleg) + } + fn from_item(item: ast::Item<Self::ItemKind>) -> Self { + AstNodeWrapper::new(P(item), TraitItemTag) + } + fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy { + items.flatten().collect() + } } struct ImplItemTag; impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag> { type OutputTy = SmallVec<[P<ast::AssocItem>; 1]>; + type ItemKind = AssocItemKind; const KIND: AstFragmentKind = AstFragmentKind::ImplItems; fn to_annotatable(self) -> Annotatable { Annotatable::ImplItem(self.wrapped) @@ -1243,7 +1292,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag> fragment.make_impl_items() } fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy { - noop_flat_map_assoc_item(self.wrapped, visitor) + noop_flat_map_item(self.wrapped, visitor) } fn is_mac_call(&self) -> bool { matches!(self.wrapped.kind, AssocItemKind::MacCall(..)) @@ -1255,6 +1304,21 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag> _ => unreachable!(), } } + fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> { + match &self.wrapped.kind { + AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)), + _ => None, + } + } + fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind { + AssocItemKind::Delegation(deleg) + } + fn from_item(item: ast::Item<Self::ItemKind>) -> Self { + AstNodeWrapper::new(P(item), ImplItemTag) + } + fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy { + items.flatten().collect() + } } impl InvocationCollectorNode for P<ast::ForeignItem> { @@ -1266,7 +1330,7 @@ impl InvocationCollectorNode for P<ast::ForeignItem> { fragment.make_foreign_items() } fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy { - noop_flat_map_foreign_item(self, visitor) + noop_flat_map_item(self, visitor) } fn is_mac_call(&self) -> bool { matches!(self.kind, ForeignItemKind::MacCall(..)) @@ -1416,6 +1480,24 @@ impl InvocationCollectorNode for ast::Stmt { }; (mac, attrs, if add_semicolon { AddSemicolon::Yes } else { AddSemicolon::No }) } + fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> { + match &self.kind { + StmtKind::Item(item) => match &item.kind { + ItemKind::DelegationMac(deleg) => Some((deleg, item)), + _ => None, + }, + _ => None, + } + } + fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind { + ItemKind::Delegation(deleg) + } + fn from_item(item: ast::Item<Self::ItemKind>) -> Self { + ast::Stmt { id: ast::DUMMY_NODE_ID, span: item.span, kind: StmtKind::Item(P(item)) } + } + fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy { + items.flatten().collect() + } fn post_flat_map_node_collect_bang(stmts: &mut Self::OutputTy, add_semicolon: AddSemicolon) { // If this is a macro invocation with a semicolon, then apply that // semicolon to the final statement produced by expansion. @@ -1717,11 +1799,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { } if attr.is_doc_comment() { - self.cx.sess.psess.buffer_lint_with_diagnostic( + self.cx.sess.psess.buffer_lint( UNUSED_DOC_COMMENTS, current_span, self.cx.current_expansion.lint_node_id, - "unused doc comment", BuiltinLintDiag::UnusedDocComment(attr.span), ); } else if rustc_attr::is_builtin_attr(attr) { @@ -1729,11 +1810,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { // `#[cfg]` and `#[cfg_attr]` are special - they are // eagerly evaluated. if attr_name != sym::cfg && attr_name != sym::cfg_attr { - self.cx.sess.psess.buffer_lint_with_diagnostic( + self.cx.sess.psess.buffer_lint( UNUSED_ATTRIBUTES, attr.span, self.cx.current_expansion.lint_node_id, - format!("unused attribute `{attr_name}`"), BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name: pprust::path_to_string(&call.path), @@ -1814,6 +1894,40 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { Node::post_flat_map_node_collect_bang(&mut res, add_semicolon); res } + None if let Some((deleg, item)) = node.delegation_list() => { + if deleg.suffixes.is_empty() { + // Report an error for now, to avoid keeping stem for resolution and + // stability checks. + self.cx.dcx().emit_err(EmptyDelegationList { span: item.span }); + } + + Node::flatten_outputs(deleg.suffixes.iter().map(|&(ident, rename)| { + let mut path = deleg.prefix.clone(); + path.segments.push(ast::PathSegment { + ident, + id: ast::DUMMY_NODE_ID, + args: None, + }); + + let mut item = Node::from_item(ast::Item { + attrs: item.attrs.clone(), + id: ast::DUMMY_NODE_ID, + span: ident.span, + vis: item.vis.clone(), + ident: rename.unwrap_or(ident), + kind: Node::delegation_item_kind(Box::new(ast::Delegation { + id: ast::DUMMY_NODE_ID, + qself: deleg.qself.clone(), + path, + rename, + body: deleg.body.clone(), + })), + tokens: None, + }); + + assign_id!(self, item.node_id_mut(), || item.noop_flat_map(self)) + })) + } None => { match Node::wrap_flat_map_node_noop_flat_map(node, self, |mut node, this| { assign_id!(this, node.node_id_mut(), || node.noop_flat_map(this)) @@ -1862,6 +1976,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.collect_bang(mac, Node::KIND).make_ast::<Node>() }) } + None if node.delegation_list().is_some() => unreachable!(), None => { assign_id!(self, node.node_id_mut(), || node.noop_visit(self)) } diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index c9a3aeedd02..4222c9fe906 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -1,68 +1,37 @@ +// tidy-alphabetical-start +#![allow(internal_features)] +#![allow(rustc::diagnostic_outside_of_impl)] #![doc(rust_logo)] -#![feature(rustdoc_internals)] #![feature(array_windows)] -#![cfg_attr(bootstrap, feature(associated_type_bounds))] #![feature(associated_type_defaults)] #![feature(if_let_guard)] #![feature(let_chains)] -#![feature(lint_reasons)] #![feature(macro_metavar_expr)] #![feature(map_try_insert)] #![feature(proc_macro_diagnostic)] #![feature(proc_macro_internals)] -#![feature(proc_macro_span)] +#![feature(rustdoc_internals)] #![feature(try_blocks)] #![feature(yeet_expr)] -#![allow(rustc::diagnostic_outside_of_impl)] -#![allow(internal_features)] - -#[macro_use] -extern crate rustc_macros; - -#[macro_use] -extern crate tracing; +// tidy-alphabetical-end extern crate proc_macro as pm; +mod build; +mod errors; +// FIXME(Nilstrieb) Translate macro_rules diagnostics +#[allow(rustc::untranslatable_diagnostic)] +mod mbe; mod placeholders; mod proc_macro_server; pub use mbe::macro_rules::compile_declarative_macro; -pub(crate) use rustc_span::hygiene; pub mod base; -pub mod build; -#[macro_use] pub mod config; -pub mod errors; pub mod expand; pub mod module; - // FIXME(Nilstrieb) Translate proc_macro diagnostics #[allow(rustc::untranslatable_diagnostic)] pub mod proc_macro; -// FIXME(Nilstrieb) Translate macro_rules diagnostics -#[allow(rustc::untranslatable_diagnostic)] -pub(crate) mod mbe; - -// HACK(Centril, #64197): These shouldn't really be here. -// Rather, they should be with their respective modules which are defined in other crates. -// However, since for now constructing a `ParseSess` sorta requires `config` from this crate, -// these tests will need to live here in the interim. - -#[cfg(test)] -mod tests; -#[cfg(test)] -mod parse { - mod tests; -} -#[cfg(test)] -mod tokenstream { - mod tests; -} -#[cfg(test)] -mod mut_visit { - mod tests; -} - rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_expand/src/mbe.rs b/compiler/rustc_expand/src/mbe.rs index ca4a1f327ad..08d4a039454 100644 --- a/compiler/rustc_expand/src/mbe.rs +++ b/compiler/rustc_expand/src/mbe.rs @@ -4,16 +4,18 @@ //! official terminology: "declarative macros". pub(crate) mod diagnostics; -pub(crate) mod macro_check; -pub(crate) mod macro_parser; pub(crate) mod macro_rules; -pub(crate) mod metavar_expr; -pub(crate) mod quoted; -pub(crate) mod transcribe; + +mod macro_check; +mod macro_parser; +mod metavar_expr; +mod quoted; +mod transcribe; use metavar_expr::MetaVarExpr; use rustc_ast::token::{Delimiter, NonterminalKind, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan}; +use rustc_macros::{Decodable, Encodable}; use rustc_span::symbol::Ident; use rustc_span::Span; @@ -66,12 +68,15 @@ pub(crate) enum KleeneOp { /// `MetaVarExpr` are "first-class" token trees. Useful for parsing macros. #[derive(Debug, PartialEq, Encodable, Decodable)] enum TokenTree { + /// A token. Unlike `tokenstream::TokenTree::Token` this lacks a `Spacing`. + /// See the comments about `Spacing` in the `transcribe` function. Token(Token), /// A delimited sequence, e.g. `($e:expr)` (RHS) or `{ $e }` (LHS). Delimited(DelimSpan, DelimSpacing, Delimited), /// A kleene-style repetition sequence, e.g. `$($e:expr)*` (RHS) or `$($e),*` (LHS). Sequence(DelimSpan, SequenceRepetition), - /// e.g., `$var`. + /// e.g., `$var`. The span covers the leading dollar and the ident. (The span within the ident + /// only covers the ident, e.g. `var`.) MetaVar(Span, Ident), /// e.g., `$var:expr`. Only appears on the LHS. MetaVarDecl(Span, Ident /* name to bind */, Option<NonterminalKind>), diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 15193298cca..442fd654b6a 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -8,11 +8,13 @@ use rustc_ast::token::{self, Token, TokenKind}; use rustc_ast::tokenstream::TokenStream; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, Diag, DiagCtxt, DiagMessage}; +use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::Ident; use rustc_span::{ErrorGuaranteed, Span}; use std::borrow::Cow; +use tracing::debug; use super::macro_rules::{parser_from_cx, NoopTracker}; @@ -26,7 +28,8 @@ pub(super) fn failed_to_match_macro<'cx>( ) -> Box<dyn MacResult + 'cx> { let psess = &cx.sess.psess; - // An error occurred, try the expansion again, tracking the expansion closely for better diagnostics. + // An error occurred, try the expansion again, tracking the expansion closely for better + // diagnostics. let mut tracker = CollectTrackerAndEmitter::new(cx, sp); let try_success_result = try_match_macro(psess, name, &arg, lhses, &mut tracker); @@ -52,7 +55,7 @@ pub(super) fn failed_to_match_macro<'cx>( let span = token.span.substitute_dummy(sp); - let mut err = cx.dcx().struct_span_err(span, parse_failure_msg(&token)); + let mut err = cx.dcx().struct_span_err(span, parse_failure_msg(&token, None)); err.span_label(span, label); if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) { err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro"); @@ -70,12 +73,6 @@ pub(super) fn failed_to_match_macro<'cx>( && (matches!(expected_token.kind, TokenKind::Interpolated(_)) || matches!(token.kind, TokenKind::Interpolated(_))) { - if let TokenKind::Interpolated(node) = &expected_token.kind { - err.span_label(node.1, ""); - } - if let TokenKind::Interpolated(node) = &token.kind { - err.span_label(node.1, ""); - } err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens"); err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information"); @@ -203,9 +200,17 @@ impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> { } /// Currently used by macro_rules! compilation to extract a little information from the `Failure` case. -pub struct FailureForwarder; +pub struct FailureForwarder<'matcher> { + expected_token: Option<&'matcher Token>, +} -impl<'matcher> Tracker<'matcher> for FailureForwarder { +impl<'matcher> FailureForwarder<'matcher> { + pub fn new() -> Self { + Self { expected_token: None } + } +} + +impl<'matcher> Tracker<'matcher> for FailureForwarder<'matcher> { type Failure = (Token, usize, &'static str); fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure { @@ -215,6 +220,14 @@ impl<'matcher> Tracker<'matcher> for FailureForwarder { fn description() -> &'static str { "failure-forwarder" } + + fn set_expected_token(&mut self, tok: &'matcher Token) { + self.expected_token = Some(tok); + } + + fn get_expected_token(&self) -> Option<&'matcher Token> { + self.expected_token + } } pub(super) fn emit_frag_parse_err( @@ -323,9 +336,19 @@ pub(super) fn annotate_doc_comment(dcx: &DiagCtxt, err: &mut Diag<'_>, sm: &Sour /// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For /// other tokens, this is "unexpected token...". -pub(super) fn parse_failure_msg(tok: &Token) -> Cow<'static, str> { - match tok.kind { - token::Eof => Cow::from("unexpected end of macro invocation"), - _ => Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))), +pub(super) fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> { + if let Some(expected_token) = expected_token { + Cow::from(format!( + "expected `{}`, found `{}`", + pprust::token_to_string(expected_token), + pprust::token_to_string(tok), + )) + } else { + match tok.kind { + token::Eof => Cow::from("unexpected end of macro invocation"), + _ => { + Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))) + } + } } } diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index dce8e0c36ed..72dbbde54b3 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -110,7 +110,8 @@ use crate::mbe::{KleeneToken, TokenTree}; use rustc_ast::token::{Delimiter, IdentIsRaw, Token, TokenKind}; use rustc_ast::{NodeId, DUMMY_NODE_ID}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{DiagMessage, MultiSpan}; +use rustc_errors::MultiSpan; +use rustc_lint_defs::BuiltinLintDiag; use rustc_session::lint::builtin::{META_VARIABLE_MISUSE, MISSING_FRAGMENT_SPECIFIER}; use rustc_session::parse::ParseSess; use rustc_span::symbol::kw; @@ -252,7 +253,7 @@ fn check_binders( // 1. The meta-variable is already bound in the current LHS: This is an error. let mut span = MultiSpan::from_span(span); span.push_span_label(prev_info.span, "previous declaration"); - buffer_lint(psess, span, node_id, "duplicate matcher binding"); + buffer_lint(psess, span, node_id, BuiltinLintDiag::DuplicateMatcherBinding); } else if get_binder_info(macros, binders, name).is_none() { // 2. The meta-variable is free: This is a binder. binders.insert(name, BinderInfo { span, ops: ops.into() }); @@ -271,7 +272,7 @@ fn check_binders( MISSING_FRAGMENT_SPECIFIER, span, node_id, - "missing fragment specifier", + BuiltinLintDiag::MissingFragmentSpecifier, ); } if !macros.is_empty() { @@ -595,7 +596,7 @@ fn check_ops_is_prefix( return; } } - buffer_lint(psess, span.into(), node_id, format!("unknown macro variable `{name}`")); + buffer_lint(psess, span.into(), node_id, BuiltinLintDiag::UnknownMacroVariable(name)); } /// Returns whether `binder_ops` is a prefix of `occurrence_ops`. @@ -628,8 +629,7 @@ fn ops_is_prefix( if i >= occurrence_ops.len() { let mut span = MultiSpan::from_span(span); span.push_span_label(binder.span, "expected repetition"); - let message = format!("variable '{name}' is still repeating at this depth"); - buffer_lint(psess, span, node_id, message); + buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableStillRepeating(name)); return; } let occurrence = &occurrence_ops[i]; @@ -637,21 +637,15 @@ fn ops_is_prefix( let mut span = MultiSpan::from_span(span); span.push_span_label(binder.span, "expected repetition"); span.push_span_label(occurrence.span, "conflicting repetition"); - let message = "meta-variable repeats with different Kleene operator"; - buffer_lint(psess, span, node_id, message); + buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableWrongOperator); return; } } } -fn buffer_lint( - psess: &ParseSess, - span: MultiSpan, - node_id: NodeId, - message: impl Into<DiagMessage>, -) { +fn buffer_lint(psess: &ParseSess, span: MultiSpan, node_id: NodeId, diag: BuiltinLintDiag) { // Macros loaded from other crates have dummy node ids. if node_id != DUMMY_NODE_ID { - psess.buffer_lint(META_VARIABLE_MISUSE, span, node_id, message); + psess.buffer_lint(META_VARIABLE_MISUSE, span, node_id, diag); } } diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 9fff00ffeae..2fbd09fd9ae 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -75,10 +75,9 @@ pub(crate) use ParseResult::*; use crate::mbe::{macro_rules::Tracker, KleeneOp, TokenTree}; -use rustc_ast::token::{self, DocComment, Nonterminal, NonterminalKind, Token}; +use rustc_ast::token::{self, DocComment, NonterminalKind, Token}; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; -use rustc_data_structures::sync::Lrc; use rustc_errors::ErrorGuaranteed; use rustc_lint_defs::pluralize; use rustc_parse::parser::{ParseNtResult, Parser}; @@ -266,7 +265,7 @@ struct MatcherPos { } // This type is used a lot. Make sure it doesn't unintentionally get bigger. -#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), target_pointer_width = "64"))] +#[cfg(target_pointer_width = "64")] rustc_data_structures::static_assert_size!(MatcherPos, 16); impl MatcherPos { @@ -392,7 +391,7 @@ pub(super) fn count_metavar_decls(matcher: &[TokenTree]) -> usize { #[derive(Debug, Clone)] pub(crate) enum NamedMatch { MatchedSeq(Vec<NamedMatch>), - MatchedSingle(ParseNtResult<Lrc<(Nonterminal, Span)>>), + MatchedSingle(ParseNtResult), } /// Performs a token equality check, ignoring syntax context (that is, an unhygienic comparison) @@ -542,6 +541,8 @@ impl TtParser { // The separator matches the current token. Advance past it. mp.idx += 1; self.next_mps.push(mp); + } else { + track.set_expected_token(separator); } } &MatcherLoc::SequenceKleeneOpAfterSep { idx_first } => { @@ -633,6 +634,7 @@ impl TtParser { parser.approx_token_stream_pos(), track, ); + if let Some(res) = res { return res; } @@ -686,11 +688,7 @@ impl TtParser { } Ok(nt) => nt, }; - mp.push_match( - next_metavar, - seq_depth, - MatchedSingle(nt.map_nt(|nt| (Lrc::new((nt, span))))), - ); + mp.push_match(next_metavar, seq_depth, MatchedSingle(nt)); mp.idx += 1; } else { unreachable!() diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 7099f1b0d35..5d3ba5d3223 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -29,6 +29,7 @@ use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; use rustc_span::symbol::{kw, sym, Ident, MacroRulesNormalizedIdent}; use rustc_span::Span; +use tracing::{debug, instrument, trace, trace_span}; use std::borrow::Cow; use std::collections::hash_map::Entry; @@ -78,11 +79,10 @@ impl<'a> ParserAnyMacro<'a> { // but `m!()` is allowed in expression positions (cf. issue #34706). if kind == AstFragmentKind::Expr && parser.token == token::Semi { if is_local { - parser.psess.buffer_lint_with_diagnostic( + parser.psess.buffer_lint( SEMICOLON_IN_EXPRESSIONS_FROM_MACROS, parser.token.span, lint_node_id, - "trailing semicolon in macro used in expression position", BuiltinLintDiag::TrailingMacro(is_trailing_mac, macro_ident), ); } @@ -156,8 +156,8 @@ pub(super) trait Tracker<'matcher> { /// This is called before trying to match next MatcherLoc on the current token. fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {} - /// This is called after an arm has been parsed, either successfully or unsuccessfully. When this is called, - /// `before_match_loc` was called at least once (with a `MatcherLoc::Eof`). + /// This is called after an arm has been parsed, either successfully or unsuccessfully. When + /// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`). fn after_arm(&mut self, _result: &NamedParseResult<Self::Failure>) {} /// For tracing. @@ -166,9 +166,15 @@ pub(super) trait Tracker<'matcher> { fn recovery() -> Recovery { Recovery::Forbidden } + + fn set_expected_token(&mut self, _tok: &'matcher Token) {} + fn get_expected_token(&self) -> Option<&'matcher Token> { + None + } } -/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization. +/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to +/// monomorphization. pub(super) struct NoopTracker; impl<'matcher> Tracker<'matcher> for NoopTracker { @@ -217,7 +223,8 @@ fn expand_macro<'cx>( let arm_span = rhses[i].span(); // rhs has holes ( `$id` and `$(...)` that need filled) - let tts = match transcribe(cx, &named_matches, rhs, rhs_span, transparency) { + let id = cx.current_expansion.id; + let tts = match transcribe(psess, &named_matches, rhs, rhs_span, transparency, id) { Ok(tts) => tts, Err(err) => { let guar = err.emit(); @@ -445,16 +452,14 @@ pub fn compile_declarative_macro( // For this we need to reclone the macro body as the previous parser consumed it. let retry_parser = create_parser(); - let parse_result = tt_parser.parse_tt( - &mut Cow::Owned(retry_parser), - &argument_gram, - &mut diagnostics::FailureForwarder, - ); + let mut track = diagnostics::FailureForwarder::new(); + let parse_result = + tt_parser.parse_tt(&mut Cow::Owned(retry_parser), &argument_gram, &mut track); let Failure((token, _, msg)) = parse_result else { unreachable!("matcher returned something other than Failure after retry"); }; - let s = parse_failure_msg(&token); + let s = parse_failure_msg(&token, track.get_expected_token()); let sp = token.span.substitute_dummy(def.span); let mut err = sess.dcx().struct_span_err(sp, s); err.span_label(sp, msg); @@ -491,7 +496,7 @@ pub fn compile_declarative_macro( .pop() .unwrap(); // We don't handle errors here, the driver will abort - // after parsing/expansion. we can report every error in every macro this way. + // after parsing/expansion. We can report every error in every macro this way. check_emission(check_lhs_nt_follows(sess, def, &tt)); return tt; } @@ -527,7 +532,7 @@ pub fn compile_declarative_macro( check_emission(check_rhs(sess, rhs)); } - // don't abort iteration early, so that errors for multiple lhses can be reported + // Don't abort iteration early, so that errors for multiple lhses can be reported. for lhs in &lhses { check_emission(check_lhs_no_empty_seq(sess, slice::from_ref(lhs))); } @@ -1149,11 +1154,10 @@ fn check_matcher_core<'tt>( name, Some(NonterminalKind::PatParam { inferred: false }), )); - sess.psess.buffer_lint_with_diagnostic( + sess.psess.buffer_lint( RUST_2021_INCOMPATIBLE_OR_PATTERNS, span, ast::CRATE_NODE_ID, - "the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro", BuiltinLintDiag::OrPatternsBackCompat(span, suggestion), ); } @@ -1288,7 +1292,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow { // maintain IsInFollow::Yes } - NonterminalKind::Stmt | NonterminalKind::Expr => { + NonterminalKind::Stmt | NonterminalKind::Expr | NonterminalKind::Expr2021 => { const TOKENS: &[&str] = &["`=>`", "`,`", "`;`"]; match tok { TokenTree::Token(token) => match token.kind { diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs index 81e1de5b095..128e9f48ff5 100644 --- a/compiler/rustc_expand/src/mbe/metavar_expr.rs +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -3,6 +3,7 @@ use rustc_ast::tokenstream::{RefTokenTreeCursor, TokenStream, TokenTree}; use rustc_ast::{LitIntType, LitKind}; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, PResult}; +use rustc_macros::{Decodable, Encodable}; use rustc_session::parse::ParseSess; use rustc_span::symbol::Ident; use rustc_span::Span; @@ -22,7 +23,7 @@ pub(crate) enum MetaVarExpr { /// The length of the repetition at a particular depth, where 0 is the inner-most /// repetition. The `usize` is the depth. - Length(usize), + Len(usize), } impl MetaVarExpr { @@ -47,13 +48,13 @@ impl MetaVarExpr { MetaVarExpr::Ignore(parse_ident(&mut iter, psess, ident.span)?) } "index" => MetaVarExpr::Index(parse_depth(&mut iter, psess, ident.span)?), - "length" => MetaVarExpr::Length(parse_depth(&mut iter, psess, ident.span)?), + "len" => MetaVarExpr::Len(parse_depth(&mut iter, psess, ident.span)?), _ => { let err_msg = "unrecognized meta-variable expression"; let mut err = psess.dcx.struct_span_err(ident.span, err_msg); err.span_suggestion( ident.span, - "supported expressions are count, ignore, index and length", + "supported expressions are count, ignore, index and len", "", Applicability::MachineApplicable, ); @@ -67,7 +68,7 @@ impl MetaVarExpr { pub(crate) fn ident(&self) -> Option<Ident> { match *self { MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident), - MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None, + MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => None, } } } @@ -110,7 +111,7 @@ fn parse_count<'psess>( Ok(MetaVarExpr::Count(ident, depth)) } -/// Parses the depth used by index(depth) and length(depth). +/// Parses the depth used by index(depth) and len(depth). fn parse_depth<'psess>( iter: &mut RefTokenTreeCursor<'_>, psess: &'psess ParseSess, diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index 06c1612ddba..8ad7cb15c92 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -16,6 +16,10 @@ use rustc_span::Span; const VALID_FRAGMENT_NAMES_MSG: &str = "valid fragment specifiers are \ `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, \ `literal`, `path`, `meta`, `tt`, `item` and `vis`"; +const VALID_FRAGMENT_NAMES_MSG_2021: &str = "valid fragment specifiers are \ + `ident`, `block`, `stmt`, `expr`, `expr_2021`, `pat`, \ + `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, \ + `item` and `vis`"; /// Takes a `tokenstream::TokenStream` and returns a `Vec<self::TokenTree>`. Specifically, this /// takes a generic `TokenStream`, such as is used in the rest of the compiler, and returns a @@ -58,49 +62,79 @@ pub(super) fn parse( match tree { TokenTree::MetaVar(start_sp, ident) if parsing_patterns => { let span = match trees.next() { - Some(&tokenstream::TokenTree::Token(Token { kind: token::Colon, span }, _)) => { + Some(&tokenstream::TokenTree::Token( + Token { kind: token::Colon, span: colon_span }, + _, + )) => { match trees.next() { Some(tokenstream::TokenTree::Token(token, _)) => match token.ident() { Some((fragment, _)) => { let span = token.span.with_lo(start_sp.lo()); - + let edition = || { + // FIXME(#85708) - once we properly decode a foreign + // crate's `SyntaxContext::root`, then we can replace + // this with just `span.edition()`. A + // `SyntaxContext::root()` from the current crate will + // have the edition of the current crate, and a + // `SyntaxContext::root()` from a foreign crate will + // have the edition of that crate (which we manually + // retrieve via the `edition` parameter). + if !span.from_expansion() { + edition + } else { + span.edition() + } + }; let kind = - token::NonterminalKind::from_symbol(fragment.name, || { - // FIXME(#85708) - once we properly decode a foreign - // crate's `SyntaxContext::root`, then we can replace - // this with just `span.edition()`. A - // `SyntaxContext::root()` from the current crate will - // have the edition of the current crate, and a - // `SyntaxContext::root()` from a foreign crate will - // have the edition of that crate (which we manually - // retrieve via the `edition` parameter). - if !span.from_expansion() { - edition - } else { - span.edition() - } - }) - .unwrap_or_else( - || { + token::NonterminalKind::from_symbol(fragment.name, edition) + .unwrap_or_else(|| { + let help = match fragment.name { + sym::expr_2021 => { + format!( + "fragment specifier `expr_2021` \ + requires Rust 2021 or later\n\ + {VALID_FRAGMENT_NAMES_MSG}" + ) + } + _ if edition().at_least_rust_2021() + && features + .expr_fragment_specifier_2024 => + { + VALID_FRAGMENT_NAMES_MSG_2021.into() + } + _ => VALID_FRAGMENT_NAMES_MSG.into(), + }; sess.dcx().emit_err( errors::InvalidFragmentSpecifier { span, fragment, - help: VALID_FRAGMENT_NAMES_MSG.into(), + help, }, ); token::NonterminalKind::Ident - }, - ); + }); + if kind == token::NonterminalKind::Expr2021 + && !features.expr_fragment_specifier_2024 + { + rustc_session::parse::feature_err( + sess, + sym::expr_fragment_specifier_2024, + span, + "fragment specifier `expr_2021` is unstable", + ) + .emit(); + } result.push(TokenTree::MetaVarDecl(span, ident, Some(kind))); continue; } _ => token.span, }, - tree => tree.map_or(span, tokenstream::TokenTree::span), + Some(tree) => tree.span(), + None => colon_span, } } - tree => tree.map_or(start_sp, tokenstream::TokenTree::span), + Some(tree) => tree.span(), + None => start_sp, }; result.push(TokenTree::MetaVarDecl(span, ident, None)); @@ -147,7 +181,7 @@ fn parse_tree<'a>( // Depending on what `tree` is, we could be parsing different parts of a macro match tree { // `tree` is a `$` token. Look at the next token in `trees` - &tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _) => { + &tokenstream::TokenTree::Token(Token { kind: token::Dollar, span: dollar_span }, _) => { // FIXME: Handle `Invisible`-delimited groups in a more systematic way // during parsing. let mut next = outer_trees.next(); @@ -180,7 +214,7 @@ fn parse_tree<'a>( err.emit(); // Returns early the same read `$` to avoid spanning // unrelated diagnostics that could be performed afterwards - return TokenTree::token(token::Dollar, span); + return TokenTree::token(token::Dollar, dollar_span); } Ok(elem) => { maybe_emit_macro_metavar_expr_feature( @@ -222,7 +256,7 @@ fn parse_tree<'a>( // special metavariable that names the crate of the invocation. Some(tokenstream::TokenTree::Token(token, _)) if token.is_ident() => { let (ident, is_raw) = token.ident().unwrap(); - let span = ident.span.with_lo(span.lo()); + let span = ident.span.with_lo(dollar_span.lo()); if ident.name == kw::Crate && matches!(is_raw, IdentIsRaw::No) { TokenTree::token(token::Ident(kw::DollarCrate, is_raw), span) } else { @@ -231,16 +265,19 @@ fn parse_tree<'a>( } // `tree` is followed by another `$`. This is an escaped `$`. - Some(&tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _)) => { + Some(&tokenstream::TokenTree::Token( + Token { kind: token::Dollar, span: dollar_span2 }, + _, + )) => { if parsing_patterns { span_dollar_dollar_or_metavar_in_the_lhs_err( sess, - &Token { kind: token::Dollar, span }, + &Token { kind: token::Dollar, span: dollar_span2 }, ); } else { - maybe_emit_macro_metavar_expr_feature(features, sess, span); + maybe_emit_macro_metavar_expr_feature(features, sess, dollar_span2); } - TokenTree::token(token::Dollar, span) + TokenTree::token(token::Dollar, dollar_span2) } // `tree` is followed by some other token. This is an error. @@ -252,7 +289,7 @@ fn parse_tree<'a>( } // There are no more tokens. Just return the `$` we already have. - None => TokenTree::token(token::Dollar, span), + None => TokenTree::token(token::Dollar, dollar_span), } } @@ -357,7 +394,7 @@ fn parse_sep_and_kleene_op<'a>( // `$$` or a meta-variable is the lhs of a macro but shouldn't. // -// For example, `macro_rules! foo { ( ${length()} ) => {} }` +// For example, `macro_rules! foo { ( ${len()} ) => {} }` fn span_dollar_dollar_or_metavar_in_the_lhs_err(sess: &Session, token: &Token) { sess.dcx() .span_err(token.span, format!("unexpected token: {}", pprust::token_to_string(token))); diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index dad83984c8b..e1f50876b05 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -1,4 +1,3 @@ -use crate::base::ExtCtxt; use crate::errors::{ CountRepetitionMisplaced, MetaVarExprUnrecognizedVar, MetaVarsDifSeqMatchers, MustRepeatOnce, NoSyntaxVarsExprRepeat, VarStillRepeating, @@ -9,12 +8,13 @@ use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::token::{self, Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{pluralize, Diag, PResult}; +use rustc_errors::{pluralize, Diag, DiagCtxt, PResult}; use rustc_parse::parser::ParseNtResult; use rustc_span::hygiene::{LocalExpnId, Transparency}; use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent}; use rustc_span::{with_metavar_spans, Span, SyntaxContext}; +use rustc_session::parse::ParseSess; use smallvec::{smallvec, SmallVec}; use std::mem; @@ -39,26 +39,32 @@ impl MutVisitor for Marker { } /// An iterator over the token trees in a delimited token tree (`{ ... }`) or a sequence (`$(...)`). -enum Frame<'a> { - Delimited { - tts: &'a [mbe::TokenTree], - idx: usize, - delim: Delimiter, - span: DelimSpan, - spacing: DelimSpacing, - }, - Sequence { - tts: &'a [mbe::TokenTree], - idx: usize, - sep: Option<Token>, - kleene_op: KleeneOp, - }, +struct Frame<'a> { + tts: &'a [mbe::TokenTree], + idx: usize, + kind: FrameKind, +} + +enum FrameKind { + Delimited { delim: Delimiter, span: DelimSpan, spacing: DelimSpacing }, + Sequence { sep: Option<Token>, kleene_op: KleeneOp }, } impl<'a> Frame<'a> { - /// Construct a new frame around the delimited set of tokens. - fn new(src: &'a mbe::Delimited, span: DelimSpan, spacing: DelimSpacing) -> Frame<'a> { - Frame::Delimited { tts: &src.tts, idx: 0, delim: src.delim, span, spacing } + fn new_delimited(src: &'a mbe::Delimited, span: DelimSpan, spacing: DelimSpacing) -> Frame<'a> { + Frame { + tts: &src.tts, + idx: 0, + kind: FrameKind::Delimited { delim: src.delim, span, spacing }, + } + } + + fn new_sequence( + src: &'a mbe::SequenceRepetition, + sep: Option<Token>, + kleene_op: KleeneOp, + ) -> Frame<'a> { + Frame { tts: &src.tts, idx: 0, kind: FrameKind::Sequence { sep, kleene_op } } } } @@ -66,13 +72,9 @@ impl<'a> Iterator for Frame<'a> { type Item = &'a mbe::TokenTree; fn next(&mut self) -> Option<&'a mbe::TokenTree> { - match self { - Frame::Delimited { tts, idx, .. } | Frame::Sequence { tts, idx, .. } => { - let res = tts.get(*idx); - *idx += 1; - res - } - } + let res = self.tts.get(self.idx); + self.idx += 1; + res } } @@ -97,11 +99,12 @@ impl<'a> Iterator for Frame<'a> { /// /// Along the way, we do some additional error checking. pub(super) fn transcribe<'a>( - cx: &ExtCtxt<'a>, + psess: &'a ParseSess, interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, src: &mbe::Delimited, src_span: DelimSpan, transparency: Transparency, + expand_id: LocalExpnId, ) -> PResult<'a, TokenStream> { // Nothing for us to transcribe... if src.tts.is_empty() { @@ -111,13 +114,16 @@ pub(super) fn transcribe<'a>( // We descend into the RHS (`src`), expanding things as we go. This stack contains the things // we have yet to expand/are still expanding. We start the stack off with the whole RHS. The // choice of spacing values doesn't matter. - let mut stack: SmallVec<[Frame<'_>; 1]> = - smallvec![Frame::new(src, src_span, DelimSpacing::new(Spacing::Alone, Spacing::Alone))]; + let mut stack: SmallVec<[Frame<'_>; 1]> = smallvec![Frame::new_delimited( + src, + src_span, + DelimSpacing::new(Spacing::Alone, Spacing::Alone) + )]; // As we descend in the RHS, we will need to be able to match nested sequences of matchers. // `repeats` keeps track of where we are in matching at each level, with the last element being // the most deeply nested sequence. This is used as a stack. - let mut repeats = Vec::new(); + let mut repeats: Vec<(usize, usize)> = Vec::new(); // `result` contains resulting token stream from the TokenTree we just finished processing. At // the end, this will contain the full result of transcription, but at arbitrary points during @@ -132,8 +138,9 @@ pub(super) fn transcribe<'a>( // again, and we are done transcribing. let mut result: Vec<TokenTree> = Vec::new(); let mut result_stack = Vec::new(); - let mut marker = Marker(cx.current_expansion.id, transparency, Default::default()); + let mut marker = Marker(expand_id, transparency, Default::default()); + let dcx = &psess.dcx; loop { // Look at the last frame on the stack. // If it still has a TokenTree we have not looked at yet, use that tree. @@ -142,11 +149,12 @@ pub(super) fn transcribe<'a>( // Otherwise, if we have just reached the end of a sequence and we can keep repeating, // go back to the beginning of the sequence. - if let Frame::Sequence { idx, sep, .. } = stack.last_mut().unwrap() { + let frame = stack.last_mut().unwrap(); + if let FrameKind::Sequence { sep, .. } = &frame.kind { let (repeat_idx, repeat_len) = repeats.last_mut().unwrap(); *repeat_idx += 1; if repeat_idx < repeat_len { - *idx = 0; + frame.idx = 0; if let Some(sep) = sep { result.push(TokenTree::Token(sep.clone(), Spacing::Alone)); } @@ -157,16 +165,16 @@ pub(super) fn transcribe<'a>( // We are done with the top of the stack. Pop it. Depending on what it was, we do // different things. Note that the outermost item must be the delimited, wrapped RHS // that was passed in originally to `transcribe`. - match stack.pop().unwrap() { + match stack.pop().unwrap().kind { // Done with a sequence. Pop from repeats. - Frame::Sequence { .. } => { + FrameKind::Sequence { .. } => { repeats.pop(); } // We are done processing a Delimited. If this is the top-level delimited, we are // done. Otherwise, we unwind the result_stack to append what we have produced to // any previous results. - Frame::Delimited { delim, span, mut spacing, .. } => { + FrameKind::Delimited { delim, span, mut spacing, .. } => { // Hack to force-insert a space after `]` in certain case. // See discussion of the `hex-literal` crate in #114571. if delim == Delimiter::Bracket { @@ -192,12 +200,10 @@ pub(super) fn transcribe<'a>( // We are descending into a sequence. We first make sure that the matchers in the RHS // and the matches in `interp` have the same shape. Otherwise, either the caller or the // macro writer has made a mistake. - seq @ mbe::TokenTree::Sequence(_, delimited) => { + seq @ mbe::TokenTree::Sequence(_, seq_rep) => { match lockstep_iter_size(seq, interp, &repeats) { LockstepIterSize::Unconstrained => { - return Err(cx - .dcx() - .create_err(NoSyntaxVarsExprRepeat { span: seq.span() })); + return Err(dcx.create_err(NoSyntaxVarsExprRepeat { span: seq.span() })); } LockstepIterSize::Contradiction(msg) => { @@ -205,9 +211,9 @@ pub(super) fn transcribe<'a>( // happens when two meta-variables are used in the same repetition in a // sequence, but they come from different sequence matchers and repeat // different amounts. - return Err(cx - .dcx() - .create_err(MetaVarsDifSeqMatchers { span: seq.span(), msg })); + return Err( + dcx.create_err(MetaVarsDifSeqMatchers { span: seq.span(), msg }) + ); } LockstepIterSize::Constraint(len, _) => { @@ -221,9 +227,7 @@ pub(super) fn transcribe<'a>( // FIXME: this really ought to be caught at macro definition // time... It happens when the Kleene operator in the matcher and // the body for the same meta-variable do not match. - return Err(cx - .dcx() - .create_err(MustRepeatOnce { span: sp.entire() })); + return Err(dcx.create_err(MustRepeatOnce { span: sp.entire() })); } } else { // 0 is the initial counter (we have done 0 repetitions so far). `len` @@ -233,12 +237,11 @@ pub(super) fn transcribe<'a>( // The first time we encounter the sequence we push it to the stack. It // then gets reused (see the beginning of the loop) until we are done // repeating. - stack.push(Frame::Sequence { - idx: 0, - sep: seq.separator.clone(), - tts: &delimited.tts, - kleene_op: seq.kleene.op, - }); + stack.push(Frame::new_sequence( + seq_rep, + seq.separator.clone(), + seq.kleene.op, + )); } } } @@ -248,13 +251,38 @@ pub(super) fn transcribe<'a>( mbe::TokenTree::MetaVar(mut sp, mut original_ident) => { // Find the matched nonterminal from the macro invocation, and use it to replace // the meta-var. + // + // We use `Spacing::Alone` everywhere here, because that's the conservative choice + // and spacing of declarative macros is tricky. E.g. in this macro: + // ``` + // macro_rules! idents { + // ($($a:ident,)*) => { stringify!($($a)*) } + // } + // ``` + // `$a` has no whitespace after it and will be marked `JointHidden`. If you then + // call `idents!(x,y,z,)`, each of `x`, `y`, and `z` will be marked as `Joint`. So + // if you choose to use `$x`'s spacing or the identifier's spacing, you'll end up + // producing "xyz", which is bad because it effectively merges tokens. + // `Spacing::Alone` is the safer option. Fortunately, `space_between` will avoid + // some of the unnecessary whitespace. let ident = MacroRulesNormalizedIdent::new(original_ident); if let Some(cur_matched) = lookup_cur_matched(ident, interp, &repeats) { + // njn: explain the use of alone here let tt = match cur_matched { MatchedSingle(ParseNtResult::Tt(tt)) => { // `tt`s are emitted into the output stream directly as "raw tokens", // without wrapping them into groups. - maybe_use_metavar_location(cx, &stack, sp, tt, &mut marker) + maybe_use_metavar_location(psess, &stack, sp, tt, &mut marker) + } + MatchedSingle(ParseNtResult::Ident(ident, is_raw)) => { + marker.visit_span(&mut sp); + let kind = token::NtIdent(*ident, *is_raw); + TokenTree::token_alone(kind, sp) + } + MatchedSingle(ParseNtResult::Lifetime(ident)) => { + marker.visit_span(&mut sp); + let kind = token::NtLifetime(*ident); + TokenTree::token_alone(kind, sp) } MatchedSingle(ParseNtResult::Nt(nt)) => { // Other variables are emitted into the output stream as groups with @@ -265,7 +293,7 @@ pub(super) fn transcribe<'a>( } MatchedSeq(..) => { // We were unable to descend far enough. This is an error. - return Err(cx.dcx().create_err(VarStillRepeating { span: sp, ident })); + return Err(dcx.create_err(VarStillRepeating { span: sp, ident })); } }; result.push(tt) @@ -284,7 +312,7 @@ pub(super) fn transcribe<'a>( // Replace meta-variable expressions with the result of their expansion. mbe::TokenTree::MetaVarExpr(sp, expr) => { - transcribe_metavar_expr(cx, expr, interp, &mut marker, &repeats, &mut result, sp)?; + transcribe_metavar_expr(dcx, expr, interp, &mut marker, &repeats, &mut result, sp)?; } // If we are entering a new delimiter, we push its contents to the `stack` to be @@ -294,13 +322,7 @@ pub(super) fn transcribe<'a>( // the previous results (from outside the Delimited). mbe::TokenTree::Delimited(mut span, spacing, delimited) => { mut_visit::visit_delim_span(&mut span, &mut marker); - stack.push(Frame::Delimited { - tts: &delimited.tts, - delim: delimited.delim, - idx: 0, - span, - spacing: *spacing, - }); + stack.push(Frame::new_delimited(delimited, span, *spacing)); result_stack.push(mem::take(&mut result)); } @@ -350,7 +372,7 @@ pub(super) fn transcribe<'a>( /// combine with each other and not with tokens outside of the sequence. /// - The metavariable span comes from a different crate, then we prefer the more local span. fn maybe_use_metavar_location( - cx: &ExtCtxt<'_>, + psess: &ParseSess, stack: &[Frame<'_>], mut metavar_span: Span, orig_tt: &TokenTree, @@ -358,10 +380,13 @@ fn maybe_use_metavar_location( ) -> TokenTree { let undelimited_seq = matches!( stack.last(), - Some(Frame::Sequence { + Some(Frame { tts: [_], - sep: None, - kleene_op: KleeneOp::ZeroOrMore | KleeneOp::OneOrMore, + kind: FrameKind::Sequence { + sep: None, + kleene_op: KleeneOp::ZeroOrMore | KleeneOp::OneOrMore, + .. + }, .. }) ); @@ -385,7 +410,7 @@ fn maybe_use_metavar_location( && insert(mspans, dspan.entire(), metavar_span) }), }; - if no_collision || cx.source_map().is_imported(metavar_span) { + if no_collision || psess.source_map().is_imported(metavar_span) { return orig_tt.clone(); } @@ -546,7 +571,7 @@ fn lockstep_iter_size( /// * `[ $( ${count(foo, 1)} ),* ]` will return an error because `${count(foo, 1)}` is /// declared inside a single repetition and the index `1` implies two nested repetitions. fn count_repetitions<'a>( - cx: &ExtCtxt<'a>, + dcx: &'a DiagCtxt, depth_user: usize, mut matched: &NamedMatch, repeats: &[(usize, usize)], @@ -583,7 +608,7 @@ fn count_repetitions<'a>( .and_then(|el| el.checked_sub(repeats.len())) .unwrap_or_default(); if depth_user > depth_max { - return Err(out_of_bounds_err(cx, depth_max + 1, sp.entire(), "count")); + return Err(out_of_bounds_err(dcx, depth_max + 1, sp.entire(), "count")); } // `repeats` records all of the nested levels at which we are currently @@ -599,7 +624,7 @@ fn count_repetitions<'a>( } if let MatchedSingle(_) = matched { - return Err(cx.dcx().create_err(CountRepetitionMisplaced { span: sp.entire() })); + return Err(dcx.create_err(CountRepetitionMisplaced { span: sp.entire() })); } count(depth_user, depth_max, matched) @@ -607,7 +632,7 @@ fn count_repetitions<'a>( /// Returns a `NamedMatch` item declared on the LHS given an arbitrary [Ident] fn matched_from_ident<'ctx, 'interp, 'rslt>( - cx: &ExtCtxt<'ctx>, + dcx: &'ctx DiagCtxt, ident: Ident, interp: &'interp FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, ) -> PResult<'ctx, &'rslt NamedMatch> @@ -616,12 +641,12 @@ where { let span = ident.span; let key = MacroRulesNormalizedIdent::new(ident); - interp.get(&key).ok_or_else(|| cx.dcx().create_err(MetaVarExprUnrecognizedVar { span, key })) + interp.get(&key).ok_or_else(|| dcx.create_err(MetaVarExprUnrecognizedVar { span, key })) } /// Used by meta-variable expressions when an user input is out of the actual declared bounds. For /// example, index(999999) in an repetition of only three elements. -fn out_of_bounds_err<'a>(cx: &ExtCtxt<'a>, max: usize, span: Span, ty: &str) -> Diag<'a> { +fn out_of_bounds_err<'a>(dcx: &'a DiagCtxt, max: usize, span: Span, ty: &str) -> Diag<'a> { let msg = if max == 0 { format!( "meta-variable expression `{ty}` with depth parameter \ @@ -633,11 +658,11 @@ fn out_of_bounds_err<'a>(cx: &ExtCtxt<'a>, max: usize, span: Span, ty: &str) -> must be less than {max}" ) }; - cx.dcx().struct_span_err(span, msg) + dcx.struct_span_err(span, msg) } fn transcribe_metavar_expr<'a>( - cx: &ExtCtxt<'a>, + dcx: &'a DiagCtxt, expr: &MetaVarExpr, interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, marker: &mut Marker, @@ -652,8 +677,8 @@ fn transcribe_metavar_expr<'a>( }; match *expr { MetaVarExpr::Count(original_ident, depth) => { - let matched = matched_from_ident(cx, original_ident, interp)?; - let count = count_repetitions(cx, depth, matched, repeats, sp)?; + let matched = matched_from_ident(dcx, original_ident, interp)?; + let count = count_repetitions(dcx, depth, matched, repeats, sp)?; let tt = TokenTree::token_alone( TokenKind::lit(token::Integer, sym::integer(count), None), visited_span(), @@ -662,7 +687,7 @@ fn transcribe_metavar_expr<'a>( } MetaVarExpr::Ignore(original_ident) => { // Used to ensure that `original_ident` is present in the LHS - let _ = matched_from_ident(cx, original_ident, interp)?; + let _ = matched_from_ident(dcx, original_ident, interp)?; } MetaVarExpr::Index(depth) => match repeats.iter().nth_back(depth) { Some((index, _)) => { @@ -671,16 +696,16 @@ fn transcribe_metavar_expr<'a>( visited_span(), )); } - None => return Err(out_of_bounds_err(cx, repeats.len(), sp.entire(), "index")), + None => return Err(out_of_bounds_err(dcx, repeats.len(), sp.entire(), "index")), }, - MetaVarExpr::Length(depth) => match repeats.iter().nth_back(depth) { + MetaVarExpr::Len(depth) => match repeats.iter().nth_back(depth) { Some((_, length)) => { result.push(TokenTree::token_alone( TokenKind::lit(token::Integer, sym::integer(*length), None), visited_span(), )); } - None => return Err(out_of_bounds_err(cx, repeats.len(), sp.entire(), "length")), + None => return Err(out_of_bounds_err(dcx, repeats.len(), sp.entire(), "len")), }, } Ok(()) diff --git a/compiler/rustc_expand/src/mut_visit/tests.rs b/compiler/rustc_expand/src/mut_visit/tests.rs deleted file mode 100644 index 8974d45b4d8..00000000000 --- a/compiler/rustc_expand/src/mut_visit/tests.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::tests::{matches_codepattern, string_to_crate}; - -use rustc_ast as ast; -use rustc_ast::mut_visit::MutVisitor; -use rustc_ast_pretty::pprust; -use rustc_span::create_default_session_globals_then; -use rustc_span::symbol::Ident; - -// This version doesn't care about getting comments or doc-strings in. -fn print_crate_items(krate: &ast::Crate) -> String { - krate.items.iter().map(|i| pprust::item_to_string(i)).collect::<Vec<_>>().join(" ") -} - -// Change every identifier to "zz". -struct ToZzIdentMutVisitor; - -impl MutVisitor for ToZzIdentMutVisitor { - const VISIT_TOKENS: bool = true; - - fn visit_ident(&mut self, ident: &mut Ident) { - *ident = Ident::from_str("zz"); - } -} - -// Maybe add to `expand.rs`. -macro_rules! assert_pred { - ($pred:expr, $predname:expr, $a:expr , $b:expr) => {{ - let pred_val = $pred; - let a_val = $a; - let b_val = $b; - if !(pred_val(&a_val, &b_val)) { - panic!("expected args satisfying {}, got {} and {}", $predname, a_val, b_val); - } - }}; -} - -// Make sure idents get transformed everywhere. -#[test] -fn ident_transformation() { - create_default_session_globals_then(|| { - let mut zz_visitor = ToZzIdentMutVisitor; - let mut krate = - string_to_crate("#[a] mod b {fn c (d : e, f : g) {h!(i,j,k);l;m}}".to_string()); - zz_visitor.visit_crate(&mut krate); - assert_pred!( - matches_codepattern, - "matches_codepattern", - print_crate_items(&krate), - "#[zz]mod zz{fn zz(zz:zz,zz:zz){zz!(zz,zz,zz);zz;zz}}".to_string() - ); - }) -} - -// Make sure idents get transformed even inside macro defs. -#[test] -fn ident_transformation_in_defs() { - create_default_session_globals_then(|| { - let mut zz_visitor = ToZzIdentMutVisitor; - let mut krate = string_to_crate( - "macro_rules! a {(b $c:expr $(d $e:token)f+ => \ - (g $(d $d $e)+))} " - .to_string(), - ); - zz_visitor.visit_crate(&mut krate); - assert_pred!( - matches_codepattern, - "matches_codepattern", - print_crate_items(&krate), - "macro_rules! zz{(zz$zz:zz$(zz $zz:zz)zz+=>(zz$(zz$zz$zz)+))}".to_string() - ); - }) -} diff --git a/compiler/rustc_expand/src/parse/tests.rs b/compiler/rustc_expand/src/parse/tests.rs deleted file mode 100644 index 066afd7a41d..00000000000 --- a/compiler/rustc_expand/src/parse/tests.rs +++ /dev/null @@ -1,382 +0,0 @@ -use crate::tests::{ - matches_codepattern, psess, string_to_stream, with_error_checking_parse, - with_expected_parse_error, -}; - -use ast::token::IdentIsRaw; -use rustc_ast::ptr::P; -use rustc_ast::token::{self, Delimiter, Token}; -use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; -use rustc_ast::visit; -use rustc_ast::{self as ast, PatKind}; -use rustc_ast_pretty::pprust::item_to_string; -use rustc_errors::PResult; -use rustc_parse::new_parser_from_source_str; -use rustc_parse::parser::ForceCollect; -use rustc_session::parse::ParseSess; -use rustc_span::create_default_session_globals_then; -use rustc_span::symbol::{kw, sym, Symbol}; -use rustc_span::{BytePos, FileName, Pos, Span}; -use std::path::PathBuf; - -/// Parses an item. -/// -/// Returns `Ok(Some(item))` when successful, `Ok(None)` when no item was found, and `Err` -/// when a syntax error occurred. -fn parse_item_from_source_str( - name: FileName, - source: String, - psess: &ParseSess, -) -> PResult<'_, Option<P<ast::Item>>> { - new_parser_from_source_str(psess, name, source).parse_item(ForceCollect::No) -} - -// Produces a `rustc_span::span`. -fn sp(a: u32, b: u32) -> Span { - Span::with_root_ctxt(BytePos(a), BytePos(b)) -} - -/// Parses a string, return an expression. -fn string_to_expr(source_str: String) -> P<ast::Expr> { - with_error_checking_parse(source_str, &psess(), |p| p.parse_expr()) -} - -/// Parses a string, returns an item. -fn string_to_item(source_str: String) -> Option<P<ast::Item>> { - with_error_checking_parse(source_str, &psess(), |p| p.parse_item(ForceCollect::No)) -} - -#[test] -fn bad_path_expr_1() { - // This should trigger error: expected identifier, found keyword `return` - create_default_session_globals_then(|| { - with_expected_parse_error( - "::abc::def::return", - "expected identifier, found keyword `return`", - |p| p.parse_expr(), - ); - }) -} - -// Checks the token-tree-ization of macros. -#[test] -fn string_to_tts_macro() { - create_default_session_globals_then(|| { - let stream = string_to_stream("macro_rules! zip (($a)=>($a))".to_string()); - let tts = &stream.trees().collect::<Vec<_>>()[..]; - - match tts { - [ - TokenTree::Token( - Token { kind: token::Ident(name_macro_rules, IdentIsRaw::No), .. }, - _, - ), - TokenTree::Token(Token { kind: token::Not, .. }, _), - TokenTree::Token(Token { kind: token::Ident(name_zip, IdentIsRaw::No), .. }, _), - TokenTree::Delimited(.., macro_delim, macro_tts), - ] if name_macro_rules == &kw::MacroRules && name_zip.as_str() == "zip" => { - let tts = ¯o_tts.trees().collect::<Vec<_>>(); - match &tts[..] { - [ - TokenTree::Delimited(.., first_delim, first_tts), - TokenTree::Token(Token { kind: token::FatArrow, .. }, _), - TokenTree::Delimited(.., second_delim, second_tts), - ] if macro_delim == &Delimiter::Parenthesis => { - let tts = &first_tts.trees().collect::<Vec<_>>(); - match &tts[..] { - [ - TokenTree::Token(Token { kind: token::Dollar, .. }, _), - TokenTree::Token( - Token { kind: token::Ident(name, IdentIsRaw::No), .. }, - _, - ), - ] if first_delim == &Delimiter::Parenthesis && name.as_str() == "a" => { - } - _ => panic!("value 3: {:?} {:?}", first_delim, first_tts), - } - let tts = &second_tts.trees().collect::<Vec<_>>(); - match &tts[..] { - [ - TokenTree::Token(Token { kind: token::Dollar, .. }, _), - TokenTree::Token( - Token { kind: token::Ident(name, IdentIsRaw::No), .. }, - _, - ), - ] if second_delim == &Delimiter::Parenthesis - && name.as_str() == "a" => {} - _ => panic!("value 4: {:?} {:?}", second_delim, second_tts), - } - } - _ => panic!("value 2: {:?} {:?}", macro_delim, macro_tts), - } - } - _ => panic!("value: {:?}", tts), - } - }) -} - -#[test] -fn string_to_tts_1() { - create_default_session_globals_then(|| { - let tts = string_to_stream("fn a(b: i32) { b; }".to_string()); - - let expected = TokenStream::new(vec![ - TokenTree::token_alone(token::Ident(kw::Fn, IdentIsRaw::No), sp(0, 2)), - TokenTree::token_joint_hidden( - token::Ident(Symbol::intern("a"), IdentIsRaw::No), - sp(3, 4), - ), - TokenTree::Delimited( - DelimSpan::from_pair(sp(4, 5), sp(11, 12)), - // `JointHidden` because the `(` is followed immediately by - // `b`, `Alone` because the `)` is followed by whitespace. - DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), - Delimiter::Parenthesis, - TokenStream::new(vec![ - TokenTree::token_joint( - token::Ident(Symbol::intern("b"), IdentIsRaw::No), - sp(5, 6), - ), - TokenTree::token_alone(token::Colon, sp(6, 7)), - // `JointHidden` because the `i32` is immediately followed by the `)`. - TokenTree::token_joint_hidden( - token::Ident(sym::i32, IdentIsRaw::No), - sp(8, 11), - ), - ]) - .into(), - ), - TokenTree::Delimited( - DelimSpan::from_pair(sp(13, 14), sp(18, 19)), - // First `Alone` because the `{` is followed by whitespace, - // second `Alone` because the `}` is followed immediately by - // EOF. - DelimSpacing::new(Spacing::Alone, Spacing::Alone), - Delimiter::Brace, - TokenStream::new(vec![ - TokenTree::token_joint( - token::Ident(Symbol::intern("b"), IdentIsRaw::No), - sp(15, 16), - ), - // `Alone` because the `;` is followed by whitespace. - TokenTree::token_alone(token::Semi, sp(16, 17)), - ]) - .into(), - ), - ]); - - assert_eq!(tts, expected); - }) -} - -#[test] -fn parse_use() { - create_default_session_globals_then(|| { - let use_s = "use foo::bar::baz;"; - let vitem = string_to_item(use_s.to_string()).unwrap(); - let vitem_s = item_to_string(&vitem); - assert_eq!(&vitem_s[..], use_s); - - let use_s = "use foo::bar as baz;"; - let vitem = string_to_item(use_s.to_string()).unwrap(); - let vitem_s = item_to_string(&vitem); - assert_eq!(&vitem_s[..], use_s); - }) -} - -#[test] -fn parse_extern_crate() { - create_default_session_globals_then(|| { - let ex_s = "extern crate foo;"; - let vitem = string_to_item(ex_s.to_string()).unwrap(); - let vitem_s = item_to_string(&vitem); - assert_eq!(&vitem_s[..], ex_s); - - let ex_s = "extern crate foo as bar;"; - let vitem = string_to_item(ex_s.to_string()).unwrap(); - let vitem_s = item_to_string(&vitem); - assert_eq!(&vitem_s[..], ex_s); - }) -} - -fn get_spans_of_pat_idents(src: &str) -> Vec<Span> { - let item = string_to_item(src.to_string()).unwrap(); - - struct PatIdentVisitor { - spans: Vec<Span>, - } - impl<'a> visit::Visitor<'a> for PatIdentVisitor { - fn visit_pat(&mut self, p: &'a ast::Pat) { - match &p.kind { - PatKind::Ident(_, ident, _) => { - self.spans.push(ident.span); - } - _ => { - visit::walk_pat(self, p); - } - } - } - } - let mut v = PatIdentVisitor { spans: Vec::new() }; - visit::walk_item(&mut v, &item); - return v.spans; -} - -#[test] -fn span_of_self_arg_pat_idents_are_correct() { - create_default_session_globals_then(|| { - let srcs = [ - "impl z { fn a (&self, &myarg: i32) {} }", - "impl z { fn a (&mut self, &myarg: i32) {} }", - "impl z { fn a (&'a self, &myarg: i32) {} }", - "impl z { fn a (self, &myarg: i32) {} }", - "impl z { fn a (self: Foo, &myarg: i32) {} }", - ]; - - for src in srcs { - let spans = get_spans_of_pat_idents(src); - let (lo, hi) = (spans[0].lo(), spans[0].hi()); - assert!( - "self" == &src[lo.to_usize()..hi.to_usize()], - "\"{}\" != \"self\". src=\"{}\"", - &src[lo.to_usize()..hi.to_usize()], - src - ) - } - }) -} - -#[test] -fn parse_exprs() { - create_default_session_globals_then(|| { - // just make sure that they parse.... - string_to_expr("3 + 4".to_string()); - string_to_expr("a::z.froob(b,&(987+3))".to_string()); - }) -} - -#[test] -fn attrs_fix_bug() { - create_default_session_globals_then(|| { - string_to_item( - "pub fn mk_file_writer(path: &Path, flags: &[FileFlag]) - -> Result<Box<Writer>, String> { -#[cfg(windows)] -fn wb() -> c_int { - (O_WRONLY | libc::consts::os::extra::O_BINARY) as c_int -} - -#[cfg(unix)] -fn wb() -> c_int { O_WRONLY as c_int } - -let mut fflags: c_int = wb(); -}" - .to_string(), - ); - }) -} - -#[test] -fn crlf_doc_comments() { - create_default_session_globals_then(|| { - let psess = psess(); - - let name_1 = FileName::Custom("crlf_source_1".to_string()); - let source = "/// doc comment\r\nfn foo() {}".to_string(); - let item = parse_item_from_source_str(name_1, source, &psess).unwrap().unwrap(); - let doc = item.attrs.iter().filter_map(|at| at.doc_str()).next().unwrap(); - assert_eq!(doc.as_str(), " doc comment"); - - let name_2 = FileName::Custom("crlf_source_2".to_string()); - let source = "/// doc comment\r\n/// line 2\r\nfn foo() {}".to_string(); - let item = parse_item_from_source_str(name_2, source, &psess).unwrap().unwrap(); - let docs = item.attrs.iter().filter_map(|at| at.doc_str()).collect::<Vec<_>>(); - let b: &[_] = &[Symbol::intern(" doc comment"), Symbol::intern(" line 2")]; - assert_eq!(&docs[..], b); - - let name_3 = FileName::Custom("clrf_source_3".to_string()); - let source = "/** doc comment\r\n * with CRLF */\r\nfn foo() {}".to_string(); - let item = parse_item_from_source_str(name_3, source, &psess).unwrap().unwrap(); - let doc = item.attrs.iter().filter_map(|at| at.doc_str()).next().unwrap(); - assert_eq!(doc.as_str(), " doc comment\n * with CRLF "); - }); -} - -#[test] -fn ttdelim_span() { - fn parse_expr_from_source_str( - name: FileName, - source: String, - psess: &ParseSess, - ) -> PResult<'_, P<ast::Expr>> { - new_parser_from_source_str(psess, name, source).parse_expr() - } - - create_default_session_globals_then(|| { - let psess = psess(); - let expr = parse_expr_from_source_str( - PathBuf::from("foo").into(), - "foo!( fn main() { body } )".to_string(), - &psess, - ) - .unwrap(); - - let ast::ExprKind::MacCall(mac) = &expr.kind else { panic!("not a macro") }; - let span = mac.args.tokens.trees().last().unwrap().span(); - - match psess.source_map().span_to_snippet(span) { - Ok(s) => assert_eq!(&s[..], "{ body }"), - Err(_) => panic!("could not get snippet"), - } - }); -} - -// This tests that when parsing a string (rather than a file) we don't try -// and read in a file for a module declaration and just parse a stub. -// See `recurse_into_file_modules` in the parser. -#[test] -fn out_of_line_mod() { - create_default_session_globals_then(|| { - let item = parse_item_from_source_str( - PathBuf::from("foo").into(), - "mod foo { struct S; mod this_does_not_exist; }".to_owned(), - &psess(), - ) - .unwrap() - .unwrap(); - - let ast::ItemKind::Mod(_, mod_kind) = &item.kind else { panic!() }; - assert!(matches!(mod_kind, ast::ModKind::Loaded(items, ..) if items.len() == 2)); - }); -} - -#[test] -fn eqmodws() { - assert_eq!(matches_codepattern("", ""), true); - assert_eq!(matches_codepattern("", "a"), false); - assert_eq!(matches_codepattern("a", ""), false); - assert_eq!(matches_codepattern("a", "a"), true); - assert_eq!(matches_codepattern("a b", "a \n\t\r b"), true); - assert_eq!(matches_codepattern("a b ", "a \n\t\r b"), true); - assert_eq!(matches_codepattern("a b", "a \n\t\r b "), false); - assert_eq!(matches_codepattern("a b", "a b"), true); - assert_eq!(matches_codepattern("ab", "a b"), false); - assert_eq!(matches_codepattern("a b", "ab"), true); - assert_eq!(matches_codepattern(" a b", "ab"), true); -} - -#[test] -fn pattern_whitespace() { - assert_eq!(matches_codepattern("", "\x0C"), false); - assert_eq!(matches_codepattern("a b ", "a \u{0085}\n\t\r b"), true); - assert_eq!(matches_codepattern("a b", "a \u{0085}\n\t\r b "), false); -} - -#[test] -fn non_pattern_whitespace() { - // These have the property 'White_Space' but not 'Pattern_White_Space' - assert_eq!(matches_codepattern("a b", "a\u{2002}b"), false); - assert_eq!(matches_codepattern("a b", "a\u{2002}b"), false); - assert_eq!(matches_codepattern("\u{205F}a b", "ab"), false); - assert_eq!(matches_codepattern("a \u{3000}b", "ab"), false); -} diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 2c4187031ca..e21f041d69a 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -9,7 +9,7 @@ use rustc_span::DUMMY_SP; use smallvec::{smallvec, SmallVec}; use thin_vec::ThinVec; -pub fn placeholder( +pub(crate) fn placeholder( kind: AstFragmentKind, id: ast::NodeId, vis: Option<ast::Visibility>, @@ -174,7 +174,10 @@ pub fn placeholder( }]), AstFragmentKind::Variants => AstFragment::Variants(smallvec![ast::Variant { attrs: Default::default(), - data: ast::VariantData::Struct { fields: Default::default(), recovered: false }, + data: ast::VariantData::Struct { + fields: Default::default(), + recovered: ast::Recovered::No + }, disr_expr: None, id, ident, @@ -271,14 +274,14 @@ impl MutVisitor for PlaceholderExpander { fn flat_map_trait_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> { match item.kind { ast::AssocItemKind::MacCall(_) => self.remove(item.id).make_trait_items(), - _ => noop_flat_map_assoc_item(item, self), + _ => noop_flat_map_item(item, self), } } fn flat_map_impl_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> { match item.kind { ast::AssocItemKind::MacCall(_) => self.remove(item.id).make_impl_items(), - _ => noop_flat_map_assoc_item(item, self), + _ => noop_flat_map_item(item, self), } } @@ -288,7 +291,7 @@ impl MutVisitor for PlaceholderExpander { ) -> SmallVec<[P<ast::ForeignItem>; 1]> { match item.kind { ast::ForeignItemKind::MacCall(_) => self.remove(item.id).make_foreign_items(), - _ => noop_flat_map_foreign_item(item, self), + _ => noop_flat_map_item(item, self), } } diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 4b5c148cb55..530059e53c2 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -127,7 +127,7 @@ impl MultiItemModifier for DeriveProcMacro { Annotatable::Stmt(stmt) => token::NtStmt(stmt), _ => unreachable!(), }; - TokenStream::token_alone(token::Interpolated(Lrc::new((nt, span))), DUMMY_SP) + TokenStream::token_alone(token::Interpolated(Lrc::new(nt)), DUMMY_SP) } else { item.to_tokens() }; diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 5a66b0fbdef..ec7e4416b91 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -220,6 +220,12 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre Ident(sym, is_raw) => { trees.push(TokenTree::Ident(Ident { sym, is_raw: is_raw.into(), span })) } + NtIdent(ident, is_raw) => trees.push(TokenTree::Ident(Ident { + sym: ident.name, + is_raw: is_raw.into(), + span: ident.span, + })), + Lifetime(name) => { let ident = symbol::Ident::new(name, span).without_first_quote(); trees.extend([ @@ -227,6 +233,15 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre TokenTree::Ident(Ident { sym: ident.name, is_raw: false, span }), ]); } + NtLifetime(ident) => { + let stream = TokenStream::token_alone(token::Lifetime(ident.name), ident.span); + trees.push(TokenTree::Group(Group { + delimiter: pm::Delimiter::None, + stream: Some(stream), + span: DelimSpan::from_single(span), + })) + } + Literal(token::Lit { kind, symbol, suffix }) => { trees.push(TokenTree::Literal(self::Literal { kind: FromInternal::from_internal(kind), @@ -259,23 +274,15 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre })); } - Interpolated(ref nt) if let NtIdent(ident, is_raw) = &nt.0 => { - trees.push(TokenTree::Ident(Ident { - sym: ident.name, - is_raw: matches!(is_raw, IdentIsRaw::Yes), - span: ident.span, - })) - } - Interpolated(nt) => { - let stream = TokenStream::from_nonterminal_ast(&nt.0); + let stream = TokenStream::from_nonterminal_ast(&nt); // A hack used to pass AST fragments to attribute and derive // macros as a single nonterminal token instead of a token // stream. Such token needs to be "unwrapped" and not // represented as a delimited group. // FIXME: It needs to be removed, but there are some // compatibility issues (see #73345). - if crate::base::nt_pretty_printing_compatibility_hack(&nt.0, rustc.ecx.sess) { + if crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.ecx.sess) { trees.extend(Self::from_internal((stream, rustc))); } else { trees.push(TokenTree::Group(Group { @@ -302,10 +309,10 @@ impl ToInternal<SmallVec<[tokenstream::TokenTree; 2]>> use rustc_ast::token::*; // The code below is conservative, using `token_alone`/`Spacing::Alone` - // in most places. When the resulting code is pretty-printed by - // `print_tts` it ends up with spaces between most tokens, which is - // safe but ugly. It's hard in general to do better when working at the - // token level. + // in most places. It's hard in general to do better when working at + // the token level. When the resulting code is pretty-printed by + // `print_tts` the `space_between` function helps avoid a lot of + // unnecessary whitespace, so the results aren't too bad. let (tree, rustc) = self; match tree { TokenTree::Punct(Punct { ch, joint, span }) => { diff --git a/compiler/rustc_expand/src/tests.rs b/compiler/rustc_expand/src/tests.rs deleted file mode 100644 index 2b0b58eb1d9..00000000000 --- a/compiler/rustc_expand/src/tests.rs +++ /dev/null @@ -1,1055 +0,0 @@ -use rustc_ast as ast; -use rustc_ast::tokenstream::TokenStream; -use rustc_parse::{new_parser_from_source_str, parser::Parser, source_file_to_stream}; -use rustc_session::parse::ParseSess; -use rustc_span::create_default_session_globals_then; -use rustc_span::source_map::{FilePathMapping, SourceMap}; -use rustc_span::{BytePos, Span}; - -use rustc_data_structures::sync::Lrc; -use rustc_errors::emitter::HumanEmitter; -use rustc_errors::{DiagCtxt, MultiSpan, PResult}; -use termcolor::WriteColor; - -use std::io; -use std::io::prelude::*; -use std::iter::Peekable; -use std::path::{Path, PathBuf}; -use std::str; -use std::sync::{Arc, Mutex}; - -pub(crate) fn psess() -> ParseSess { - ParseSess::new(vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE]) -} - -/// Map string to parser (via tts). -fn string_to_parser(psess: &ParseSess, source_str: String) -> Parser<'_> { - new_parser_from_source_str(psess, PathBuf::from("bogofile").into(), source_str) -} - -fn create_test_handler() -> (DiagCtxt, Lrc<SourceMap>, Arc<Mutex<Vec<u8>>>) { - let output = Arc::new(Mutex::new(Vec::new())); - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let fallback_bundle = rustc_errors::fallback_fluent_bundle( - vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE], - false, - ); - let emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle) - .sm(Some(source_map.clone())) - .diagnostic_width(Some(140)); - let dcx = DiagCtxt::new(Box::new(emitter)); - (dcx, source_map, output) -} - -/// Returns the result of parsing the given string via the given callback. -/// -/// If there are any errors, this will panic. -pub(crate) fn with_error_checking_parse<'a, T, F>(s: String, psess: &'a ParseSess, f: F) -> T -where - F: FnOnce(&mut Parser<'a>) -> PResult<'a, T>, -{ - let mut p = string_to_parser(&psess, s); - let x = f(&mut p).unwrap(); - p.psess.dcx.abort_if_errors(); - x -} - -/// Verifies that parsing the given string using the given callback will -/// generate an error that contains the given text. -pub(crate) fn with_expected_parse_error<T, F>(source_str: &str, expected_output: &str, f: F) -where - F: for<'a> FnOnce(&mut Parser<'a>) -> PResult<'a, T>, -{ - let (handler, source_map, output) = create_test_handler(); - let psess = ParseSess::with_dcx(handler, source_map); - let mut p = string_to_parser(&psess, source_str.to_string()); - let result = f(&mut p); - assert!(result.is_ok()); - - let bytes = output.lock().unwrap(); - let actual_output = str::from_utf8(&bytes).unwrap(); - println!("expected output:\n------\n{}------", expected_output); - println!("actual output:\n------\n{}------", actual_output); - - assert!(actual_output.contains(expected_output)) -} - -/// Maps a string to tts, using a made-up filename. -pub(crate) fn string_to_stream(source_str: String) -> TokenStream { - let psess = psess(); - source_file_to_stream( - &psess, - psess.source_map().new_source_file(PathBuf::from("bogofile").into(), source_str), - None, - ) -} - -/// Parses a string, returns a crate. -pub(crate) fn string_to_crate(source_str: String) -> ast::Crate { - let psess = psess(); - with_error_checking_parse(source_str, &psess, |p| p.parse_crate_mod()) -} - -/// Does the given string match the pattern? whitespace in the first string -/// may be deleted or replaced with other whitespace to match the pattern. -/// This function is relatively Unicode-ignorant; fortunately, the careful design -/// of UTF-8 mitigates this ignorance. It doesn't do NKF-normalization(?). -pub(crate) fn matches_codepattern(a: &str, b: &str) -> bool { - let mut a_iter = a.chars().peekable(); - let mut b_iter = b.chars().peekable(); - - loop { - let (a, b) = match (a_iter.peek(), b_iter.peek()) { - (None, None) => return true, - (None, _) => return false, - (Some(&a), None) => { - if rustc_lexer::is_whitespace(a) { - break; // Trailing whitespace check is out of loop for borrowck. - } else { - return false; - } - } - (Some(&a), Some(&b)) => (a, b), - }; - - if rustc_lexer::is_whitespace(a) && rustc_lexer::is_whitespace(b) { - // Skip whitespace for `a` and `b`. - scan_for_non_ws_or_end(&mut a_iter); - scan_for_non_ws_or_end(&mut b_iter); - } else if rustc_lexer::is_whitespace(a) { - // Skip whitespace for `a`. - scan_for_non_ws_or_end(&mut a_iter); - } else if a == b { - a_iter.next(); - b_iter.next(); - } else { - return false; - } - } - - // Check if a has *only* trailing whitespace. - a_iter.all(rustc_lexer::is_whitespace) -} - -/// Advances the given peekable `Iterator` until it reaches a non-whitespace character. -fn scan_for_non_ws_or_end<I: Iterator<Item = char>>(iter: &mut Peekable<I>) { - while iter.peek().copied().is_some_and(rustc_lexer::is_whitespace) { - iter.next(); - } -} - -/// Identifies a position in the text by the n'th occurrence of a string. -struct Position { - string: &'static str, - count: usize, -} - -struct SpanLabel { - start: Position, - end: Position, - label: &'static str, -} - -pub(crate) struct Shared<T: Write> { - pub data: Arc<Mutex<T>>, -} - -impl<T: Write> WriteColor for Shared<T> { - fn supports_color(&self) -> bool { - false - } - - fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> { - Ok(()) - } - - fn reset(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl<T: Write> Write for Shared<T> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - self.data.lock().unwrap().write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.data.lock().unwrap().flush() - } -} - -#[allow(rustc::untranslatable_diagnostic)] // no translation needed for tests -fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) { - create_default_session_globals_then(|| { - let (handler, source_map, output) = create_test_handler(); - source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned()); - - let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); - let mut msp = MultiSpan::from_span(primary_span); - for span_label in span_labels { - let span = make_span(&file_text, &span_label.start, &span_label.end); - msp.push_span_label(span, span_label.label); - println!("span: {:?} label: {:?}", span, span_label.label); - println!("text: {:?}", source_map.span_to_snippet(span)); - } - - handler.span_err(msp, "foo"); - - assert!( - expected_output.chars().next() == Some('\n'), - "expected output should begin with newline" - ); - let expected_output = &expected_output[1..]; - - let bytes = output.lock().unwrap(); - let actual_output = str::from_utf8(&bytes).unwrap(); - println!("expected output:\n------\n{}------", expected_output); - println!("actual output:\n------\n{}------", actual_output); - - assert!(expected_output == actual_output) - }) -} - -fn make_span(file_text: &str, start: &Position, end: &Position) -> Span { - let start = make_pos(file_text, start); - let end = make_pos(file_text, end) + end.string.len(); // just after matching thing ends - assert!(start <= end); - Span::with_root_ctxt(BytePos(start as u32), BytePos(end as u32)) -} - -fn make_pos(file_text: &str, pos: &Position) -> usize { - let mut remainder = file_text; - let mut offset = 0; - for _ in 0..pos.count { - if let Some(n) = remainder.find(&pos.string) { - offset += n; - remainder = &remainder[n + 1..]; - } else { - panic!("failed to find {} instances of {:?} in {:?}", pos.count, pos.string, file_text); - } - } - offset -} - -#[test] -fn ends_on_col0() { - test_harness( - r#" -fn foo() { -} -"#, - vec![SpanLabel { - start: Position { string: "{", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "test", - }], - r#" -error: foo - --> test.rs:2:10 - | -2 | fn foo() { - | __________^ -3 | | } - | |_^ test - -"#, - ); -} - -#[test] -fn ends_on_col2() { - test_harness( - r#" -fn foo() { - - - } -"#, - vec![SpanLabel { - start: Position { string: "{", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "test", - }], - r#" -error: foo - --> test.rs:2:10 - | -2 | fn foo() { - | __________^ -... | -5 | | } - | |___^ test - -"#, - ); -} -#[test] -fn non_nested() { - test_harness( - r#" -fn foo() { - X0 Y0 - X1 Y1 - X2 Y2 -} -"#, - vec![ - SpanLabel { - start: Position { string: "X0", count: 1 }, - end: Position { string: "X2", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "Y2", count: 1 }, - label: "`Y` is a good letter too", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | X0 Y0 - | ___^__- - | |___| - | || -4 | || X1 Y1 -5 | || X2 Y2 - | ||____^__- `Y` is a good letter too - | |_____| - | `X` is a good letter - -"#, - ); -} - -#[test] -fn nested() { - test_harness( - r#" -fn foo() { - X0 Y0 - Y1 X1 -} -"#, - vec![ - SpanLabel { - start: Position { string: "X0", count: 1 }, - end: Position { string: "X1", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "Y1", count: 1 }, - label: "`Y` is a good letter too", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | X0 Y0 - | ___^__- - | |___| - | || -4 | || Y1 X1 - | ||____-__^ `X` is a good letter - | |____| - | `Y` is a good letter too - -"#, - ); -} - -#[test] -fn different_overlap() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 - X1 Y1 Z1 - X2 Y2 Z2 - X3 Y3 Z3 -} -"#, - vec![ - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "X2", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Z1", count: 1 }, - end: Position { string: "X3", count: 1 }, - label: "`Y` is a good letter too", - }, - ], - r#" -error: foo - --> test.rs:3:6 - | -3 | X0 Y0 Z0 - | _______^ -4 | | X1 Y1 Z1 - | | _________- -5 | || X2 Y2 Z2 - | ||____^ `X` is a good letter -6 | | X3 Y3 Z3 - | |____- `Y` is a good letter too - -"#, - ); -} - -#[test] -fn triple_overlap() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 - X1 Y1 Z1 - X2 Y2 Z2 -} -"#, - vec![ - SpanLabel { - start: Position { string: "X0", count: 1 }, - end: Position { string: "X2", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "Y2", count: 1 }, - label: "`Y` is a good letter too", - }, - SpanLabel { - start: Position { string: "Z0", count: 1 }, - end: Position { string: "Z2", count: 1 }, - label: "`Z` label", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | X0 Y0 Z0 - | ___^__-__- - | |___|__| - | ||___| - | ||| -4 | ||| X1 Y1 Z1 -5 | ||| X2 Y2 Z2 - | |||____^__-__- `Z` label - | ||_____|__| - | |______| `Y` is a good letter too - | `X` is a good letter - -"#, - ); -} - -#[test] -fn triple_exact_overlap() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 - X1 Y1 Z1 - X2 Y2 Z2 -} -"#, - vec![ - SpanLabel { - start: Position { string: "X0", count: 1 }, - end: Position { string: "X2", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "X0", count: 1 }, - end: Position { string: "X2", count: 1 }, - label: "`Y` is a good letter too", - }, - SpanLabel { - start: Position { string: "X0", count: 1 }, - end: Position { string: "X2", count: 1 }, - label: "`Z` label", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | / X0 Y0 Z0 -4 | | X1 Y1 Z1 -5 | | X2 Y2 Z2 - | | ^ - | | | - | | `X` is a good letter - | |____`Y` is a good letter too - | `Z` label - -"#, - ); -} - -#[test] -fn minimum_depth() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 - X1 Y1 Z1 - X2 Y2 Z2 - X3 Y3 Z3 -} -"#, - vec![ - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "X1", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Y1", count: 1 }, - end: Position { string: "Z2", count: 1 }, - label: "`Y` is a good letter too", - }, - SpanLabel { - start: Position { string: "X2", count: 1 }, - end: Position { string: "Y3", count: 1 }, - label: "`Z`", - }, - ], - r#" -error: foo - --> test.rs:3:6 - | -3 | X0 Y0 Z0 - | _______^ -4 | | X1 Y1 Z1 - | | ____^_- - | ||____| - | | `X` is a good letter -5 | | X2 Y2 Z2 - | |___-______- `Y` is a good letter too - | ___| - | | -6 | | X3 Y3 Z3 - | |_______- `Z` - -"#, - ); -} - -#[test] -fn non_overlapping() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 - X1 Y1 Z1 - X2 Y2 Z2 - X3 Y3 Z3 -} -"#, - vec![ - SpanLabel { - start: Position { string: "X0", count: 1 }, - end: Position { string: "X1", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Y2", count: 1 }, - end: Position { string: "Z3", count: 1 }, - label: "`Y` is a good letter too", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | / X0 Y0 Z0 -4 | | X1 Y1 Z1 - | |____^ `X` is a good letter -5 | X2 Y2 Z2 - | ______- -6 | | X3 Y3 Z3 - | |__________- `Y` is a good letter too - -"#, - ); -} - -#[test] -fn overlapping_start_and_end() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 - X1 Y1 Z1 - X2 Y2 Z2 - X3 Y3 Z3 -} -"#, - vec![ - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "X1", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Z1", count: 1 }, - end: Position { string: "Z3", count: 1 }, - label: "`Y` is a good letter too", - }, - ], - r#" -error: foo - --> test.rs:3:6 - | -3 | X0 Y0 Z0 - | _______^ -4 | | X1 Y1 Z1 - | | ____^____- - | ||____| - | | `X` is a good letter -5 | | X2 Y2 Z2 -6 | | X3 Y3 Z3 - | |__________- `Y` is a good letter too - -"#, - ); -} - -#[test] -fn multiple_labels_primary_without_message() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![ - SpanLabel { - start: Position { string: "b", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "", - }, - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "`a` is a good letter", - }, - SpanLabel { - start: Position { string: "c", count: 1 }, - end: Position { string: "c", count: 1 }, - label: "", - }, - ], - r#" -error: foo - --> test.rs:3:7 - | -3 | a { b { c } d } - | ----^^^^-^^-- `a` is a good letter - -"#, - ); -} - -#[test] -fn multiple_labels_secondary_without_message() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![ - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "`a` is a good letter", - }, - SpanLabel { - start: Position { string: "b", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | a { b { c } d } - | ^^^^-------^^ `a` is a good letter - -"#, - ); -} - -#[test] -fn multiple_labels_primary_without_message_2() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![ - SpanLabel { - start: Position { string: "b", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "`b` is a good letter", - }, - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "", - }, - SpanLabel { - start: Position { string: "c", count: 1 }, - end: Position { string: "c", count: 1 }, - label: "", - }, - ], - r#" -error: foo - --> test.rs:3:7 - | -3 | a { b { c } d } - | ----^^^^-^^-- - | | - | `b` is a good letter - -"#, - ); -} - -#[test] -fn multiple_labels_secondary_without_message_2() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![ - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "", - }, - SpanLabel { - start: Position { string: "b", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "`b` is a good letter", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | a { b { c } d } - | ^^^^-------^^ - | | - | `b` is a good letter - -"#, - ); -} - -#[test] -fn multiple_labels_secondary_without_message_3() { - test_harness( - r#" -fn foo() { - a bc d -} -"#, - vec![ - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "b", count: 1 }, - label: "`a` is a good letter", - }, - SpanLabel { - start: Position { string: "c", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | a bc d - | ^^^^---- - | | - | `a` is a good letter - -"#, - ); -} - -#[test] -fn multiple_labels_without_message() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![ - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "", - }, - SpanLabel { - start: Position { string: "b", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | a { b { c } d } - | ^^^^-------^^ - -"#, - ); -} - -#[test] -fn multiple_labels_without_message_2() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![ - SpanLabel { - start: Position { string: "b", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "", - }, - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "", - }, - SpanLabel { - start: Position { string: "c", count: 1 }, - end: Position { string: "c", count: 1 }, - label: "", - }, - ], - r#" -error: foo - --> test.rs:3:7 - | -3 | a { b { c } d } - | ----^^^^-^^-- - -"#, - ); -} - -#[test] -fn multiple_labels_with_message() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![ - SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "`a` is a good letter", - }, - SpanLabel { - start: Position { string: "b", count: 1 }, - end: Position { string: "}", count: 1 }, - label: "`b` is a good letter", - }, - ], - r#" -error: foo - --> test.rs:3:3 - | -3 | a { b { c } d } - | ^^^^-------^^ - | | | - | | `b` is a good letter - | `a` is a good letter - -"#, - ); -} - -#[test] -fn single_label_with_message() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "`a` is a good letter", - }], - r#" -error: foo - --> test.rs:3:3 - | -3 | a { b { c } d } - | ^^^^^^^^^^^^^ `a` is a good letter - -"#, - ); -} - -#[test] -fn single_label_without_message() { - test_harness( - r#" -fn foo() { - a { b { c } d } -} -"#, - vec![SpanLabel { - start: Position { string: "a", count: 1 }, - end: Position { string: "d", count: 1 }, - label: "", - }], - r#" -error: foo - --> test.rs:3:3 - | -3 | a { b { c } d } - | ^^^^^^^^^^^^^ - -"#, - ); -} - -#[test] -fn long_snippet() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 - X1 Y1 Z1 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 - X2 Y2 Z2 - X3 Y3 Z3 -} -"#, - vec![ - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "X1", count: 1 }, - label: "`X` is a good letter", - }, - SpanLabel { - start: Position { string: "Z1", count: 1 }, - end: Position { string: "Z3", count: 1 }, - label: "`Y` is a good letter too", - }, - ], - r#" -error: foo - --> test.rs:3:6 - | -3 | X0 Y0 Z0 - | _______^ -4 | | X1 Y1 Z1 - | | ____^____- - | ||____| - | | `X` is a good letter -5 | | 1 -6 | | 2 -7 | | 3 -... | -15 | | X2 Y2 Z2 -16 | | X3 Y3 Z3 - | |__________- `Y` is a good letter too - -"#, - ); -} - -#[test] -fn long_snippet_multiple_spans() { - test_harness( - r#" -fn foo() { - X0 Y0 Z0 -1 -2 -3 - X1 Y1 Z1 -4 -5 -6 - X2 Y2 Z2 -7 -8 -9 -10 - X3 Y3 Z3 -} -"#, - vec![ - SpanLabel { - start: Position { string: "Y0", count: 1 }, - end: Position { string: "Y3", count: 1 }, - label: "`Y` is a good letter", - }, - SpanLabel { - start: Position { string: "Z1", count: 1 }, - end: Position { string: "Z2", count: 1 }, - label: "`Z` is a good letter too", - }, - ], - r#" -error: foo - --> test.rs:3:6 - | -3 | X0 Y0 Z0 - | _______^ -4 | | 1 -5 | | 2 -6 | | 3 -7 | | X1 Y1 Z1 - | | _________- -8 | || 4 -9 | || 5 -10 | || 6 -11 | || X2 Y2 Z2 - | ||__________- `Z` is a good letter too -... | -15 | | 10 -16 | | X3 Y3 Z3 - | |________^ `Y` is a good letter - -"#, - ); -} diff --git a/compiler/rustc_expand/src/tokenstream/tests.rs b/compiler/rustc_expand/src/tokenstream/tests.rs deleted file mode 100644 index 78795e86fd5..00000000000 --- a/compiler/rustc_expand/src/tokenstream/tests.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::tests::string_to_stream; - -use rustc_ast::token::{self, IdentIsRaw}; -use rustc_ast::tokenstream::{TokenStream, TokenTree}; -use rustc_span::create_default_session_globals_then; -use rustc_span::{BytePos, Span, Symbol}; - -fn string_to_ts(string: &str) -> TokenStream { - string_to_stream(string.to_owned()) -} - -fn sp(a: u32, b: u32) -> Span { - Span::with_root_ctxt(BytePos(a), BytePos(b)) -} - -#[test] -fn test_concat() { - create_default_session_globals_then(|| { - let test_res = string_to_ts("foo::bar::baz"); - let test_fst = string_to_ts("foo::bar"); - let test_snd = string_to_ts("::baz"); - let mut eq_res = TokenStream::default(); - eq_res.push_stream(test_fst); - eq_res.push_stream(test_snd); - assert_eq!(test_res.trees().count(), 5); - assert_eq!(eq_res.trees().count(), 5); - assert_eq!(test_res.eq_unspanned(&eq_res), true); - }) -} - -#[test] -fn test_to_from_bijection() { - create_default_session_globals_then(|| { - let test_start = string_to_ts("foo::bar(baz)"); - let test_end = test_start.trees().cloned().collect(); - assert_eq!(test_start, test_end) - }) -} - -#[test] -fn test_eq_0() { - create_default_session_globals_then(|| { - let test_res = string_to_ts("foo"); - let test_eqs = string_to_ts("foo"); - assert_eq!(test_res, test_eqs) - }) -} - -#[test] -fn test_eq_1() { - create_default_session_globals_then(|| { - let test_res = string_to_ts("::bar::baz"); - let test_eqs = string_to_ts("::bar::baz"); - assert_eq!(test_res, test_eqs) - }) -} - -#[test] -fn test_eq_3() { - create_default_session_globals_then(|| { - let test_res = string_to_ts(""); - let test_eqs = string_to_ts(""); - assert_eq!(test_res, test_eqs) - }) -} - -#[test] -fn test_diseq_0() { - create_default_session_globals_then(|| { - let test_res = string_to_ts("::bar::baz"); - let test_eqs = string_to_ts("bar::baz"); - assert_eq!(test_res == test_eqs, false) - }) -} - -#[test] -fn test_diseq_1() { - create_default_session_globals_then(|| { - let test_res = string_to_ts("(bar,baz)"); - let test_eqs = string_to_ts("bar,baz"); - assert_eq!(test_res == test_eqs, false) - }) -} - -#[test] -fn test_is_empty() { - create_default_session_globals_then(|| { - let test0 = TokenStream::default(); - let test1 = - TokenStream::token_alone(token::Ident(Symbol::intern("a"), IdentIsRaw::No), sp(0, 1)); - let test2 = string_to_ts("foo(bar::baz)"); - - assert_eq!(test0.is_empty(), true); - assert_eq!(test1.is_empty(), false); - assert_eq!(test2.is_empty(), false); - }) -} - -#[test] -fn test_dotdotdot() { - create_default_session_globals_then(|| { - let mut stream = TokenStream::default(); - stream.push_tree(TokenTree::token_joint(token::Dot, sp(0, 1))); - stream.push_tree(TokenTree::token_joint(token::Dot, sp(1, 2))); - stream.push_tree(TokenTree::token_alone(token::Dot, sp(2, 3))); - assert!(stream.eq_unspanned(&string_to_ts("..."))); - assert_eq!(stream.trees().count(), 1); - }) -} |
