Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/inlay_hints/bounds.rs')
| -rw-r--r-- | crates/ide/src/inlay_hints/bounds.rs | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/crates/ide/src/inlay_hints/bounds.rs b/crates/ide/src/inlay_hints/bounds.rs new file mode 100644 index 0000000000..429ddd31cb --- /dev/null +++ b/crates/ide/src/inlay_hints/bounds.rs @@ -0,0 +1,152 @@ +//! Implementation of trait bound hints. +//! +//! Currently this renders the implied `Sized` bound. +use ide_db::{famous_defs::FamousDefs, FileRange}; + +use span::EditionedFileId; +use syntax::ast::{self, AstNode, HasTypeBounds}; + +use crate::{ + InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind, + TryToNav, +}; + +pub(super) fn hints( + acc: &mut Vec<InlayHint>, + famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, + config: &InlayHintsConfig, + _file_id: EditionedFileId, + params: ast::GenericParamList, +) -> Option<()> { + if !config.sized_bound { + return None; + } + + let linked_location = + famous_defs.core_marker_Sized().and_then(|it| it.try_to_nav(sema.db)).map(|it| { + let n = it.call_site(); + FileRange { file_id: n.file_id, range: n.focus_or_full_range() } + }); + + for param in params.type_or_const_params() { + match param { + ast::TypeOrConstParam::Type(type_param) => { + let c = type_param.colon_token().map(|it| it.text_range()); + let has_bounds = + type_param.type_bound_list().is_some_and(|it| it.bounds().next().is_some()); + acc.push(InlayHint { + range: c.unwrap_or_else(|| type_param.syntax().text_range()), + kind: InlayKind::Type, + label: { + let mut hint = InlayHintLabel::default(); + if c.is_none() { + hint.parts.push(InlayHintLabelPart { + text: ": ".to_owned(), + linked_location: None, + tooltip: None, + }); + } + hint.parts.push(InlayHintLabelPart { + text: "Sized".to_owned(), + linked_location, + tooltip: None, + }); + if has_bounds { + hint.parts.push(InlayHintLabelPart { + text: " +".to_owned(), + linked_location: None, + tooltip: None, + }); + } + hint + }, + text_edit: None, + position: InlayHintPosition::After, + pad_left: c.is_some(), + pad_right: has_bounds, + resolve_parent: Some(params.syntax().text_range()), + }); + } + ast::TypeOrConstParam::Const(_) => (), + } + } + + Some(()) +} + +#[cfg(test)] +mod tests { + use expect_test::expect; + + use crate::inlay_hints::InlayHintsConfig; + + use crate::inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG}; + + #[track_caller] + fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + check_with_config(InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG }, ra_fixture); + } + + #[test] + fn smoke() { + check( + r#" +fn foo<T>() {} + // ^ : Sized +"#, + ); + } + + #[test] + fn with_colon() { + check( + r#" +fn foo<T:>() {} + // ^ Sized +"#, + ); + } + + #[test] + fn with_colon_and_bounds() { + check( + r#" +fn foo<T: 'static>() {} + // ^ Sized + +"#, + ); + } + + #[test] + fn location_works() { + check_expect( + InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG }, + r#" +//- minicore: sized +fn foo<T>() {} +"#, + expect![[r#" + [ + ( + 7..8, + [ + ": ", + InlayHintLabelPart { + text: "Sized", + linked_location: Some( + FileRangeWrapper { + file_id: FileId( + 1, + ), + range: 135..140, + }, + ), + tooltip: "", + }, + ], + ), + ] + "#]], + ); + } +} |