diff --git a/crates/base-db/src/fixture.rs b/crates/base-db/src/fixture.rs index 60d1e488d8a..8a7e9dfadfe 100644 --- a/crates/base-db/src/fixture.rs +++ b/crates/base-db/src/fixture.rs @@ -6,7 +6,7 @@ use test_utils::{ extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER, }; -use tt::token_id::Subtree; +use tt::token_id::{Leaf, Subtree, TokenTree}; use vfs::{file_set::FileSet, VfsPath}; use crate::{ @@ -310,7 +310,7 @@ pub fn parse_with_proc_macros( } } -fn default_test_proc_macros() -> [(String, ProcMacro); 4] { +fn default_test_proc_macros() -> [(String, ProcMacro); 5] { [ ( r#" @@ -368,6 +368,20 @@ pub fn mirror(input: TokenStream) -> TokenStream { expander: Arc::new(MirrorProcMacroExpander), }, ), + ( + r#" +#[proc_macro] +pub fn shorten(input: TokenStream) -> TokenStream { + loop {} +} +"# + .into(), + ProcMacro { + name: "shorten".into(), + kind: crate::ProcMacroKind::FuncLike, + expander: Arc::new(ShortenProcMacroExpander), + }, + ), ] } @@ -508,3 +522,47 @@ fn traverse(input: &Subtree) -> Subtree { Ok(traverse(input)) } } + +// Replaces every literal with an empty string literal and every identifier with its first letter, +// but retains all tokens' span. Useful for testing we don't assume token hasn't been modified by +// macros even if it retains its span. +#[derive(Debug)] +struct ShortenProcMacroExpander; +impl ProcMacroExpander for ShortenProcMacroExpander { + fn expand( + &self, + input: &Subtree, + _: Option<&Subtree>, + _: &Env, + ) -> Result { + return Ok(traverse(input)); + + fn traverse(input: &Subtree) -> Subtree { + let token_trees = input + .token_trees + .iter() + .map(|it| match it { + TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(modify_leaf(leaf)), + TokenTree::Subtree(subtree) => tt::TokenTree::Subtree(traverse(subtree)), + }) + .collect(); + Subtree { delimiter: input.delimiter, token_trees } + } + + fn modify_leaf(leaf: &Leaf) -> Leaf { + let mut leaf = leaf.clone(); + match &mut leaf { + Leaf::Literal(it) => { + // XXX Currently replaces any literals with an empty string, but supporting + // "shortening" other literals would be nice. + it.text = "\"\"".into(); + } + Leaf::Punct(_) => {} + Leaf::Ident(it) => { + it.text = it.text.chars().take(1).collect(); + } + } + leaf + } + } +} diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index e34824e22ea..f606d79ad20 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -48,7 +48,9 @@ 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(sema, original_file, speculative_file, offset, fake_ident_token); + let expansion = + expand(sema, original_file, speculative_file, offset, fake_ident_token, relative_offset); + // add the relative offset back, so that left_biased finds the proper token let offset = expansion.offset + relative_offset; let token = expansion.original_file.token_at_offset(offset).left_biased()?; @@ -67,6 +69,7 @@ fn expand( mut speculative_file: SyntaxNode, mut offset: TextSize, mut fake_ident_token: SyntaxToken, + relative_offset: TextSize, ) -> ExpansionResult { let _p = profile::span("CompletionContext::expand"); let mut derive_ctx = None; @@ -97,7 +100,7 @@ fn expand( // successful expansions (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { + if new_offset + relative_offset > actual_expansion.text_range().end() { // offset outside of bounds from the original expansion, // stop here to prevent problems from happening break 'expansion; @@ -176,7 +179,7 @@ fn expand( // successful expansions (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { + if new_offset + relative_offset > actual_expansion.text_range().end() { // offset outside of bounds from the original expansion, // stop here to prevent problems from happening break 'expansion; diff --git a/crates/ide-completion/src/tests/proc_macros.rs b/crates/ide-completion/src/tests/proc_macros.rs index 9eae6f84954..fec149e56a9 100644 --- a/crates/ide-completion/src/tests/proc_macros.rs +++ b/crates/ide-completion/src/tests/proc_macros.rs @@ -131,3 +131,33 @@ fn main() {} "#]], ) } + +#[test] +fn issue_13836_str() { + check( + r#" +//- proc_macros: shorten +fn main() { + let s = proc_macros::shorten!("text.$0"); +} +"#, + expect![[r#""#]], + ) +} + +#[test] +fn issue_13836_ident() { + check( + r#" +//- proc_macros: shorten +struct S; +impl S { + fn foo(&self) {} +} +fn main() { + let s = proc_macros::shorten!(S.fo$0); +} +"#, + expect![[r#""#]], + ) +}