Skip to content

Commit bfb8127

Browse files
authored
Merge pull request #18852 from ChayimFriedman2/proc-macro-panic
fix: Fix a bug that was caused by fixup reversing
2 parents 72b9427 + f7c4833 commit bfb8127

File tree

3 files changed

+82
-14
lines changed

3 files changed

+82
-14
lines changed

Diff for: crates/hir-expand/src/fixup.rs

+21-13
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ pub(crate) fn reverse_fixups(tt: &mut TopSubtree, undo_info: &SyntaxFixupUndoInf
388388
reverse_fixups_(tt, undo_info);
389389
}
390390

391+
#[derive(Debug)]
391392
enum TransformTtAction<'a> {
392393
Keep,
393394
ReplaceWith(tt::TokenTreesView<'a>),
@@ -399,51 +400,58 @@ impl TransformTtAction<'_> {
399400
}
400401
}
401402

403+
/// This function takes a token tree, and calls `callback` with each token tree in it.
404+
/// Then it does what the callback says: keeps the tt or replaces it with a (possibly empty)
405+
/// tts view.
402406
fn transform_tt<'a, 'b>(
403407
tt: &'a mut Vec<tt::TokenTree>,
404408
mut callback: impl FnMut(&mut tt::TokenTree) -> TransformTtAction<'b>,
405409
) {
410+
// We need to keep a stack of the currently open subtrees, because we need to update
411+
// them if we change the number of items in them.
406412
let mut subtrees_stack = Vec::new();
407413
let mut i = 0;
408414
while i < tt.len() {
409-
while let Some(&subtree_idx) = subtrees_stack.last() {
415+
'pop_finished_subtrees: while let Some(&subtree_idx) = subtrees_stack.last() {
410416
let tt::TokenTree::Subtree(subtree) = &tt[subtree_idx] else {
411417
unreachable!("non-subtree on subtrees stack");
412418
};
413-
if subtree_idx + 1 + subtree.usize_len() == i {
419+
if i >= subtree_idx + 1 + subtree.usize_len() {
414420
subtrees_stack.pop();
415421
} else {
416-
break;
422+
break 'pop_finished_subtrees;
417423
}
418424
}
419425

420426
let action = callback(&mut tt[i]);
421427
match action {
422-
TransformTtAction::Keep => {}
428+
TransformTtAction::Keep => {
429+
// This cannot be shared with the replaced case, because then we may push the same subtree
430+
// twice, and will update it twice which will lead to errors.
431+
if let tt::TokenTree::Subtree(_) = &tt[i] {
432+
subtrees_stack.push(i);
433+
}
434+
435+
i += 1;
436+
}
423437
TransformTtAction::ReplaceWith(replacement) => {
424438
let old_len = 1 + match &tt[i] {
425439
tt::TokenTree::Leaf(_) => 0,
426440
tt::TokenTree::Subtree(subtree) => subtree.usize_len(),
427441
};
428442
let len_diff = replacement.len() as i64 - old_len as i64;
429443
tt.splice(i..i + old_len, replacement.flat_tokens().iter().cloned());
430-
i = i.checked_add_signed(len_diff as isize).unwrap();
444+
// `+1` for the loop.
445+
i = i.checked_add_signed(len_diff as isize + 1).unwrap();
431446

432447
for &subtree_idx in &subtrees_stack {
433448
let tt::TokenTree::Subtree(subtree) = &mut tt[subtree_idx] else {
434449
unreachable!("non-subtree on subtrees stack");
435450
};
436-
subtree.len = (subtree.len as i64 + len_diff).try_into().unwrap();
451+
subtree.len = (i64::from(subtree.len) + len_diff).try_into().unwrap();
437452
}
438453
}
439454
}
440-
441-
// `tt[i]` might have been removed.
442-
if let Some(tt::TokenTree::Subtree(_)) = tt.get(i) {
443-
subtrees_stack.push(i);
444-
}
445-
446-
i += 1;
447455
}
448456
}
449457

Diff for: crates/ide/src/inlay_hints.rs

+14
Original file line numberDiff line numberDiff line change
@@ -856,4 +856,18 @@ fn main() {
856856
}"#,
857857
);
858858
}
859+
860+
#[test]
861+
fn regression_18840() {
862+
check(
863+
r#"
864+
//- proc_macros: issue_18840
865+
#[proc_macros::issue_18840]
866+
fn foo() {
867+
let
868+
loop {}
869+
}
870+
"#,
871+
);
872+
}
859873
}

Diff for: crates/test-fixture/src/lib.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ impl ChangeFixture {
376376
}
377377
}
378378

379-
fn default_test_proc_macros() -> [(String, ProcMacro); 6] {
379+
fn default_test_proc_macros() -> [(String, ProcMacro); 7] {
380380
[
381381
(
382382
r#"
@@ -468,6 +468,21 @@ pub fn issue_18089(_attr: TokenStream, _item: TokenStream) -> TokenStream {
468468
disabled: false,
469469
},
470470
),
471+
(
472+
r#"
473+
#[proc_macro_attribute]
474+
pub fn issue_18840(_attr: TokenStream, _item: TokenStream) -> TokenStream {
475+
loop {}
476+
}
477+
"#
478+
.into(),
479+
ProcMacro {
480+
name: Symbol::intern("issue_18840"),
481+
kind: ProcMacroKind::Attr,
482+
expander: sync::Arc::new(Issue18840ProcMacroExpander),
483+
disabled: false,
484+
},
485+
),
471486
]
472487
}
473488

@@ -645,6 +660,37 @@ impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
645660
}
646661
}
647662

663+
#[derive(Debug)]
664+
struct Issue18840ProcMacroExpander;
665+
impl ProcMacroExpander for Issue18840ProcMacroExpander {
666+
fn expand(
667+
&self,
668+
fn_: &TopSubtree,
669+
_: Option<&TopSubtree>,
670+
_: &Env,
671+
def_site: Span,
672+
_: Span,
673+
_: Span,
674+
_: Option<String>,
675+
) -> Result<TopSubtree, ProcMacroExpansionError> {
676+
// Input:
677+
// ```
678+
// #[issue_18840]
679+
// fn foo() { let loop {} }
680+
// ```
681+
682+
// The span that was created by the fixup infra.
683+
let fixed_up_span = fn_.token_trees().flat_tokens()[5].first_span();
684+
let mut result =
685+
quote! {fixed_up_span => ::core::compile_error! { "my cool compile_error!" } };
686+
// Make it so we won't remove the top subtree when reversing fixups.
687+
let top_subtree_delimiter_mut = result.top_subtree_delimiter_mut();
688+
top_subtree_delimiter_mut.open = def_site;
689+
top_subtree_delimiter_mut.close = def_site;
690+
Ok(result)
691+
}
692+
}
693+
648694
#[derive(Debug)]
649695
struct MirrorProcMacroExpander;
650696
impl ProcMacroExpander for MirrorProcMacroExpander {

0 commit comments

Comments
 (0)