Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better completions for extern blocks #18360

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 75 additions & 51 deletions crates/ide-completion/src/completions/item_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ pub(crate) fn complete_item_list(
kind: &ItemListKind,
) {
let _p = tracing::info_span!("complete_item_list").entered();
if path_ctx.is_trivial_path() {

// We handle completions for trait-impls in [`item_list::trait_impl`]
if path_ctx.is_trivial_path() && !matches!(kind, ItemListKind::TraitImpl(_)) {
add_keywords(acc, ctx, Some(kind));
}

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

let in_item_list = matches!(kind, Some(ItemListKind::SourceFile | ItemListKind::Module) | None);
let in_assoc_non_trait_impl = matches!(kind, Some(ItemListKind::Impl | ItemListKind::Trait));
let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock));

let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock { .. }));
let in_unsafe_extern_block =
matches!(kind, Some(ItemListKind::ExternBlock { is_unsafe: true }));

let in_trait = matches!(kind, Some(ItemListKind::Trait));
let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl(_)));
let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl));
let no_vis_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
let in_block = kind.is_none();

let missing_qualifiers = [
ctx.qualifier_ctx.unsafe_tok.is_none().then_some(("unsafe", "unsafe $0")),
ctx.qualifier_ctx.async_tok.is_none().then_some(("async", "async $0")),
];

