Skip to content

Commit 23738b5

Browse files
authored
Merge pull request #19890 from Veykril/push-kzzntrpllsqx
fix: Fix import insertion not being fully cfg aware
2 parents f06e376 + 087cfe3 commit 23738b5

File tree

12 files changed

+214
-128
lines changed

12 files changed

+214
-128
lines changed

src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
128128
format!("Import `{import_name}`"),
129129
range,
130130
|builder| {
131-
let scope = match scope.clone() {
132-
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
133-
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
134-
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
135-
};
131+
let scope = builder.make_import_scope_mut(scope.clone());
136132
insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use);
137133
},
138134
);
@@ -153,11 +149,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
153149
format!("Import `{import_name} as _`"),
154150
range,
155151
|builder| {
156-
let scope = match scope.clone() {
157-
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
158-
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
159-
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
160-
};
152+
let scope = builder.make_import_scope_mut(scope.clone());
161153
insert_use_as_alias(
162154
&scope,
163155
mod_path_to_ast(&import_path, edition),
@@ -1877,4 +1869,30 @@ fn main() {
18771869
",
18781870
);
18791871
}
1872+
1873+
#[test]
1874+
fn carries_cfg_attr() {
1875+
check_assist(
1876+
auto_import,
1877+
r#"
1878+
mod m {
1879+
pub struct S;
1880+
}
1881+
1882+
#[cfg(test)]
1883+
fn foo(_: S$0) {}
1884+
"#,
1885+
r#"
1886+
#[cfg(test)]
1887+
use m::S;
1888+
1889+
mod m {
1890+
pub struct S;
1891+
}
1892+
1893+
#[cfg(test)]
1894+
fn foo(_: S) {}
1895+
"#,
1896+
);
1897+
}
18801898
}

src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,8 @@ fn replace_usages(
312312
}
313313

314314
// add imports across modules where needed
315-
if let Some((import_scope, path)) = import_data {
316-
let scope = match import_scope {
317-
ImportScope::File(it) => ImportScope::File(edit.make_mut(it)),
318-
ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)),
319-
ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)),
320-
};
315+
if let Some((scope, path)) = import_data {
316+
let scope = edit.make_import_scope_mut(scope);
321317
delayed_mutations.push((scope, path));
322318
}
323319
},

src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,8 @@ pub struct $0Foo {
996996
}
997997
"#,
998998
r#"
999-
pub struct Foo(#[my_custom_attr] u32);
999+
pub struct Foo(#[my_custom_attr]
1000+
u32);
10001001
"#,
10011002
);
10021003
}

src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,8 @@ where
923923
pub struct $0Foo(#[my_custom_attr] u32);
924924
"#,
925925
r#"
926-
pub struct Foo { #[my_custom_attr] field1: u32 }
926+
pub struct Foo { #[my_custom_attr]
927+
field1: u32 }
927928
"#,
928929
);
929930
}

src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
204204
.kind
205205
.is_some_and(|kind| matches!(kind, FlowKind::Break(_, _) | FlowKind::Continue(_)))
206206
{
207-
let scope = match scope {
208-
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
209-
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
210-
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
211-
};
212-
207+
let scope = builder.make_import_scope_mut(scope);
213208
let control_flow_enum =
214209
FamousDefs(&ctx.sema, module.krate()).core_ops_ControlFlow();
215210

src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,7 @@ pub(crate) fn replace_qualified_name_with_use(
8181
|builder| {
8282
// Now that we've brought the name into scope, re-qualify all paths that could be
8383
// affected (that is, all paths inside the node we added the `use` to).
84-
let scope = match scope {
85-
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
86-
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
87-
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
88-
};
84+
let scope = builder.make_import_scope_mut(scope);
8985
shorten_paths(scope.as_syntax_node(), &original_path);
9086
let path = drop_generic_args(&original_path);
9187
let edition = ctx

src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use ide_db::imports::insert_use::ImportScope;
21
use syntax::{
32
TextRange,
43
ast::{self, AstNode, HasArgList, prec::ExprPrecedence},
@@ -114,11 +113,7 @@ fn add_import(
114113
);
115114

116115
if let Some(scope) = scope {
117-
let scope = match scope {
118-
ImportScope::File(it) => ImportScope::File(edit.make_mut(it)),
119-
ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)),
120-
ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)),
121-
};
116+
let scope = edit.make_import_scope_mut(scope);
122117
ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use);
123118
}
124119
}

