Skip to content

Commit 3a6ac65

Browse files
authored
Merge pull request rust-lang#19864 from ChayimFriedman2/is-in-macro
fix: Properly implement `might_be_inside_macro_call()` using semantic information instead of syntactical hacks
2 parents 083acb9 + a66a623 commit 3a6ac65

File tree

9 files changed

+125
-85
lines changed

9 files changed

+125
-85
lines changed

src/tools/rust-analyzer/crates/hir/src/semantics.rs

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -407,14 +407,14 @@ impl<'db> SemanticsImpl<'db> {
407407
res
408408
}
409409

410-
pub fn expand_macro_call(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
410+
pub fn expand_macro_call(&self, macro_call: &ast::MacroCall) -> Option<InFile<SyntaxNode>> {
411411
let sa = self.analyze_no_infer(macro_call.syntax())?;
412412

413413
let macro_call = InFile::new(sa.file_id, macro_call);
414414
let file_id = sa.expand(self.db, macro_call)?;
415415

416416
let node = self.parse_or_expand(file_id.into());
417-
Some(node)
417+
Some(InFile::new(file_id.into(), node))
418418
}
419419

420420
pub fn check_cfg_attr(&self, attr: &ast::TokenTree) -> Option<bool> {
@@ -468,10 +468,10 @@ impl<'db> SemanticsImpl<'db> {
468468
}
469469

470470
/// If `item` has an attribute macro attached to it, expands it.
471-
pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<ExpandResult<SyntaxNode>> {
471+
pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<ExpandResult<InFile<SyntaxNode>>> {
472472
let src = self.wrap_node_infile(item.clone());
473473
let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src.as_ref()))?;
474-
Some(self.expand(macro_call_id))
474+
Some(self.expand(macro_call_id).map(|it| InFile::new(macro_call_id.into(), it)))
475475
}
476476

477477
pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> {
@@ -846,49 +846,35 @@ impl<'db> SemanticsImpl<'db> {
846846
res
847847
}
848848

849-
// FIXME: This isn't quite right wrt to inner attributes
850-
/// Does a syntactic traversal to check whether this token might be inside a macro call
851-
pub fn might_be_inside_macro_call(&self, token: &SyntaxToken) -> bool {
852-
token.parent_ancestors().any(|ancestor| {
849+
pub fn is_inside_macro_call(&self, token: InFile<&SyntaxToken>) -> bool {
850+
// FIXME: Maybe `ancestors_with_macros()` is more suitable here? Currently
851+
// this is only used on real (not macro) files so this is not a problem.
852+
token.value.parent_ancestors().any(|ancestor| {
853853
if ast::MacroCall::can_cast(ancestor.kind()) {
854854
return true;
855855
}
856-
// Check if it is an item (only items can have macro attributes) that has a non-builtin attribute.
857-
let Some(item) = ast::Item::cast(ancestor) else { return false };
858-
item.attrs().any(|attr| {
859-
let Some(meta) = attr.meta() else { return false };
860-
let Some(path) = meta.path() else { return false };
861-
if let Some(attr_name) = path.as_single_name_ref() {
862-
let attr_name = attr_name.text();
863-
let attr_name = Symbol::intern(attr_name.as_str());
864-
if attr_name == sym::derive {
865-
return true;
866-
}
867-
// We ignore `#[test]` and friends in the def map, so we cannot expand them.
868-
// FIXME: We match by text. This is both hacky and incorrect (people can, and do, create
869-
// other macros named `test`). We cannot fix that unfortunately because we use this method
870-
// for speculative expansion in completion, which we cannot analyze. Fortunately, most macros
871-
// named `test` are test-like, meaning their expansion is not terribly important for IDE.
872-
if attr_name == sym::test
873-
|| attr_name == sym::bench
874-
|| attr_name == sym::test_case
875-
|| find_builtin_attr_idx(&attr_name).is_some()
876-
{
877-
return false;
878-
}
879-
}
880-
let mut segments = path.segments();
881-
let mut next_segment_text = || segments.next().and_then(|it| it.name_ref());
882-
// `#[core::prelude::rust_2024::test]` or `#[std::prelude::rust_2024::test]`.
883-
if next_segment_text().is_some_and(|it| matches!(&*it.text(), "core" | "std"))
884-
&& next_segment_text().is_some_and(|it| it.text() == "prelude")
885-
&& next_segment_text().is_some()
886-
&& next_segment_text()
887-
.is_some_and(|it| matches!(&*it.text(), "test" | "bench" | "test_case"))
888-
{
889-
return false;
856+
857+
let Some(item) = ast::Item::cast(ancestor) else {
858+
return false;
859+
};
860+
// Optimization to skip the semantic check.
861+
if item.attrs().all(|attr| {
862+
attr.simple_name()
863+
.is_some_and(|attr| find_builtin_attr_idx(&Symbol::intern(&attr)).is_some())
864+
}) {
865+
return false;
866+
}
867+
self.with_ctx(|ctx| {
868+
if ctx.item_to_macro_call(token.with_value(&item)).is_some() {
869+
return true;
890870
}
891-
true
871+
let adt = match item {
872+
ast::Item::Struct(it) => it.into(),
873+
ast::Item::Enum(it) => it.into(),
874+
ast::Item::Union(it) => it.into(),
875+
_ => return false,
876+
};
877+
ctx.has_derives(token.with_value(&adt))
892878
})
893879
})
894880
}

src/tools/rust-analyzer/crates/ide-completion/src/context.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::{iter, ops::ControlFlow};
88

99
use base_db::RootQueryDb as _;
1010
use hir::{
11-
DisplayTarget, HasAttrs, Local, ModuleDef, ModuleSource, Name, PathResolution, ScopeDef,
12-
Semantics, SemanticsScope, Symbol, Type, TypeInfo,
11+
DisplayTarget, HasAttrs, InFile, Local, ModuleDef, ModuleSource, Name, PathResolution,
12+
ScopeDef, Semantics, SemanticsScope, Symbol, Type, TypeInfo,
1313
};
1414
use ide_db::{
1515
FilePosition, FxHashMap, FxHashSet, RootDatabase, famous_defs::FamousDefs,
@@ -751,7 +751,7 @@ impl<'a> CompletionContext<'a> {
751751
original_offset,
752752
} = expand_and_analyze(
753753
&sema,
754-
original_file.syntax().clone(),
754+
InFile::new(editioned_file_id.into(), original_file.syntax().clone()),
755755
file_with_fake_ident.syntax().clone(),
756756
offset,
757757
&original_token,

src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Module responsible for analyzing the code surrounding the cursor for completion.
22
use std::iter;
33

4-
use hir::{ExpandResult, Semantics, Type, TypeInfo, Variant};
4+
use hir::{ExpandResult, InFile, Semantics, Type, TypeInfo, Variant};
55
use ide_db::{RootDatabase, active_parameter::ActiveParameter};
66
use itertools::Either;
77
use syntax::{
@@ -50,7 +50,7 @@ pub(super) struct AnalysisResult {
5050

5151
pub(super) fn expand_and_analyze(
5252
sema: &Semantics<'_, RootDatabase>,
53-
original_file: SyntaxNode,
53+
original_file: InFile<SyntaxNode>,
5454
speculative_file: SyntaxNode,
5555
offset: TextSize,
5656
original_token: &SyntaxToken,
@@ -72,7 +72,7 @@ pub(super) fn expand_and_analyze(
7272
relative_offset,
7373
)
7474
.unwrap_or(ExpansionResult {
75-
original_file,
75+
original_file: original_file.value,
7676
speculative_file,
7777
original_offset: offset,
7878
speculative_offset: fake_ident_token.text_range().start(),
@@ -125,7 +125,7 @@ fn token_at_offset_ignore_whitespace(file: &SyntaxNode, offset: TextSize) -> Opt
125125
/// the best we can do.
126126
fn expand_maybe_stop(
127127
sema: &Semantics<'_, RootDatabase>,
128-
original_file: SyntaxNode,
128+
original_file: InFile<SyntaxNode>,
129129
speculative_file: SyntaxNode,
130130
original_offset: TextSize,
131131
fake_ident_token: SyntaxToken,
@@ -142,17 +142,16 @@ fn expand_maybe_stop(
142142
return result;
143143
}
144144

145-
// This needs to come after the recursive call, because our "inside macro" detection is subtly wrong
146-
// with regard to attribute macros named `test` that are not std's test. So hopefully we will expand
147-
// them successfully above and be able to analyze.
148-
// Left biased since there may already be an identifier token there, and we appended to it.
149-
if !sema.might_be_inside_macro_call(&fake_ident_token)
150-
&& token_at_offset_ignore_whitespace(&original_file, original_offset + relative_offset)
151-
.is_some_and(|original_token| !sema.might_be_inside_macro_call(&original_token))
145+
// We can't check whether the fake expansion is inside macro call, because that requires semantic info.
146+
// But hopefully checking just the real one should be enough.
147+
if token_at_offset_ignore_whitespace(&original_file.value, original_offset + relative_offset)
148+
.is_some_and(|original_token| {
149+
!sema.is_inside_macro_call(original_file.with_value(&original_token))
150+
})
152151
{
153152
// Recursion base case.
154153
Some(ExpansionResult {
155-
original_file,
154+
original_file: original_file.value,
156155
speculative_file,
157156
original_offset,
158157
speculative_offset: fake_ident_token.text_range().start(),
@@ -166,7 +165,7 @@ fn expand_maybe_stop(
166165

167166
fn expand(
168167
sema: &Semantics<'_, RootDatabase>,
169-
original_file: SyntaxNode,
168+
original_file: InFile<SyntaxNode>,
170169
speculative_file: SyntaxNode,
171170
original_offset: TextSize,
172171
fake_ident_token: SyntaxToken,
@@ -176,7 +175,7 @@ fn expand(
176175

177176
let parent_item =
178177
|item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
179-
let original_node = token_at_offset_ignore_whitespace(&original_file, original_offset)
178+
let original_node = token_at_offset_ignore_whitespace(&original_file.value, original_offset)
180179
.and_then(|token| token.parent_ancestors().find_map(ast::Item::cast));
181180
let ancestor_items = iter::successors(
182181
Option::zip(
@@ -249,7 +248,7 @@ fn expand(
249248
}
250249

251250
// No attributes have been expanded, so look for macro_call! token trees or derive token trees
252-
let orig_tt = ancestors_at_offset(&original_file, original_offset)
251+
let orig_tt = ancestors_at_offset(&original_file.value, original_offset)
253252
.map_while(Either::<ast::TokenTree, ast::Meta>::cast)
254253
.last()?;
255254
let spec_tt = ancestors_at_offset(&speculative_file, fake_ident_token.text_range().start())
@@ -292,7 +291,7 @@ fn expand(
292291
fake_mapped_tokens.into_iter().min_by_key(|(_, rank)| *rank)
293292
{
294293
return Some(ExpansionResult {
295-
original_file,
294+
original_file: original_file.value,
296295
speculative_file,
297296
original_offset,
298297
speculative_offset: fake_ident_token.text_range().start(),
@@ -349,7 +348,7 @@ fn expand(
349348
}
350349
let result = expand_maybe_stop(
351350
sema,
352-
actual_expansion.clone(),
351+
InFile::new(file.into(), actual_expansion.clone()),
353352
fake_expansion.clone(),
354353
new_offset,
355354
fake_mapped_token,

src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,56 @@ fn foo() {
21112111
);
21122112
}
21132113

2114+
#[test]
2115+
fn cfg_attr_attr_macro() {
2116+
check(
2117+
r#"
2118+
//- proc_macros: identity
2119+
#[cfg_attr(test, proc_macros::identity)]
2120+
fn foo() {
2121+
$0
2122+
}
2123+
"#,
2124+
expect![[r#"
2125+
fn foo() fn()
2126+
md proc_macros
2127+
bt u32 u32
2128+
kw async
2129+
kw const
2130+
kw crate::
2131+
kw enum
2132+
kw extern
2133+
kw false
2134+
kw fn
2135+
kw for
2136+
kw if
2137+
kw if let
2138+
kw impl
2139+
kw impl for
2140+
kw let
2141+
kw letm
2142+
kw loop
2143+
kw match
2144+
kw mod
2145+
kw return
2146+
kw self::
2147+
kw static
2148+
kw struct
2149+
kw trait
2150+
kw true
2151+
kw type
2152+
kw union
2153+
kw unsafe
2154+
kw use
2155+
kw while
2156+
kw while let
2157+
sn macro_rules
2158+
sn pd
2159+
sn ppd
2160+
"#]],
2161+
);
2162+
}
2163+
21142164
#[test]
21152165
fn escaped_label() {
21162166
check(

src/tools/rust-analyzer/crates/ide-db/src/search.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ impl<'a> FindUsages<'a> {
524524
fn find_nodes<'b>(
525525
sema: &'b Semantics<'_, RootDatabase>,
526526
name: &str,
527+
file_id: EditionedFileId,
527528
node: &syntax::SyntaxNode,
528529
offset: TextSize,
529530
) -> impl Iterator<Item = SyntaxNode> + 'b {
@@ -534,7 +535,7 @@ impl<'a> FindUsages<'a> {
534535
})
535536
.into_iter()
536537
.flat_map(move |token| {
537-
if sema.might_be_inside_macro_call(&token) {
538+
if sema.is_inside_macro_call(InFile::new(file_id.into(), &token)) {
538539
sema.descend_into_macros_exact(token)
539540
} else {
540541
<_>::from([token])
@@ -654,11 +655,14 @@ impl<'a> FindUsages<'a> {
654655
let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
655656

656657
for offset in FindUsages::match_indices(&file_text, &finder, search_range) {
657-
let usages =
658-
FindUsages::find_nodes(sema, &current_to_process, &tree, offset)
659-
.filter(|it| {
660-
matches!(it.kind(), SyntaxKind::NAME | SyntaxKind::NAME_REF)
661-
});
658+
let usages = FindUsages::find_nodes(
659+
sema,
660+
&current_to_process,
661+
file_id,
662+
&tree,
663+
offset,
664+
)
665+
.filter(|it| matches!(it.kind(), SyntaxKind::NAME | SyntaxKind::NAME_REF));
662666
for usage in usages {
663667
if let Some(alias) = usage.parent().and_then(|it| {
664668
let path = ast::PathSegment::cast(it)?.parent_path();
@@ -813,7 +817,7 @@ impl<'a> FindUsages<'a> {
813817
let tree = LazyCell::new(move || this.sema.parse(file_id).syntax().clone());
814818

815819
for offset in FindUsages::match_indices(&file_text, finder, search_range) {
816-
let usages = FindUsages::find_nodes(this.sema, name, &tree, offset)
820+
let usages = FindUsages::find_nodes(this.sema, name, file_id, &tree, offset)
817821
.filter_map(ast::NameRef::cast);
818822
for usage in usages {
819823
let found_usage = usage
@@ -970,8 +974,8 @@ impl<'a> FindUsages<'a> {
970974
return;
971975
}
972976

973-
for name in
974-
Self::find_nodes(sema, name, &tree, offset).filter_map(ast::NameLike::cast)
977+
for name in Self::find_nodes(sema, name, file_id, &tree, offset)
978+
.filter_map(ast::NameLike::cast)
975979
{
976980
if match name {
977981
ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
@@ -985,8 +989,8 @@ impl<'a> FindUsages<'a> {
985989
// Search for occurrences of the `Self` referring to our type
986990
if let Some((self_ty, finder)) = &include_self_kw_refs {
987991
for offset in Self::match_indices(&text, finder, search_range) {
988-
for name_ref in
989-
Self::find_nodes(sema, "Self", &tree, offset).filter_map(ast::NameRef::cast)
992+
for name_ref in Self::find_nodes(sema, "Self", file_id, &tree, offset)
993+
.filter_map(ast::NameRef::cast)
990994
{
991995
if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
992996
return;
@@ -1010,7 +1014,7 @@ impl<'a> FindUsages<'a> {
10101014
let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
10111015

10121016
for offset in Self::match_indices(&text, finder, search_range) {
1013-
for name_ref in Self::find_nodes(sema, "super", &tree, offset)
1017+
for name_ref in Self::find_nodes(sema, "super", file_id, &tree, offset)
10141018
.filter_map(ast::NameRef::cast)
10151019
{
10161020
if self.found_name_ref(&name_ref, sink) {
@@ -1020,7 +1024,7 @@ impl<'a> FindUsages<'a> {
10201024
}
10211025
if let Some(finder) = &is_crate_root {
10221026
for offset in Self::match_indices(&text, finder, search_range) {
1023-
for name_ref in Self::find_nodes(sema, "crate", &tree, offset)
1027+
for name_ref in Self::find_nodes(sema, "crate", file_id, &tree, offset)
10241028
.filter_map(ast::NameRef::cast)
10251029
{
10261030
if self.found_name_ref(&name_ref, sink) {
@@ -1064,8 +1068,8 @@ impl<'a> FindUsages<'a> {
10641068
let finder = &Finder::new("self");
10651069

10661070
for offset in Self::match_indices(&text, finder, search_range) {
1067-
for name_ref in
1068-
Self::find_nodes(sema, "self", &tree, offset).filter_map(ast::NameRef::cast)
1071+
for name_ref in Self::find_nodes(sema, "self", file_id, &tree, offset)
1072+
.filter_map(ast::NameRef::cast)
10691073
{
10701074
if self.found_self_module_name_ref(&name_ref, sink) {
10711075
return;

src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ impl<'db> MatchFinder<'db> {
287287
if let Some(expanded) = self.sema.expand_macro_call(&macro_call) {
288288
if let Some(tt) = macro_call.token_tree() {
289289
self.output_debug_for_nodes_at_range(
290-
&expanded,
290+
&expanded.value,
291291
range,
292292
&Some(self.sema.original_range(tt.syntax())),
293293
out,

0 commit comments

Comments
 (0)