Skip to content

Commit fa815a1

Browse files
committed
Auto merge of #18360 - roife:safe-kw-3, r=Veykril
feat: better completions for extern blcoks This PR refactors `add_keywords` (making it much clearer!) and enhances completion for `extern` blocks. It is recommended to reviewing the changes in order of the commits: - The first commit (f3c4dde0a4917a2bac98605cc045eecfb4d69872) doesn’t change any logic but refactors parts of the `add_keywords` function and adds detailed comments. - The second commit (5dcc1ab649bf8a49cadf006d620871b12f093a2f) improves completion for `extern` kw and extern blocks.
2 parents 20864ef + ad27b82 commit fa815a1

File tree

5 files changed

+146
-54
lines changed

5 files changed

+146
-54
lines changed

src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs

+75-51
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ pub(crate) fn complete_item_list(
2929
kind: &ItemListKind,
3030
) {
3131
let _p = tracing::info_span!("complete_item_list").entered();
32-
if path_ctx.is_trivial_path() {
32+
33+
// We handle completions for trait-impls in [`item_list::trait_impl`]
34+
if path_ctx.is_trivial_path() && !matches!(kind, ItemListKind::TraitImpl(_)) {
3335
add_keywords(acc, ctx, Some(kind));
3436
}
3537

@@ -75,73 +77,95 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option
7577

7678
let in_item_list = matches!(kind, Some(ItemListKind::SourceFile | ItemListKind::Module) | None);
7779
let in_assoc_non_trait_impl = matches!(kind, Some(ItemListKind::Impl | ItemListKind::Trait));
78-
let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock));
80+
81+
let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock { .. }));
82+
let in_unsafe_extern_block =
83+
matches!(kind, Some(ItemListKind::ExternBlock { is_unsafe: true }));
84+
7985
let in_trait = matches!(kind, Some(ItemListKind::Trait));
80-
let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl(_)));
8186
let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl));
82-
let no_vis_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
8387
let in_block = kind.is_none();
8488