src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs

Lines changed: 64 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -60,107 +60,87 @@ pub struct InsertUseConfig {
6060
}
6161

6262
#[derive(Debug, Clone)]
63-
pub enum ImportScope {
63+
pub struct ImportScope {
64+
pub kind: ImportScopeKind,
65+
pub required_cfgs: Vec<ast::Attr>,
66+
}
67+
68+
#[derive(Debug, Clone)]
69+
pub enum ImportScopeKind {
6470
File(ast::SourceFile),
6571
Module(ast::ItemList),
6672
Block(ast::StmtList),
6773
}
6874

6975
impl ImportScope {
70-
// FIXME: Remove this?
71-
#[cfg(test)]
72-
fn from(syntax: SyntaxNode) -> Option<Self> {
73-
use syntax::match_ast;
74-
fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool {
75-
attrs.attrs().any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
76-
}
77-
match_ast! {
78-
match syntax {
79-
ast::Module(module) => module.item_list().map(ImportScope::Module),
80-
ast::SourceFile(file) => Some(ImportScope::File(file)),
81-
ast::Fn(func) => contains_cfg_attr(&func).then(|| func.body().and_then(|it| it.stmt_list().map(ImportScope::Block))).flatten(),
82-
ast::Const(konst) => contains_cfg_attr(&konst).then(|| match konst.body()? {
83-
ast::Expr::BlockExpr(block) => Some(block),
84-
_ => None,
85-
}).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)),
86-
ast::Static(statik) => contains_cfg_attr(&statik).then(|| match statik.body()? {
87-
ast::Expr::BlockExpr(block) => Some(block),
88-
_ => None,
89-
}).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)),
90-
_ => None,
91-
92-
}
93-
}
94-
}
95-
9676
/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
9777
/// Returns the original source node inside attributes.
9878
pub fn find_insert_use_container(
9979
position: &SyntaxNode,
10080
sema: &Semantics<'_, RootDatabase>,
10181
) -> Option<Self> {
102-
fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool {
103-
attrs.attrs().any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
104-
}
105-
82+
// The closest block expression ancestor
83+
let mut block = None;
84+
let mut required_cfgs = Vec::new();
10685
// Walk up the ancestor tree searching for a suitable node to do insertions on
10786
// with special handling on cfg-gated items, in which case we want to insert imports locally
10887
// or FIXME: annotate inserted imports with the same cfg
10988
for syntax in sema.ancestors_with_macros(position.clone()) {
11089
if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
111-
return Some(ImportScope::File(file));
112-
} else if let Some(item) = ast::Item::cast(syntax) {
113-
return match item {
114-
ast::Item::Const(konst) if contains_cfg_attr(&konst) => {
115-
// FIXME: Instead of bailing out with None, we should note down that
116-
// this import needs an attribute added
117-
match sema.original_ast_node(konst)?.body()? {
118-
ast::Expr::BlockExpr(block) => block,
119-
_ => return None,
90+
return Some(ImportScope { kind: ImportScopeKind::File(file), required_cfgs });
91+
} else if let Some(module) = ast::Module::cast(syntax.clone()) {
92+
// early return is important here, if we can't find the original module
93+
// in the input there is no way for us to insert an import anywhere.
94+
return sema
95+
.original_ast_node(module)?
96+
.item_list()
97+
.map(ImportScopeKind::Module)
98+
.map(|kind| ImportScope { kind, required_cfgs });
99+
} else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) {
100+
if block.is_none() {
101+
if let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone()) {
102+
if let Some(b) = sema.original_ast_node(b) {
103+
block = b.stmt_list();
120104
}
121-
.stmt_list()
122-
.map(ImportScope::Block)
123105
}
124-
ast::Item::Fn(func) if contains_cfg_attr(&func) => {
125-
// FIXME: Instead of bailing out with None, we should note down that
126-
// this import needs an attribute added
127-
sema.original_ast_node(func)?.body()?.stmt_list().map(ImportScope::Block)
128-
}
129-
ast::Item::Static(statik) if contains_cfg_attr(&statik) => {
130-
// FIXME: Instead of bailing out with None, we should note down that
131-
// this import needs an attribute added
132-
match sema.original_ast_node(statik)?.body()? {
133-
ast::Expr::BlockExpr(block) => block,
134-
_ => return None,
135-
}
136-
.stmt_list()
137-
.map(ImportScope::Block)
138-
}
139-
ast::Item::Module(module) => {
140-
// early return is important here, if we can't find the original module
141-
// in the input there is no way for us to insert an import anywhere.
142-
sema.original_ast_node(module)?.item_list().map(ImportScope::Module)
106+
}
107+
if has_attrs
108+
.attrs()
109+
.any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
110+
{
111+
if let Some(b) = block {
112+
return Some(ImportScope {
113+
kind: ImportScopeKind::Block(b),
114+
required_cfgs,
115+
});
143116
}
144-
_ => continue,
145-
};
117+
required_cfgs.extend(has_attrs.attrs().filter(|attr| {
118+
attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
119+
}));
120+
}
146121
}
147122
}
148123
None
149124
}
150125

