| //! Implementation of trait bound hints. |
| //! |
| //! Currently this renders the implied `Sized` bound. |
| use ide_db::{FileRange, famous_defs::FamousDefs}; |
| |
| 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, |
| params: ast::GenericParamList, |
| ) -> Option<()> { |
| if !config.sized_bound { |
| return None; |
| } |
| |
| let sized_trait = famous_defs.core_marker_Sized(); |
| |
| 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: sized_trait.and_then(|it| { |
| config.lazy_location_opt(|| { |
| 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(), |
| } |
| }) |
| }) |
| }), |
| 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::{DISABLED_CONFIG, check_expect, check_with_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( |
| Computed( |
| FileRangeWrapper { |
| file_id: FileId( |
| 1, |
| ), |
| range: 446..451, |
| }, |
| ), |
| ), |
| tooltip: "", |
| }, |
| ], |
| ), |
| ] |
| "#]], |
| ); |
| } |
| } |