In completion's expand, consider recursion stop condition (when we're not inside a macro call anymore) *after* the recursive call instead of before it

This is because our detection is imperfect, and miss some cases such as an impersonating `test` macro, so we hope we'll expand successfully in this case.
This commit is contained in:
Chayim Refael Friedman 2025-01-26 19:06:29 +02:00
parent 2df4ecfc74
commit cf8ae2c694
2 changed files with 95 additions and 20 deletions

View File

@ -59,7 +59,7 @@ pub(super) fn expand_and_analyze(
// make the offset point to the start of the original token, as that is what the
// intermediate offsets calculated in expansion always points to
let offset = offset - relative_offset;
let expansion = expand(
let expansion = expand_maybe_stop(
sema,
original_file.clone(),
speculative_file.clone(),
@ -118,6 +118,47 @@ fn token_at_offset_ignore_whitespace(file: &SyntaxNode, offset: TextSize) -> Opt
/// that we check, we subtract `COMPLETION_MARKER.len()`. This may not be accurate because proc macros
/// can insert the text of the completion marker in other places while removing the span, but this is
/// the best we can do.
fn expand_maybe_stop(
sema: &Semantics<'_, RootDatabase>,
original_file: SyntaxNode,
speculative_file: SyntaxNode,
original_offset: TextSize,
fake_ident_token: SyntaxToken,
relative_offset: TextSize,
) -> Option<ExpansionResult> {
if let result @ Some(_) = expand(
sema,
original_file.clone(),
speculative_file.clone(),
original_offset,
fake_ident_token.clone(),
relative_offset,
) {
return result;
}
// This needs to come after the recursive call, because our "inside macro" detection is subtly wrong
// with regard to attribute macros named `test` that are not std's test. So hopefully we will expand
// them successfully above and be able to analyze.
// Left biased since there may already be an identifier token there, and we appended to it.
if !sema.might_be_inside_macro_call(&fake_ident_token)
&& token_at_offset_ignore_whitespace(&original_file, original_offset + relative_offset)
.is_some_and(|original_token| !sema.might_be_inside_macro_call(&original_token))
{
// Recursion base case.
Some(ExpansionResult {
original_file,
speculative_file,
original_offset,
speculative_offset: fake_ident_token.text_range().start(),
fake_ident_token,
derive_ctx: None,
})
} else {
None
}
}
fn expand(
sema: &Semantics<'_, RootDatabase>,
original_file: SyntaxNode,
@ -128,22 +169,6 @@ fn expand(
) -> Option<ExpansionResult> {
let _p = tracing::info_span!("CompletionContext::expand").entered();
// Left biased since there may already be an identifier token there, and we appended to it.
if !sema.might_be_inside_macro_call(&fake_ident_token)
&& token_at_offset_ignore_whitespace(&original_file, original_offset + relative_offset)
.is_some_and(|original_token| !sema.might_be_inside_macro_call(&original_token))
{
// Recursion base case.
return Some(ExpansionResult {
original_file,
speculative_file,
original_offset,
speculative_offset: fake_ident_token.text_range().start(),
fake_ident_token,
derive_ctx: None,
});
}
let parent_item =
|item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
let original_node = token_at_offset_ignore_whitespace(&original_file, original_offset)
@ -197,7 +222,7 @@ fn expand(
// stop here to prevent problems from happening
return None;
}
let result = expand(
let result = expand_maybe_stop(
sema,
actual_expansion.clone(),
fake_expansion.clone(),
@ -317,7 +342,7 @@ fn expand(
// stop here to prevent problems from happening
return None;
}
let result = expand(
let result = expand_maybe_stop(
sema,
actual_expansion.clone(),
fake_expansion.clone(),
@ -386,7 +411,7 @@ fn expand(
// stop here to prevent problems from happening
return None;
}
let result = expand(
let result = expand_maybe_stop(
sema,
actual_expansion.clone(),
fake_expansion.clone(),

View File

@ -1986,3 +1986,53 @@ fn foo() {
"#]],
);
}
#[test]
fn non_std_test_attr_macro() {
check(
r#"
//- proc_macros: identity
use proc_macros::identity as test;
#[test]
fn foo() {
$0
}
"#,
expect![[r#"
fn foo() fn()
md proc_macros
bt u32 u32
kw async
kw const
kw crate::
kw enum
kw extern
kw false
kw fn
kw for
kw if
kw if let
kw impl
kw let
kw loop
kw match
kw mod
kw return
kw self::
kw static
kw struct
kw trait
kw true
kw type
kw union
kw unsafe
kw use
kw while
kw while let
sn macro_rules
sn pd
sn ppd
"#]],
);
}