ide/inlay_hints/
implied_dyn_trait.rs

1//! Implementation of trait bound hints.
2//!
3//! Currently this renders the implied `Sized` bound.
4use 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                        // only show it for impl type if the impl is not incomplete, otherwise we
36                        // are likely typing a trait impl
37                        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}