From 19465b94f5623a8c317ad60162291fec4dd6bdf4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Dec 2024 09:00:08 +0100 Subject: [PATCH] Add implict unsafety inlay hints for extern blocks --- crates/ide/src/inlay_hints.rs | 21 +++- crates/ide/src/inlay_hints/extern_block.rs | 138 +++++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 crates/ide/src/inlay_hints/extern_block.rs diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 1ae8bfa9b6..aa99ba49bc 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -29,6 +29,7 @@ mod closing_brace; mod closure_captures; mod closure_ret; mod discriminant; +mod extern_block; mod generic_param; mod implicit_drop; mod implicit_static; @@ -116,6 +117,7 @@ pub(crate) fn inlay_hints( #[derive(Default)] struct InlayHintCtx { lifetime_stacks: Vec>, + extern_block_parent: Option, } pub(crate) fn inlay_hints_resolve( @@ -174,12 +176,18 @@ fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent) -> Option { if ast::AnyHasGenericParams::can_cast(n.kind()) { ctx.lifetime_stacks.pop(); } + if ast::ExternBlock::can_cast(n.kind()) { + ctx.extern_block_parent = None; + } None } } @@ -234,12 +242,20 @@ fn hints( ast::Item(it) => match it { ast::Item::Fn(it) => { implicit_drop::hints(hints, famous_defs, config, file_id, &it); + if let Some(extern_block) = &ctx.extern_block_parent { + extern_block::fn_hints(hints, famous_defs, config, file_id, &it, extern_block); + } lifetime::fn_hints(hints, ctx, famous_defs, config, file_id, it) }, - // static type elisions - ast::Item::Static(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)), + ast::Item::Static(it) => { + if let Some(extern_block) = &ctx.extern_block_parent { + extern_block::static_hints(hints, famous_defs, config, file_id, &it, extern_block); + } + implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)) + }, ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)), ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it), + ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, file_id, it), _ => None, }, // FIXME: trait object type elisions @@ -368,6 +384,7 @@ pub enum InlayKind { Type, Drop, RangeExclusive, + ExternUnsafety, } #[derive(Debug, Hash)] diff --git a/crates/ide/src/inlay_hints/extern_block.rs b/crates/ide/src/inlay_hints/extern_block.rs new file mode 100644 index 0000000000..4cc4925cda --- /dev/null +++ b/crates/ide/src/inlay_hints/extern_block.rs @@ -0,0 +1,138 @@ +//! Extern block hints +use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; +use span::EditionedFileId; +use syntax::{ast, AstNode, SyntaxToken}; + +use crate::{InlayHint, InlayHintsConfig}; + +pub(super) fn extern_block_hints( + acc: &mut Vec, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + extern_block: ast::ExternBlock, +) -> Option<()> { + if extern_block.unsafe_token().is_some() { + return None; + } + let abi = extern_block.abi()?; + acc.push(InlayHint { + range: abi.syntax().text_range(), + position: crate::InlayHintPosition::Before, + pad_left: false, + pad_right: true, + kind: crate::InlayKind::ExternUnsafety, + label: crate::InlayHintLabel::from("unsafe"), + text_edit: Some(TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())), + resolve_parent: Some(extern_block.syntax().text_range()), + }); + Some(()) +} + +pub(super) fn fn_hints( + acc: &mut Vec, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + fn_: &ast::Fn, + extern_block: &ast::ExternBlock, +) -> Option<()> { + let implicit_unsafe = fn_.safe_token().is_none() && fn_.unsafe_token().is_none(); + if !implicit_unsafe { + return None; + } + let fn_ = fn_.fn_token()?; + acc.push(item_hint(extern_block, fn_)); + Some(()) +} + +pub(super) fn static_hints( + acc: &mut Vec, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + static_: &ast::Static, + extern_block: &ast::ExternBlock, +) -> Option<()> { + let implicit_unsafe = static_.safe_token().is_none() && static_.unsafe_token().is_none(); + if !implicit_unsafe { + return None; + } + let static_ = static_.static_token()?; + acc.push(item_hint(extern_block, static_)); + Some(()) +} + +fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint { + InlayHint { + range: token.text_range(), + position: crate::InlayHintPosition::Before, + pad_left: false, + pad_right: true, + kind: crate::InlayKind::ExternUnsafety, + label: crate::InlayHintLabel::from("unsafe"), + text_edit: { + let mut builder = TextEdit::builder(); + builder.insert(token.text_range().start(), "unsafe ".to_owned()); + if extern_block.unsafe_token().is_none() { + if let Some(abi) = extern_block.abi() { + builder.insert(abi.syntax().text_range().start(), "unsafe ".to_owned()); + } + } + Some(builder.finish()) + }, + resolve_parent: Some(extern_block.syntax().text_range()), + } +} + +#[cfg(test)] +mod tests { + use crate::inlay_hints::tests::{check_with_config, DISABLED_CONFIG}; + + #[test] + fn unadorned() { + check_with_config( + DISABLED_CONFIG, + r#" + extern "C" { +//^^^^^^^^^^ unsafe + static FOO: (); + // ^^^^^^ unsafe + pub static FOO: (); + // ^^^^^^unsafe + unsafe static FOO: (); + safe static FOO: (); + fn foo(); + // ^^ unsafe + pub fn foo(); + // ^^ unsafe + unsafe fn foo(); + safe fn foo(); +} +"#, + ); + } + + #[test] + fn adorned() { + check_with_config( + DISABLED_CONFIG, + r#" +unsafe extern "C" { + static FOO: (); + // ^^^^^^ unsafe + pub static FOO: (); + // ^^^^^^unsafe + unsafe static FOO: (); + safe static FOO: (); + fn foo(); + // ^^ unsafe + pub fn foo(); + // ^^ unsafe + unsafe fn foo(); + safe fn foo(); +} +"#, + ); + } +}