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.rs152
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: "",
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+ }
+}