ide/inlay_hints/
implied_dyn_trait.rs1use either::Either;
5use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit};
6
7use syntax::ast::{self, AstNode};
8
9use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
10
11pub(super) fn hints(
12 acc: &mut Vec<InlayHint>,
13 FamousDefs(sema, _): &FamousDefs<'_, '_>,
14 config: &InlayHintsConfig<'_>,
15 path: Either<ast::PathType, ast::DynTraitType>,
16) -> Option<()> {
17 if !config.implied_dyn_trait_hints {
18 return None;
19 }
20
21 let parent = path.syntax().parent()?;
22 let range = match path {
23 Either::Left(path) => {
24 let paren = parent
25 .ancestors()
26 .take_while(|it| {
27 ast::ParenType::can_cast(it.kind()) || ast::ForType::can_cast(it.kind())
28 })
29 .last();
30 let parent = paren.as_ref().and_then(|it| it.parent()).unwrap_or(parent);
31 if ast::TypeBound::can_cast(parent.kind())
32 || ast::TypeAnchor::can_cast(parent.kind())
33 || ast::Impl::cast(parent).is_some_and(|it| {
34 it.trait_().map_or(
35 it.assoc_item_list().is_none_or(|it| it.l_curly_token().is_none()),
38 |trait_| trait_.syntax() == path.syntax(),
39 )
40 })
41 {
42 return None;
43 }
44 sema.resolve_trait(&path.path()?)?;
45 path.syntax().text_range()
46 }
47 Either::Right(dyn_) => {
48 if dyn_.dyn_token().is_some() {
49 return None;
50 }
51
52 dyn_.syntax().text_range()
53 }
54 };
55
56 acc.push(InlayHint {
57 range,
58 kind: InlayKind::Dyn,
59 label: InlayHintLabel::simple("dyn", None, None),
60 text_edit: Some(
61 config.lazy_text_edit(|| TextEdit::insert(range.start(), "dyn ".to_owned())),
62 ),
63 position: InlayHintPosition::Before,
64 pad_left: false,
65 pad_right: true,
66 resolve_parent: Some(range),
67 });
68
69 Some(())
70}
71
72#[cfg(test)]
73mod tests {
74
75 use expect_test::expect;
76
77 use crate::inlay_hints::InlayHintsConfig;
78
79 use crate::inlay_hints::tests::{DISABLED_CONFIG, check_edit, check_with_config};
80
81 #[track_caller]
82 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
83 check_with_config(
84 InlayHintsConfig {
85 sized_bound: true,
86 implied_dyn_trait_hints: true,
87 ..DISABLED_CONFIG
88 },
89 ra_fixture,
90 );
91 }
92
93 #[test]
94 fn path_works() {
95 check(
96 r#"
97struct S {}
98trait T {}
99fn foo(_: T, _: dyn T, _: S) {}
100 // ^ dyn
101fn foo(_: &T, _: for<'a> T) {}
102 // ^ dyn
103 // ^ dyn
104impl T {}
105 // ^ dyn
106impl T for (T) {}
107 // ^ dyn
108impl T for {}
109impl T
110"#,
111 );
112 }
113
114 #[test]
115 fn missing_dyn_bounds() {
116 check(
117 r#"
118trait T {}
119fn foo(
120 _: T + T,
121 // ^^^^^ dyn
122 _: T + 'a,
123 // ^^^^^^ dyn
124 _: 'a + T,
125 // ^^^^^^ dyn
126 _: &(T + T)
127 // ^^^^^ dyn
128 _: &mut (T + T)
129 // ^^^^^ dyn
130 _: *mut (T),
131 // ^ dyn
132) {}
133"#,
134 );
135 }
136
137 #[test]
138 fn edit() {
139 check_edit(
140 InlayHintsConfig { implied_dyn_trait_hints: true, ..DISABLED_CONFIG },
141 r#"
142trait T {}
143fn foo(
144 _: &mut T
145) {}
146"#,
147 expect![[r#"
148 trait T {}
149 fn foo(
150 _: &mut dyn T
151 ) {}
152 "#]],
153 );
154 }
155
156 #[test]
157 fn hrtb_bound_does_not_add_dyn() {
158 check(
159 r#"
160//- minicore: fn
161fn test<F>(f: F) where F: for<'a> FnOnce(&'a i32) {}
162 // ^: Sized
163 "#,
164 );
165 }
166
167 #[test]
168 fn with_parentheses() {
169 check(
170 r#"
171trait T {}
172fn foo(v: &(T)) {}
173 // ^ dyn
174 "#,
175 );
176 }
177}