85-
let missing_qualifiers = [
86-
ctx.qualifier_ctx.unsafe_tok.is_none().then_some(("unsafe", "unsafe $0")),
87-
ctx.qualifier_ctx.async_tok.is_none().then_some(("async", "async $0")),
88-
];
89-
90-
if !in_trait_impl {
91-
// handle qualifier tokens
92-
if missing_qualifiers.iter().any(Option::is_none) {
93-
// only complete missing qualifiers
94-
missing_qualifiers.iter().filter_map(|x| *x).for_each(|(kw, snippet)| {
95-
add_keyword(kw, snippet);
96-
});
97-
98-
if in_item_list || in_assoc_non_trait_impl {
99-
add_keyword("fn", "fn $1($2) {\n $0\n}");
100-
}
89+
let no_vis_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
90+
let has_unsafe_kw = ctx.qualifier_ctx.unsafe_tok.is_some();
91+
let has_async_kw = ctx.qualifier_ctx.async_tok.is_some();
92+
let has_safe_kw = ctx.qualifier_ctx.safe_tok.is_some();
93+
94+
// Some keywords are invalid after non-vis qualifiers, so we handle them first.
95+
if (has_unsafe_kw || has_safe_kw) && in_extern_block {
96+
add_keyword("fn", "fn $1($2);");
97+
add_keyword("static", "static $1: $2;");
98+
return;
99+
}
101100

102-
if ctx.qualifier_ctx.unsafe_tok.is_some() && in_item_list {
103-
add_keyword("trait", "trait $1 {\n $0\n}");
104-
if no_vis_qualifiers {
105-
add_keyword("impl", "impl $1 {\n $0\n}");
106-
}
107-
}
101+
if has_unsafe_kw || has_async_kw {
102+
if !has_unsafe_kw {
103+
add_keyword("unsafe", "unsafe $0");
104+
}
105+
if !has_async_kw {
106+
add_keyword("async", "async $0");
107+
}
108108

109-
return;
109+
if in_item_list || in_assoc_non_trait_impl {
110+
add_keyword("fn", "fn $1($2) {\n $0\n}");
110111
}
111112

112-
if in_item_list {
113-
add_keyword("enum", "enum $1 {\n $0\n}");
114-
add_keyword("mod", "mod $0");
115-
add_keyword("static", "static $0");
116-
add_keyword("struct", "struct $0");
113+
if has_unsafe_kw && in_item_list {
117114
add_keyword("trait", "trait $1 {\n $0\n}");
118-
add_keyword("union", "union $1 {\n $0\n}");
119-
add_keyword("use", "use $0");
120115
if no_vis_qualifiers {
121116
add_keyword("impl", "impl $1 {\n $0\n}");
122117
}
123118
}
124119

125-
if !in_trait && !in_block && no_vis_qualifiers {
126-
add_keyword("pub(crate)", "pub(crate) $0");
127-
add_keyword("pub(super)", "pub(super) $0");
128-
add_keyword("pub", "pub $0");
120+
if !has_async_kw && no_vis_qualifiers && in_item_list {
121+
add_keyword("extern", "extern $0");
129122
}
130123

131-
if in_extern_block {
132-
add_keyword("fn", "fn $1($2);");
133-
} else {
134-
if !in_inherent_impl {
135-
if !in_trait {
136-
add_keyword("extern", "extern $0");
137-
}
138-
add_keyword("type", "type $0");
139-
}
124+
return;
125+
}
140126

141-
add_keyword("fn", "fn $1($2) {\n $0\n}");
142-
add_keyword("unsafe", "unsafe $0");
143-
add_keyword("const", "const $0");
144-
add_keyword("async", "async $0");
127+
// ...and the rest deals with cases without any non-vis qualifiers.
128+
129+
// Visibility qualifiers
130+
if !in_trait && !in_block && no_vis_qualifiers {
131+
add_keyword("pub(crate)", "pub(crate) $0");
132+
add_keyword("pub(super)", "pub(super) $0");
133+
add_keyword("pub", "pub $0");
134+
}
135+
136+
// Keywords that are valid in `item_list`
137+
if in_item_list {
138+
add_keyword("enum", "enum $1 {\n $0\n}");
139+
add_keyword("mod", "mod $0");
140+
add_keyword("static", "static $0");
141+
add_keyword("struct", "struct $0");
142+
add_keyword("trait", "trait $1 {\n $0\n}");
143+
add_keyword("union", "union $1 {\n $0\n}");
144+
add_keyword("use", "use $0");
145+
if no_vis_qualifiers {
146+
add_keyword("impl", "impl $1 {\n $0\n}");
147+
}
148+
}
149+
150+
if in_extern_block {
151+
add_keyword("unsafe", "unsafe $0");
152+
if in_unsafe_extern_block {
153+
add_keyword("safe", "safe $0");
145154
}
155+
156+
add_keyword("fn", "fn $1($2);");
157+
add_keyword("static", "static $1: $2;");
158+
} else {
159+
if !in_inherent_impl {
160+
if !in_trait {
161+
add_keyword("extern", "extern $0");
162+
}
163+
add_keyword("type", "type $0");
164+
}
165+
166+
add_keyword("fn", "fn $1($2) {\n $0\n}");
167+
add_keyword("unsafe", "unsafe $0");
168+
add_keyword("const", "const $0");
169+
add_keyword("async", "async $0");
146170
}
147171
}

src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ mod tests {
5858
r"fn my_fn() { unsafe $0 }",
5959
expect![[r#"
6060
kw async
61+
kw extern
6162
kw fn
6263
kw impl
6364
kw trait

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,16 @@ pub(crate) struct QualifierCtx {
4848
// TODO: Add try_tok and default_tok
4949
pub(crate) async_tok: Option<SyntaxToken>,
5050
pub(crate) unsafe_tok: Option<SyntaxToken>,
51+
pub(crate) safe_tok: Option<SyntaxToken>,
5152
pub(crate) vis_node: Option<ast::Visibility>,
5253
}
5354

5455
impl QualifierCtx {
5556
pub(crate) fn none(&self) -> bool {
56-
self.async_tok.is_none() && self.unsafe_tok.is_none() && self.vis_node.is_none()
57+
self.async_tok.is_none()
58+
&& self.unsafe_tok.is_none()
59+
&& self.safe_tok.is_none()
60+
&& self.vis_node.is_none()
5761
}
5862
}
5963

@@ -229,7 +233,7 @@ pub(crate) enum ItemListKind {
229233
Impl,
230234
TraitImpl(Option<ast::Impl>),
231235
Trait,
232-
ExternBlock,
236+
ExternBlock { is_unsafe: bool },
233237
}
234238

235239
#[derive(Debug)]

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -1108,7 +1108,14 @@ fn classify_name_ref(
11081108
},
11091109
None => return None,
11101110
} },
1111-
ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
1111+
ast::ExternItemList(it) => {
1112+
let exn_blk = it.syntax().parent().and_then(ast::ExternBlock::cast);
1113+
PathKind::Item {
1114+
kind: ItemListKind::ExternBlock {
1115+
is_unsafe: exn_blk.and_then(|it| it.unsafe_token()).is_some(),
1116+
}
1117+
}
1118+
},
11121119
ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
11131120
_ => return None,
11141121
}
@@ -1310,6 +1317,7 @@ fn classify_name_ref(
13101317
match token.kind() {
13111318
SyntaxKind::UNSAFE_KW => qualifier_ctx.unsafe_tok = Some(token),
13121319
SyntaxKind::ASYNC_KW => qualifier_ctx.async_tok = Some(token),
1320+
SyntaxKind::SAFE_KW => qualifier_ctx.safe_tok = Some(token),
13131321
_ => {}
13141322
}
13151323
}

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

+55
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ fn after_unsafe_token() {
124124
r#"unsafe $0"#,
125125
expect![[r#"
126126
kw async
127+
kw extern
127128
kw fn
128129
kw impl
129130
kw trait
@@ -495,3 +496,57 @@ type O = $0;
495496
",
496497
)
497498
}
499+
500+
#[test]
501+
fn inside_extern_blocks() {
502+
// Should suggest `fn`, `static`, `unsafe`
503+
check(
504+
r#"extern { $0 }"#,
505+
expect![[r#"
506+
ma makro!(…) macro_rules! makro
507+
md module
508+
kw crate::
509+
kw fn
510+
kw pub
511+
kw pub(crate)
512+
kw pub(super)
513+
kw self::
514+
kw static
515+
kw unsafe
516+
"#]],
517+
);
518+
519+
// Should suggest `fn`, `static`, `safe`, `unsafe`
520+
check(
521+
r#"unsafe extern { $0 }"#,
522+
expect![[r#"
523+
ma makro!(…) macro_rules! makro
524+
md module
525+
kw crate::
526+
kw fn
527+
kw pub
528+
kw pub(crate)
529+
kw pub(super)
530+
kw safe
531+
kw self::
532+
kw static
533+
kw unsafe
534+
"#]],
535+
);
536+
537+
check(
538+
r#"unsafe extern { pub safe $0 }"#,
539+
expect![[r#"
540+
kw fn
541+
kw static
542+
"#]],
543+
);
544+
545+
check(
546+
r#"unsafe extern { pub unsafe $0 }"#,
547+
expect![[r#"
548+
kw fn
549+
kw static
550+
"#]],
551+
)
552+
}

0 commit comments

Comments
 (0)