//! Implementation of trait bound hints. //! //! Currently this renders the implied `Sized` bound. use either::Either; use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; use syntax::ast::{self, AstNode}; use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig<'_>, path: Either, ) -> Option<()> { if !config.implied_dyn_trait_hints { return None; } let parent = path.syntax().parent()?; let range = match path { Either::Left(path) => { let paren = parent .ancestors() .take_while(|it| { ast::ParenType::can_cast(it.kind()) || ast::ForType::can_cast(it.kind()) }) .last(); let parent = paren.as_ref().and_then(|it| it.parent()).unwrap_or(parent); if ast::TypeBound::can_cast(parent.kind()) || ast::TypeAnchor::can_cast(parent.kind()) || ast::Impl::cast(parent).is_some_and(|it| { it.trait_().map_or( // only show it for impl type if the impl is not incomplete, otherwise we // are likely typing a trait impl it.assoc_item_list().is_none_or(|it| it.l_curly_token().is_none()), |trait_| trait_.syntax() == path.syntax(), ) }) { return None; } sema.resolve_trait(&path.path()?)?; path.syntax().text_range() } Either::Right(dyn_) => { if dyn_.dyn_token().is_some() { return None; } dyn_.syntax().text_range() } }; acc.push(InlayHint { range, kind: InlayKind::Dyn, label: InlayHintLabel::simple("dyn", None, None), text_edit: Some( config.lazy_text_edit(|| TextEdit::insert(range.start(), "dyn ".to_owned())), ), position: InlayHintPosition::Before, pad_left: false, pad_right: true, resolve_parent: Some(range), }); Some(()) } #[cfg(test)] mod tests { use expect_test::expect; use crate::inlay_hints::InlayHintsConfig; use crate::inlay_hints::tests::{DISABLED_CONFIG, check_edit, check_with_config}; #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { check_with_config( InlayHintsConfig { sized_bound: true, implied_dyn_trait_hints: true, ..DISABLED_CONFIG }, ra_fixture, ); } #[test] fn path_works() { check( r#" struct S {} trait T {} fn foo(_: T, _: dyn T, _: S) {} // ^ dyn fn foo(_: &T, _: for<'a> T) {} // ^ dyn // ^ dyn impl T {} // ^ dyn impl T for (T) {} // ^ dyn impl T for {} impl T "#, ); } #[test] fn missing_dyn_bounds() { check( r#" trait T {} fn foo( _: T + T, // ^^^^^ dyn _: T + 'a, // ^^^^^^ dyn _: 'a + T, // ^^^^^^ dyn _: &(T + T) // ^^^^^ dyn _: &mut (T + T) // ^^^^^ dyn _: *mut (T), // ^ dyn ) {} "#, ); } #[test] fn edit() { check_edit( InlayHintsConfig { implied_dyn_trait_hints: true, ..DISABLED_CONFIG }, r#" trait T {} fn foo( _: &mut T ) {} "#, expect![[r#" trait T {} fn foo( _: &mut dyn T ) {} "#]], ); } #[test] fn hrtb_bound_does_not_add_dyn() { check( r#" //- minicore: fn fn test(f: F) where F: for<'a> FnOnce(&'a i32) {} // ^: Sized "#, ); } #[test] fn with_parentheses() { check( r#" trait T {} fn foo(v: &(T)) {} // ^ dyn "#, ); } }