151126
pub fn as_syntax_node(&self) -> &SyntaxNode {
152-
match self {
153-
ImportScope::File(file) => file.syntax(),
154-
ImportScope::Module(item_list) => item_list.syntax(),
155-
ImportScope::Block(block) => block.syntax(),
127+
match &self.kind {
128+
ImportScopeKind::File(file) => file.syntax(),
129+
ImportScopeKind::Module(item_list) => item_list.syntax(),
130+
ImportScopeKind::Block(block) => block.syntax(),
156131
}
157132
}
158133

159134
pub fn clone_for_update(&self) -> Self {
160-
match self {
161-
ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
162-
ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
163-
ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()),
135+
Self {
136+
kind: match &self.kind {
137+
ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()),
138+
ImportScopeKind::Module(item_list) => {
139+
ImportScopeKind::Module(item_list.clone_for_update())
140+
}
141+
ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()),
142+
},
143+
required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(),
164144
}
165145
}
166146
}
@@ -216,6 +196,11 @@ fn insert_use_with_alias_option(
216196
use_tree.wrap_in_tree_list();
217197
}
218198
let use_item = make::use_(None, use_tree).clone_for_update();
199+
for attr in
200+
scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
201+
{
202+
ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
203+
}
219204

220205
// merge into existing imports if possible
221206
if let Some(mb) = mb {
@@ -229,7 +214,6 @@ fn insert_use_with_alias_option(
229214
}
230215
}
231216
}
232-
233217
// either we weren't allowed to merge or there is no import that fits the merge conditions
234218
// so look for the place we have to insert to
235219
insert_use_(scope, use_item, cfg.group);
@@ -316,10 +300,10 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
316300
}
317301
_ => None,
318302
};
319-
let mut use_stmts = match scope {
320-
ImportScope::File(f) => f.items(),
321-
ImportScope::Module(m) => m.items(),
322-
ImportScope::Block(b) => b.items(),
303+
let mut use_stmts = match &scope.kind {
304+
ImportScopeKind::File(f) => f.items(),
305+
ImportScopeKind::Module(m) => m.items(),
306+
ImportScopeKind::Block(b) => b.items(),
323307
}
324308
.filter_map(use_stmt);
325309
let mut res = ImportGranularityGuess::Unknown;
@@ -463,12 +447,12 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
463447
}
464448
}
465449

466-
let l_curly = match scope {
467-
ImportScope::File(_) => None,
450+
let l_curly = match &scope.kind {
451+
ImportScopeKind::File(_) => None,
468452
// don't insert the imports before the item list/block expr's opening curly brace
469-
ImportScope::Module(item_list) => item_list.l_curly_token(),
453+
ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
470454
// don't insert the imports before the item list's opening curly brace
471-
ImportScope::Block(block) => block.l_curly_token(),
455+
ImportScopeKind::Block(block) => block.l_curly_token(),
472456
};
473457
// there are no imports in this file at all
474458
// so put the import after all inner module attributes and possible license header comments

0 commit comments

Comments
 (0)