diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index f76bbd83819..28efd483c86 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -5,7 +5,8 @@ use rustc_ast_pretty::pprust; use rustc_expand::base::{self, *}; use rustc_expand::module::DirectoryOwnership; -use rustc_parse::{self, new_parser_from_file, parser::Parser}; +use rustc_parse::parser::{ForceCollect, Parser}; +use rustc_parse::{self, new_parser_from_file}; use rustc_session::lint::builtin::INCOMPLETE_INCLUDE; use rustc_span::symbol::Symbol; use rustc_span::{self, Pos, Span}; @@ -139,7 +140,7 @@ fn make_expr(mut self: Box>) -> Option> { fn make_items(mut self: Box>) -> Option; 1]>> { let mut ret = SmallVec::new(); while self.p.token != token::Eof { - match self.p.parse_item() { + match self.p.parse_item(ForceCollect::No) { Err(mut err) => { err.emit(); break; diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 16913dbb1ab..5d398935ce8 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -20,7 +20,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{struct_span_err, Applicability, PResult}; use rustc_feature::Features; -use rustc_parse::parser::{AttemptLocalParseRecovery, Parser}; +use rustc_parse::parser::{AttemptLocalParseRecovery, ForceCollect, Parser}; use rustc_parse::validate_attr; use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS; use rustc_session::lint::BuiltinLintDiagnostics; @@ -913,7 +913,7 @@ pub fn parse_ast_fragment<'a>( Ok(match kind { AstFragmentKind::Items => { let mut items = SmallVec::new(); - while let Some(item) = this.parse_item()? { + while let Some(item) = this.parse_item(ForceCollect::No)? { items.push(item); } AstFragment::Items(items) diff --git a/compiler/rustc_expand/src/parse/tests.rs b/compiler/rustc_expand/src/parse/tests.rs index 643305f153c..f4fcaf5c0a4 100644 --- a/compiler/rustc_expand/src/parse/tests.rs +++ b/compiler/rustc_expand/src/parse/tests.rs @@ -8,6 +8,7 @@ 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::source_map::FilePathMapping; use rustc_span::symbol::{kw, sym, Symbol}; @@ -29,7 +30,7 @@ fn parse_item_from_source_str( source: String, sess: &ParseSess, ) -> PResult<'_, Option>> { - new_parser_from_source_str(sess, name, source).parse_item() + new_parser_from_source_str(sess, name, source).parse_item(ForceCollect::No) } // Produces a `rustc_span::span`. @@ -44,7 +45,7 @@ fn string_to_expr(source_str: String) -> P { /// Parses a string, returns an item. fn string_to_item(source_str: String) -> Option> { - with_error_checking_parse(source_str, &sess(), |p| p.parse_item()) + with_error_checking_parse(source_str, &sess(), |p| p.parse_item(ForceCollect::No)) } #[should_panic] diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 02129e9b5e5..6779734cfc1 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -9,6 +9,7 @@ use rustc_errors::{struct_span_err, Applicability, ErrorReported}; use rustc_lexer::is_ident; use rustc_parse::nt_to_tokenstream; +use rustc_parse::parser::ForceCollect; use rustc_span::symbol::sym; use rustc_span::{Span, DUMMY_SP}; @@ -117,7 +118,7 @@ fn expand( let mut items = vec![]; loop { - match parser.parse_item() { + match parser.parse_item(ForceCollect::No) { Ok(None) => break, Ok(Some(item)) => { if is_stmt { diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 6db415ead41..47869f775fe 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -472,7 +472,11 @@ fn parse_prefix_range_expr(&mut self, attrs: Option) -> PResult<'a, P) -> PResult<'a, P> { let attrs = self.parse_or_use_outer_attributes(attrs)?; - let needs_tokens = super::attr::maybe_needs_tokens(&attrs); + // FIXME: Use super::attr::maybe_needs_tokens(&attrs) once we come up + // with a good way of passing `force_tokens` through from `parse_nonterminal`. + // Checking !attrs.is_empty() is correct, but will cause us to unnecessarily + // capture tokens in some circumstances. + let needs_tokens = !attrs.is_empty(); let do_parse = |this: &mut Parser<'a>| { let lo = this.token.span; // Note: when adding new unary operators, don't forget to adjust TokenKind::can_begin_expr() diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 810ae61307c..28067f0216c 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1,8 +1,8 @@ use super::diagnostics::{dummy_arg, ConsumeClosingDelim, Error}; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; -use super::{FollowedByType, Parser, PathStyle}; +use super::{FollowedByType, ForceCollect, Parser, PathStyle}; -use crate::maybe_whole; +use crate::{maybe_collect_tokens, maybe_whole}; use rustc_ast::ptr::P; use rustc_ast::token::{self, TokenKind}; @@ -69,7 +69,7 @@ fn parse_mod_items( unsafety: Unsafe, ) -> PResult<'a, Mod> { let mut items = vec![]; - while let Some(item) = self.parse_item()? { + while let Some(item) = self.parse_item(ForceCollect::No)? { items.push(item); self.maybe_consume_incorrect_semicolon(&items); } @@ -93,13 +93,17 @@ fn parse_mod_items( pub(super) type ItemInfo = (Ident, ItemKind); impl<'a> Parser<'a> { - pub fn parse_item(&mut self) -> PResult<'a, Option>> { - self.parse_item_(|_| true).map(|i| i.map(P)) + pub fn parse_item(&mut self, force_collect: ForceCollect) -> PResult<'a, Option>> { + self.parse_item_(|_| true, force_collect).map(|i| i.map(P)) } - fn parse_item_(&mut self, req_name: ReqName) -> PResult<'a, Option> { + fn parse_item_( + &mut self, + req_name: ReqName, + force_collect: ForceCollect, + ) -> PResult<'a, Option> { let attrs = self.parse_outer_attributes()?; - self.parse_item_common(attrs, true, false, req_name) + self.parse_item_common(attrs, true, false, req_name, force_collect) } pub(super) fn parse_item_common( @@ -108,6 +112,7 @@ pub(super) fn parse_item_common( mac_allowed: bool, attrs_allowed: bool, req_name: ReqName, + force_collect: ForceCollect, ) -> PResult<'a, Option> { maybe_whole!(self, NtItem, |item| { let mut item = item; @@ -116,16 +121,12 @@ pub(super) fn parse_item_common( Some(item.into_inner()) }); - let needs_tokens = super::attr::maybe_needs_tokens(&attrs); - let mut unclosed_delims = vec![]; - let parse_item = |this: &mut Self| { + let item = maybe_collect_tokens!(self, force_collect, &attrs, |this: &mut Self| { let item = this.parse_item_common_(attrs, mac_allowed, attrs_allowed, req_name); unclosed_delims.append(&mut this.unclosed_delims); item - }; - - let item = if needs_tokens { self.collect_tokens(parse_item) } else { parse_item(self) }?; + })?; self.unclosed_delims.append(&mut unclosed_delims); Ok(item) @@ -731,20 +732,22 @@ pub fn parse_trait_item(&mut self) -> PResult<'a, Option>>> /// Parses associated items. fn parse_assoc_item(&mut self, req_name: ReqName) -> PResult<'a, Option>>> { - Ok(self.parse_item_(req_name)?.map(|Item { attrs, id, span, vis, ident, kind, tokens }| { - let kind = match AssocItemKind::try_from(kind) { - Ok(kind) => kind, - Err(kind) => match kind { - ItemKind::Static(a, _, b) => { - self.struct_span_err(span, "associated `static` items are not allowed") - .emit(); - AssocItemKind::Const(Defaultness::Final, a, b) - } - _ => return self.error_bad_item_kind(span, &kind, "`trait`s or `impl`s"), - }, - }; - Some(P(Item { attrs, id, span, vis, ident, kind, tokens })) - })) + Ok(self.parse_item_(req_name, ForceCollect::No)?.map( + |Item { attrs, id, span, vis, ident, kind, tokens }| { + let kind = match AssocItemKind::try_from(kind) { + Ok(kind) => kind, + Err(kind) => match kind { + ItemKind::Static(a, _, b) => { + self.struct_span_err(span, "associated `static` items are not allowed") + .emit(); + AssocItemKind::Const(Defaultness::Final, a, b) + } + _ => return self.error_bad_item_kind(span, &kind, "`trait`s or `impl`s"), + }, + }; + Some(P(Item { attrs, id, span, vis, ident, kind, tokens })) + }, + )) } /// Parses a `type` alias with the following grammar: @@ -921,19 +924,21 @@ fn parse_item_foreign_mod( /// Parses a foreign item (one in an `extern { ... }` block). pub fn parse_foreign_item(&mut self) -> PResult<'a, Option>>> { - Ok(self.parse_item_(|_| true)?.map(|Item { attrs, id, span, vis, ident, kind, tokens }| { - let kind = match ForeignItemKind::try_from(kind) { - Ok(kind) => kind, - Err(kind) => match kind { - ItemKind::Const(_, a, b) => { - self.error_on_foreign_const(span, ident); - ForeignItemKind::Static(a, Mutability::Not, b) - } - _ => return self.error_bad_item_kind(span, &kind, "`extern` blocks"), - }, - }; - Some(P(Item { attrs, id, span, vis, ident, kind, tokens })) - })) + Ok(self.parse_item_(|_| true, ForceCollect::No)?.map( + |Item { attrs, id, span, vis, ident, kind, tokens }| { + let kind = match ForeignItemKind::try_from(kind) { + Ok(kind) => kind, + Err(kind) => match kind { + ItemKind::Const(_, a, b) => { + self.error_on_foreign_const(span, ident); + ForeignItemKind::Static(a, Mutability::Not, b) + } + _ => return self.error_bad_item_kind(span, &kind, "`extern` blocks"), + }, + }; + Some(P(Item { attrs, id, span, vis, ident, kind, tokens })) + }, + )) } fn error_bad_item_kind(&self, span: Span, kind: &ItemKind, ctx: &str) -> Option { @@ -1515,7 +1520,7 @@ fn recover_nested_adt_item(&mut self, keyword: Symbol) -> PResult<'a, bool> { { let kw_token = self.token.clone(); let kw_str = pprust::token_to_string(&kw_token); - let item = self.parse_item()?; + let item = self.parse_item(ForceCollect::No)?; self.struct_span_err( kw_token.span, diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 5d7ea5b8d57..c85b7a00732 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -54,6 +54,13 @@ enum BlockMode { Ignore, } +/// Whether or not we should force collection of tokens for an AST node, +/// regardless of whether or not it has attributes +pub enum ForceCollect { + Yes, + No, +} + /// Like `maybe_whole_expr`, but for things other than expressions. #[macro_export] macro_rules! maybe_whole { @@ -1413,3 +1420,16 @@ struct FrameData { assert!(stack.is_empty(), "Stack should be empty: final_buf={:?} stack={:?}", final_buf, stack); TokenStream::new(final_buf.inner) } + +#[macro_export] +macro_rules! maybe_collect_tokens { + ($self:ident, $force_collect:expr, $attrs:expr, $f:expr) => { + if matches!($force_collect, ForceCollect::Yes) + || $crate::parser::attr::maybe_needs_tokens($attrs) + { + $self.collect_tokens($f) + } else { + $f($self) + } + }; +} diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 97d0c0d8745..012b76d3d18 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -5,7 +5,7 @@ use rustc_span::symbol::{kw, Ident}; use crate::parser::pat::{GateOr, RecoverComma}; -use crate::parser::{FollowedByType, Parser, PathStyle}; +use crate::parser::{FollowedByType, ForceCollect, Parser, PathStyle}; impl<'a> Parser<'a> { /// Checks whether a non-terminal may begin with a particular token. @@ -98,7 +98,7 @@ pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, Nonter // in advance whether or not a proc-macro will be (transitively) invoked, // we always capture tokens for any `Nonterminal` which needs them. Ok(match kind { - NonterminalKind::Item => match self.collect_tokens(|this| this.parse_item())? { + NonterminalKind::Item => match self.parse_item(ForceCollect::Yes)? { Some(item) => token::NtItem(item), None => { return Err(self.struct_span_err(self.token.span, "expected an item keyword")); @@ -107,7 +107,7 @@ pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, Nonter NonterminalKind::Block => { token::NtBlock(self.collect_tokens(|this| this.parse_block())?) } - NonterminalKind::Stmt => match self.collect_tokens(|this| this.parse_stmt())? { + NonterminalKind::Stmt => match self.parse_stmt(ForceCollect::Yes)? { Some(s) => token::NtStmt(s), None => { return Err(self.struct_span_err(self.token.span, "expected a statement")); diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 641b29227db..da60ba8472b 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -3,8 +3,8 @@ use super::expr::LhsExpr; use super::pat::{GateOr, RecoverComma}; use super::path::PathStyle; -use super::{BlockMode, Parser, Restrictions, SemiColonMode}; -use crate::maybe_whole; +use super::{BlockMode, ForceCollect, Parser, Restrictions, SemiColonMode}; +use crate::{maybe_collect_tokens, maybe_whole}; use rustc_ast as ast; use rustc_ast::attr::HasAttrs; @@ -24,17 +24,21 @@ impl<'a> Parser<'a> { /// Parses a statement. This stops just before trailing semicolons on everything but items. /// e.g., a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed. // Public for rustfmt usage. - pub fn parse_stmt(&mut self) -> PResult<'a, Option> { - Ok(self.parse_stmt_without_recovery().unwrap_or_else(|mut e| { + pub fn parse_stmt(&mut self, force_collect: ForceCollect) -> PResult<'a, Option> { + Ok(self.parse_stmt_without_recovery(force_collect).unwrap_or_else(|mut e| { e.emit(); self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore); None })) } - fn parse_stmt_without_recovery(&mut self) -> PResult<'a, Option> { + /// If `force_capture` is true, forces collection of tokens regardless of whether + /// or not we have attributes + fn parse_stmt_without_recovery( + &mut self, + force_collect: ForceCollect, + ) -> PResult<'a, Option> { let mut attrs = self.parse_outer_attributes()?; - let has_attrs = !attrs.is_empty(); let lo = self.token.span; maybe_whole!(self, NtStmt, |stmt| { @@ -46,7 +50,7 @@ fn parse_stmt_without_recovery(&mut self) -> PResult<'a, Option> { Some(stmt) }); - let parse_stmt_inner = |this: &mut Self| { + maybe_collect_tokens!(self, force_collect, &attrs, |this: &mut Self| { let stmt = if this.eat_keyword(kw::Let) { this.parse_local_mk(lo, attrs.into())? } else if this.is_kw_followed_by_ident(kw::Mut) { @@ -69,7 +73,7 @@ fn parse_stmt_without_recovery(&mut self) -> PResult<'a, Option> { // Also, we avoid stealing syntax from `parse_item_`. this.parse_stmt_path_start(lo, attrs)? } else if let Some(item) = - this.parse_item_common(attrs.clone(), false, true, |_| true)? + this.parse_item_common(attrs.clone(), false, true, |_| true, force_collect)? { // FIXME: Bad copy of attrs this.mk_stmt(lo.to(item.span), StmtKind::Item(P(item))) @@ -86,14 +90,7 @@ fn parse_stmt_without_recovery(&mut self) -> PResult<'a, Option> { return Ok(None); }; Ok(Some(stmt)) - }; - - let stmt = if has_attrs { - self.collect_tokens(parse_stmt_inner)? - } else { - parse_stmt_inner(self)? - }; - Ok(stmt) + }) } fn parse_stmt_path_start(&mut self, lo: Span, attrs: Vec) -> PResult<'a, Stmt> { @@ -292,7 +289,7 @@ fn error_block_no_opening_brace(&mut self) -> PResult<'a, T> { // bar; // // which is valid in other languages, but not Rust. - match self.parse_stmt_without_recovery() { + match self.parse_stmt_without_recovery(ForceCollect::No) { // If the next token is an open brace (e.g., `if a b {`), the place- // inside-a-block suggestion would be more likely wrong than right. Ok(Some(_)) @@ -395,7 +392,7 @@ pub fn parse_full_stmt( // Skip looking for a trailing semicolon when we have an interpolated statement. maybe_whole!(self, NtStmt, |x| Some(x)); - let mut stmt = match self.parse_stmt_without_recovery()? { + let mut stmt = match self.parse_stmt_without_recovery(ForceCollect::No)? { Some(stmt) => stmt, None => return Ok(None), }; diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 3de97f2dd2e..c87ab833f7a 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -423,6 +423,7 @@ fn drop(&mut self) { use rustc_errors::emitter::{Emitter, EmitterWriter}; use rustc_errors::Handler; use rustc_parse::maybe_new_parser_from_source_str; + use rustc_parse::parser::ForceCollect; use rustc_session::parse::ParseSess; use rustc_span::source_map::FilePathMapping; @@ -459,7 +460,7 @@ fn drop(&mut self) { }; loop { - match parser.parse_item() { + match parser.parse_item(ForceCollect::No) { Ok(Some(item)) => { if !found_main { if let ast::ItemKind::Fn(..) = item.kind { diff --git a/src/test/ui/proc-macro/issue-81007-item-attrs.rs b/src/test/ui/proc-macro/issue-81007-item-attrs.rs new file mode 100644 index 00000000000..ea27d54ee41 --- /dev/null +++ b/src/test/ui/proc-macro/issue-81007-item-attrs.rs @@ -0,0 +1,31 @@ +// check-pass +// edition:2018 +// compile-flags: -Z span-debug +// aux-build:test-macros.rs + +#![feature(rustc_attrs)] + +#![no_std] // Don't load unnecessary hygiene information from std +extern crate std; + +#[macro_use] extern crate test_macros; + +macro_rules! capture_item { + ($item:item) => { + #[print_attr] + $item + } +} + +capture_item! { + /// A doc comment + struct Foo {} +} + +capture_item! { + #[rustc_dummy] + /// Another comment comment + struct Bar {} +} + +fn main() {} diff --git a/src/test/ui/proc-macro/issue-81007-item-attrs.stdout b/src/test/ui/proc-macro/issue-81007-item-attrs.stdout new file mode 100644 index 00000000000..6f880a12021 --- /dev/null +++ b/src/test/ui/proc-macro/issue-81007-item-attrs.stdout @@ -0,0 +1,99 @@ +PRINT-ATTR INPUT (DISPLAY): #[doc = r" A doc comment"] struct Foo { } +PRINT-ATTR INPUT (DEBUG): TokenStream [ + Punct { + ch: '#', + spacing: Alone, + span: $DIR/issue-81007-item-attrs.rs:21:5: 21:22 (#0), + }, + Group { + delimiter: Bracket, + stream: TokenStream [ + Ident { + ident: "doc", + span: $DIR/issue-81007-item-attrs.rs:21:5: 21:22 (#0), + }, + Punct { + ch: '=', + spacing: Alone, + span: $DIR/issue-81007-item-attrs.rs:21:5: 21:22 (#0), + }, + Literal { + kind: StrRaw(0), + symbol: " A doc comment", + suffix: None, + span: $DIR/issue-81007-item-attrs.rs:21:5: 21:22 (#0), + }, + ], + span: $DIR/issue-81007-item-attrs.rs:21:5: 21:22 (#0), + }, + Ident { + ident: "struct", + span: $DIR/issue-81007-item-attrs.rs:22:5: 22:11 (#0), + }, + Ident { + ident: "Foo", + span: $DIR/issue-81007-item-attrs.rs:22:12: 22:15 (#0), + }, + Group { + delimiter: Brace, + stream: TokenStream [], + span: $DIR/issue-81007-item-attrs.rs:22:16: 22:18 (#0), + }, +] +PRINT-ATTR INPUT (DISPLAY): #[rustc_dummy] #[doc = r" Another comment comment"] struct Bar { } +PRINT-ATTR INPUT (DEBUG): TokenStream [ + Punct { + ch: '#', + spacing: Alone, + span: $DIR/issue-81007-item-attrs.rs:26:5: 26:6 (#0), + }, + Group { + delimiter: Bracket, + stream: TokenStream [ + Ident { + ident: "rustc_dummy", + span: $DIR/issue-81007-item-attrs.rs:26:7: 26:18 (#0), + }, + ], + span: $DIR/issue-81007-item-attrs.rs:26:6: 26:19 (#0), + }, + Punct { + ch: '#', + spacing: Alone, + span: $DIR/issue-81007-item-attrs.rs:27:5: 27:32 (#0), + }, + Group { + delimiter: Bracket, + stream: TokenStream [ + Ident { + ident: "doc", + span: $DIR/issue-81007-item-attrs.rs:27:5: 27:32 (#0), + }, + Punct { + ch: '=', + spacing: Alone, + span: $DIR/issue-81007-item-attrs.rs:27:5: 27:32 (#0), + }, + Literal { + kind: StrRaw(0), + symbol: " Another comment comment", + suffix: None, + span: $DIR/issue-81007-item-attrs.rs:27:5: 27:32 (#0), + }, + ], + span: $DIR/issue-81007-item-attrs.rs:27:5: 27:32 (#0), + }, + Ident { + ident: "struct", + span: $DIR/issue-81007-item-attrs.rs:28:5: 28:11 (#0), + }, + Ident { + ident: "Bar", + span: $DIR/issue-81007-item-attrs.rs:28:12: 28:15 (#0), + }, + Group { + delimiter: Brace, + stream: TokenStream [], + span: $DIR/issue-81007-item-attrs.rs:28:16: 28:18 (#0), + }, +] diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs index f518da55cd7..3a754f49917 100644 --- a/src/tools/clippy/clippy_lints/src/doc.rs +++ b/src/tools/clippy/clippy_lints/src/doc.rs @@ -12,6 +12,7 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty; use rustc_parse::maybe_new_parser_from_source_str; +use rustc_parse::parser::ForceCollect; use rustc_session::parse::ParseSess; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::edition::Edition; @@ -483,7 +484,7 @@ fn has_needless_main(code: &str, edition: Edition) -> bool { let mut relevant_main_found = false; loop { - match parser.parse_item() { + match parser.parse_item(ForceCollect::No) { Ok(Some(item)) => match &item.kind { // Tests with one of these items are ignored ItemKind::Static(..)