if !in_trait_impl {
// handle qualifier tokens
if missing_qualifiers.iter().any(Option::is_none) {
// only complete missing qualifiers
missing_qualifiers.iter().filter_map(|x| *x).for_each(|(kw, snippet)| {
add_keyword(kw, snippet);
});

if in_item_list || in_assoc_non_trait_impl {
add_keyword("fn", "fn $1($2) {\n $0\n}");
}
let no_vis_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
let has_unsafe_kw = ctx.qualifier_ctx.unsafe_tok.is_some();
let has_async_kw = ctx.qualifier_ctx.async_tok.is_some();
let has_safe_kw = ctx.qualifier_ctx.safe_tok.is_some();

// Some keywords are invalid after non-vis qualifiers, so we handle them first.
if (has_unsafe_kw || has_safe_kw) && in_extern_block {
add_keyword("fn", "fn $1($2);");
add_keyword("static", "static $1: $2;");
return;
}

if ctx.qualifier_ctx.unsafe_tok.is_some() && in_item_list {
add_keyword("trait", "trait $1 {\n $0\n}");
if no_vis_qualifiers {
add_keyword("impl", "impl $1 {\n $0\n}");
}
}
if has_unsafe_kw || has_async_kw {
if !has_unsafe_kw {
add_keyword("unsafe", "unsafe $0");
}
if !has_async_kw {
add_keyword("async", "async $0");
}

return;
if in_item_list || in_assoc_non_trait_impl {
add_keyword("fn", "fn $1($2) {\n $0\n}");
}

if in_item_list {
add_keyword("enum", "enum $1 {\n $0\n}");
add_keyword("mod", "mod $0");
add_keyword("static", "static $0");
add_keyword("struct", "struct $0");
if has_unsafe_kw && in_item_list {
add_keyword("trait", "trait $1 {\n $0\n}");
add_keyword("union", "union $1 {\n $0\n}");
add_keyword("use", "use $0");
if no_vis_qualifiers {
add_keyword("impl", "impl $1 {\n $0\n}");
}
}

if !in_trait && !in_block && no_vis_qualifiers {
add_keyword("pub(crate)", "pub(crate) $0");
add_keyword("pub(super)", "pub(super) $0");
add_keyword("pub", "pub $0");
if !has_async_kw && no_vis_qualifiers && in_item_list {
add_keyword("extern", "extern $0");
}

if in_extern_block {
add_keyword("fn", "fn $1($2);");
} else {
if !in_inherent_impl {
if !in_trait {
add_keyword("extern", "extern $0");
}
add_keyword("type", "type $0");
}
return;
}

add_keyword("fn", "fn $1($2) {\n $0\n}");
add_keyword("unsafe", "unsafe $0");
add_keyword("const", "const $0");
add_keyword("async", "async $0");
// ...and the rest deals with cases without any non-vis qualifiers.

// Visibility qualifiers
if !in_trait && !in_block && no_vis_qualifiers {
add_keyword("pub(crate)", "pub(crate) $0");
add_keyword("pub(super)", "pub(super) $0");
add_keyword("pub", "pub $0");
}

// Keywords that are valid in `item_list`
if in_item_list {
add_keyword("enum", "enum $1 {\n $0\n}");
add_keyword("mod", "mod $0");
add_keyword("static", "static $0");
add_keyword("struct", "struct $0");
add_keyword("trait", "trait $1 {\n $0\n}");
add_keyword("union", "union $1 {\n $0\n}");
add_keyword("use", "use $0");
if no_vis_qualifiers {
add_keyword("impl", "impl $1 {\n $0\n}");
}
}

if in_extern_block {
add_keyword("unsafe", "unsafe $0");
if in_unsafe_extern_block {
add_keyword("safe", "safe $0");
}

add_keyword("fn", "fn $1($2);");
add_keyword("static", "static $1: $2;");
} else {
if !in_inherent_impl {
if !in_trait {
add_keyword("extern", "extern $0");
}
add_keyword("type", "type $0");
}

add_keyword("fn", "fn $1($2) {\n $0\n}");
add_keyword("unsafe", "unsafe $0");
add_keyword("const", "const $0");
add_keyword("async", "async $0");
}
}
1 change: 1 addition & 0 deletions crates/ide-completion/src/completions/keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ mod tests {
r"fn my_fn() { unsafe $0 }",
expect![[r#"
kw async
kw extern
kw fn
kw impl
kw trait
Expand Down
8 changes: 6 additions & 2 deletions crates/ide-completion/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,16 @@ pub(crate) struct QualifierCtx {
// TODO: Add try_tok and default_tok
pub(crate) async_tok: Option<SyntaxToken>,
pub(crate) unsafe_tok: Option<SyntaxToken>,
pub(crate) safe_tok: Option<SyntaxToken>,
pub(crate) vis_node: Option<ast::Visibility>,
}

impl QualifierCtx {
pub(crate) fn none(&self) -> bool {
self.async_tok.is_none() && self.unsafe_tok.is_none() && self.vis_node.is_none()
self.async_tok.is_none()
&& self.unsafe_tok.is_none()
&& self.safe_tok.is_none()
&& self.vis_node.is_none()
}
}

Expand Down Expand Up @@ -229,7 +233,7 @@ pub(crate) enum ItemListKind {
Impl,
TraitImpl(Option<ast::Impl>),
Trait,
ExternBlock,
ExternBlock { is_unsafe: bool },
}

#[derive(Debug)]
Expand Down
10 changes: 9 additions & 1 deletion crates/ide-completion/src/context/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,14 @@ fn classify_name_ref(
},
None => return None,
} },
ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
ast::ExternItemList(it) => {
let exn_blk = it.syntax().parent().and_then(ast::ExternBlock::cast);
PathKind::Item {
kind: ItemListKind::ExternBlock {
is_unsafe: exn_blk.and_then(|it| it.unsafe_token()).is_some(),
}
}
},
ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
_ => return None,
}
Expand Down Expand Up @@ -1310,6 +1317,7 @@ fn classify_name_ref(
match token.kind() {
SyntaxKind::UNSAFE_KW => qualifier_ctx.unsafe_tok = Some(token),
SyntaxKind::ASYNC_KW => qualifier_ctx.async_tok = Some(token),
SyntaxKind::SAFE_KW => qualifier_ctx.safe_tok = Some(token),
_ => {}
}
}
Expand Down
55 changes: 55 additions & 0 deletions crates/ide-completion/src/tests/item_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ fn after_unsafe_token() {
r#"unsafe $0"#,
expect![[r#"
kw async
kw extern
kw fn
kw impl
kw trait
Expand Down Expand Up @@ -495,3 +496,57 @@ type O = $0;
",
)
}

#[test]
fn inside_extern_blocks() {
// Should suggest `fn`, `static`, `unsafe`
check(
r#"extern { $0 }"#,
expect![[r#"
ma makro!(…) macro_rules! makro
md module
kw crate::
kw fn
kw pub
kw pub(crate)
kw pub(super)
kw self::
kw static
kw unsafe
"#]],
);

// Should suggest `fn`, `static`, `safe`, `unsafe`
check(
r#"unsafe extern { $0 }"#,
expect![[r#"
ma makro!(…) macro_rules! makro
md module
kw crate::
kw fn
kw pub
kw pub(crate)
kw pub(super)
kw safe
kw self::
kw static
kw unsafe
"#]],
);

check(
r#"unsafe extern { pub safe $0 }"#,
expect![[r#"
kw fn
kw static
"#]],
);

check(
r#"unsafe extern { pub unsafe $0 }"#,
expect![[r#"
kw fn
kw static
"#]],
)
}