Repository: rust-lang/rust-analyzer (2025-07-06T00:22:04Z)

Issue:
RA parser gets stuck on indexing: `0.3.2519-standalone`


**rust-analyzer version**: 0.3.2519-standalone

**rustc version**: `rustc 1.90.0-nightly (da58c0513 2025-07-03)`

**editor or extension**: VSCode

**repository link (if public, optional)**: Snippet below.

Just create a `main.rs` with the following contents:

**code snippet to reproduce**:
```rust
fn test_parser_error_bad_length() {
    assert!(matches!(
        drive_parser(&mut parser, data, false),
        Err(TarParserError::CorruptField {
            field: CorruptFieldContext::PaxKvLength,
            error: GeneralParseError::ParseInt(ParseIntError { .. })
        )}) // the first round bracket in this line causes the errors
    ));
}

```

RA error:

```
thread 'Worker4' panicked at crates/parser/src/parser.rs:56:9:
the parser seems stuck
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: parser::parser::Parser::err_recover
   3: parser::grammar::expressions::stmt
   4: parser::grammar::entry::top::macro_stmts
   5: syntax_bridge::token_tree_to_syntax_node
   6: <<DB as hir_expand::db::ExpandDatabase>::parse_macro_expansion::parse_macro_expansion_shim::Configuration_ as salsa::function::Configuration>::execute
   7: salsa::function::execute::<impl salsa::function::IngredientImpl<C>>::execute
   8: salsa::function::fetch::<impl salsa::function::IngredientImpl<C>>::fetch_cold_with_retry
   9: <DB as hir_expand::db::ExpandDatabase>::parse_macro_expansion::parse_macro_expansion_shim
  10: <DB as hir_expand::db::ExpandDatabase>::parse_macro_expansion
  11: hir_expand::db::parse_or_expand
  12: salsa::function::execute::<impl salsa::function::IngredientImpl<C>>::execute
  13: salsa::function::fetch::<impl salsa::function::IngredientImpl<C>>::fetch_cold_with_retry
  14: <DB as hir_expand::db::ExpandDatabase>::ast_id_map::ast_id_map_shim
  15: <hir_def::item_tree::file_item_tree_query::Configuration_ as salsa::function::Configuration>::execute
  16: salsa::function::execute::<impl salsa::function::IngredientImpl<C>>::execute
  17: salsa::function::fetch::<impl salsa::function::IngredientImpl<C>>::fetch_cold_with_retry
  18: salsa::function::fetch::<impl salsa::function::IngredientImpl<C>>::fetch
  19: std::thread::local::LocalKey<T>::with
  20: <DB as hir_def::db::DefDatabase>::file_item_tree
  21: hir_def::nameres::collector::DefCollector::collect_macro_expansion
  22: hir_def::nameres::collector::collect_defs
  23: <hir_def::nameres::block_def_map::Configuration_ as salsa::function::Configuration>::execute
  24: salsa::function::execute::<impl salsa::function::IngredientImpl<C>>::execute
  25: salsa::function::fetch::<impl salsa::function::IngredientImpl<C>>::fetch_cold_with_retry
  26: hir_def::nameres::block_def_map
  27: hir_def::expr_store::lower::ExprCollector::collect_block_
  28: hir_def::expr_store::lower::ExprCollector::maybe_collect_expr
  29: hir_def::expr_store::lower::lower_body
  30: hir_def::expr_store::body::Body::body_with_source_map_query
  31: salsa::function::execute::<impl salsa::function::IngredientImpl<C>>::execute
  32: salsa::function::fetch::<impl salsa::function::IngredientImpl<C>>::fetch_cold_with_retry
  33: salsa::function::fetch::<impl salsa::function::IngredientImpl<C>>::fetch
  34: <DB as hir_def::db::DefDatabase>::body_with_source_map
  35: hir::semantics::SemanticsImpl::get_unsafe_ops
  36: ide::syntax_highlighting::highlight
  37: salsa::cancelled::Cancelled::catch
  38: ide::Analysis::highlight
  39: rust_analyzer::handlers::request::handle_semantic_tokens_full_delta
  40: core::ops::function::FnOnce::call_once{{vtable.shim}}
  41: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
query stacktrace:
   0: parse_macro_expansion_shim(Id(2fc00)) -> (R2257, Durability::MEDIUM)
             at crates/hir-expand/src/db.rs:52
   1: ast_id_map_shim(Id(2fc00)) -> (R1, Durability::HIGH)
             at crates/hir-expand/src/db.rs:52
   2: file_item_tree_query(Id(2fc00)) -> (R1, Durability::HIGH)
             at crates/hir-def/src/item_tree.rs:89
   3: block_def_map(Id(6400)) -> (R2257, Durability::MEDIUM)
             at crates/hir-def/src/nameres.rs:421
   4: body_with_source_map_shim(Id(5400)) -> (R2257, Durability::MEDIUM)
             at crates/hir-def/src/db.rs:98

additional context:
   0: 
version: 0.3.2519-standalone
request: textDocument/semanticTokens/full/delta SemanticTokensDeltaParams {
    work_done_progress_params: WorkDoneProgressParams {
        work_done_token: None,
    },
    partial_result_params: PartialResultParams {
        partial_result_token: None,
    },
    text_document: TextDocumentIdentifier {
        uri: Url {
            scheme: "file",
            cannot_be_a_base: false,
            username: "",
            password: None,
            host: None,
            port: None,
            path: "/home/main/Downloads/rustanaltest/rustanaltest/src/main.rs",
            query: None,
            fragment: None,
        },
    },
    previous_result_id: "1",
}

[Error - 19:31:06] Request textDocument/semanticTokens/full/delta failed.
  Message: request handler panicked: the parser seems stuck
  Code: -32603 
```



Discussion:

Patch:
diff --git a/crates/hir-expand/src/builtin/quote.rs b/crates/hir-expand/src/builtin/quote.rs
index d5874f829ba5..70c38d4d7c75 100644
--- a/crates/hir-expand/src/builtin/quote.rs
+++ b/crates/hir-expand/src/builtin/quote.rs
@@ -129,7 +129,7 @@ macro_rules! quote {
         }
     }
 }
-pub(super) use quote;
+pub use quote;
 
 pub trait ToTokenTree {
     fn to_tokens(self, span: Span, builder: &mut TopSubtreeBuilder);
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 8ed0fc6729fc..c31ebc42a865 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -411,11 +411,10 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
             dir_spec.abandon(p);
             op.abandon(p);
             op_n.abandon(p);
-            p.err_and_bump("expected asm operand");
 
-            // improves error recovery and handles err_and_bump recovering from `{` which gets
-            // the parser stuck here
+            // improves error recovery
             if p.at(T!['{']) {
+                p.error("expected asm operand");
                 // test_err bad_asm_expr
                 // fn foo() {
                 //     builtin#asm(
@@ -423,6 +422,8 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
                 //     );
                 // }
                 expr(p);
+            } else {
+                p.err_and_bump("expected asm operand");
             }
 
             if p.at(T!['}']) {
diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs
index 36a363afe93a..ca02d9fdfde1 100644
--- a/crates/parser/src/parser.rs
+++ b/crates/parser/src/parser.rs
@@ -29,7 +29,7 @@ pub(crate) struct Parser<'t> {
     edition: Edition,
 }
 
-const PARSER_STEP_LIMIT: usize = 15_000_000;
+const PARSER_STEP_LIMIT: usize = if cfg!(debug_assertions) { 150_000 } else { 15_000_000 };
 
 impl<'t> Parser<'t> {
     pub(super) fn new(inp: &'t Input, edition: Edition) -> Parser<'t> {
@@ -254,7 +254,10 @@ impl<'t> Parser<'t> {
 
     /// Create an error node and consume the next token.
     pub(crate) fn err_and_bump(&mut self, message: &str) {
-        self.err_recover(message, TokenSet::EMPTY);
+        let m = self.start();
+        self.error(message);
+        self.bump_any();
+        m.complete(self, ERROR);
     }
 
     /// Create an error node and consume the next token unless it is in the recovery set.


Test patch:
diff --git a/crates/hir-def/src/macro_expansion_tests/mod.rs b/crates/hir-def/src/macro_expansion_tests/mod.rs
index 1c69b37f164f..5e95b061399a 100644
--- a/crates/hir-def/src/macro_expansion_tests/mod.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mod.rs
@@ -20,13 +20,14 @@ use base_db::RootQueryDb;
 use expect_test::Expect;
 use hir_expand::{
     AstId, InFile, MacroCallId, MacroCallKind, MacroKind,
+    builtin::quote::quote,
     db::ExpandDatabase,
     proc_macro::{ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind},
     span_map::SpanMapRef,
 };
-use intern::Symbol;
+use intern::{Symbol, sym};
 use itertools::Itertools;
-use span::{Edition, Span};
+use span::{Edition, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SyntaxContext};
 use stdx::{format_to, format_to_acc};
 use syntax::{
     AstNode, AstPtr,
@@ -34,7 +35,9 @@ use syntax::{
     SyntaxNode, T,
     ast::{self, edit::IndentLevel},
 };
+use syntax_bridge::token_tree_to_syntax_node;
 use test_fixture::WithFixture;
+use tt::{TextRange, TextSize};
 
 use crate::{
     AdtId, Lookup, ModuleDefId,
@@ -386,3 +389,38 @@ impl ProcMacroExpander for IdentityWhenValidProcMacroExpander {
         other.type_id() == TypeId::of::<Self>()
     }
 }
+
+#[test]
+fn regression_20171() {
+    // This really isn't the appropriate place to put this test, but it's convenient with access to `quote!`.
+    let span = Span {
+        range: TextRange::empty(TextSize::new(0)),
+        anchor: SpanAnchor {
+            file_id: span::EditionedFileId::current_edition(span::FileId::from_raw(0)),
+            ast_id: ROOT_ERASED_FILE_AST_ID,
+        },
+        ctx: SyntaxContext::root(Edition::CURRENT),
+    };
+    let close_brace = tt::Punct { char: '}', spacing: tt::Spacing::Alone, span };
+    let dotdot1 = tt::Punct { char: '.', spacing: tt::Spacing::Joint, span };
+    let dotdot2 = tt::Punct { char: '.', spacing: tt::Spacing::Alone, span };
+    let dollar_crate = sym::dollar_crate;
+    let tt = quote! {
+            span => {
+        if !((matches!(
+            drive_parser(&mut parser, data, false),
+            Err(TarParserError::CorruptField {
+                field: CorruptFieldContext::PaxKvLength,
+                error: GeneralParseError::ParseInt(ParseIntError { #dotdot1 #dotdot2 })
+            })
+        #close_brace  ))) {
+        #dollar_crate::panic::panic_2021!();
+    }}
+        };
+    token_tree_to_syntax_node(
+        &tt,
+        syntax_bridge::TopEntryPoint::MacroStmts,
+        &mut |_| Edition::CURRENT,
+        Edition::CURRENT,
+    );
+}