Skip to content

Commit 67fd72d

Browse files
authored
Merge pull request #18903 from Veykril/push-mqmworppxuyw
Implement implicit sized bound inlay hints
2 parents 1b52a66 + d4fa92e commit 67fd72d

File tree

10 files changed

+192
-12
lines changed

10 files changed

+192
-12
lines changed

crates/ide-assists/src/handlers/inline_macro.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
3838
pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
3939
let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
4040
let macro_call = ctx.sema.to_def(&unexpanded)?;
41-
let expanded = ctx.sema.parse_or_expand(macro_call.as_file());
42-
let span_map = ctx.sema.db.expansion_span_map(macro_call.as_macro_file());
4341
let target_crate_id = ctx.sema.file_to_module_def(ctx.file_id())?.krate().into();
4442
let text_range = unexpanded.syntax().text_range();
4543

@@ -48,6 +46,8 @@ pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
4846
"Inline macro".to_owned(),
4947
text_range,
5048
|builder| {
49+
let expanded = ctx.sema.parse_or_expand(macro_call.as_file());
50+
let span_map = ctx.sema.db.expansion_span_map(macro_call.as_macro_file());
5151
// Don't call `prettify_macro_expansion()` outside the actual assist action; it does some heavy rowan tree manipulation,
5252
// which can be very costly for big macros when it is done *even without the assist being invoked*.
5353
let expanded = prettify_macro_expansion(ctx.db(), expanded, &span_map, target_crate_id);

crates/ide-db/src/famous_defs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ impl FamousDefs<'_, '_> {
106106
self.find_trait("core:marker:Copy")
107107
}
108108

109+
pub fn core_marker_Sized(&self) -> Option<Trait> {
110+
self.find_trait("core:marker:Sized")
111+
}
112+
109113
pub fn core_future_Future(&self) -> Option<Trait> {
110114
self.find_trait("core:future:Future")
111115
}

crates/ide/src/inlay_hints.rs

+13
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::{navigation_target::TryToNav, FileId};
2424
mod adjustment;
2525
mod bind_pat;
2626
mod binding_mode;
27+
mod bounds;
2728
mod chaining;
2829
mod closing_brace;
2930
mod closure_captures;
@@ -264,6 +265,7 @@ fn hints(
264265
ast::Type::PathType(path) => lifetime::fn_path_hints(hints, ctx, famous_defs, config, file_id, path),
265266
_ => Some(()),
266267
},
268+
ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, file_id, it),
267269
_ => Some(()),
268270
}
269271
};
@@ -273,6 +275,7 @@ fn hints(
273275
pub struct InlayHintsConfig {
274276
pub render_colons: bool,
275277
pub type_hints: bool,
278+
pub sized_bound: bool,
276279
pub discriminant_hints: DiscriminantHints,
277280
pub parameter_hints: bool,
278281
pub generic_parameter_hints: GenericParameterHints,
@@ -760,6 +763,7 @@ mod tests {
760763
render_colons: false,
761764
type_hints: false,
762765
parameter_hints: false,
766+
sized_bound: false,
763767
generic_parameter_hints: GenericParameterHints {
764768
type_hints: false,
765769
lifetime_hints: false,
@@ -814,6 +818,15 @@ mod tests {
814818
assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}");
815819
}
816820

821+
#[track_caller]
822+
pub(super) fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
823+
let (analysis, file_id) = fixture::file(ra_fixture);
824+
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
825+
let filtered =
826+
inlay_hints.into_iter().map(|hint| (hint.range, hint.label)).collect::<Vec<_>>();
827+
expect.assert_debug_eq(&filtered)
828+
}
829+
817830
/// Computes inlay hints for the fixture, applies all the provided text edits and then runs
818831
/// expect test.
819832
#[track_caller]

crates/ide/src/inlay_hints/bounds.rs

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//! Implementation of trait bound hints.
2+
//!
3+
//! Currently this renders the implied `Sized` bound.
4+
use ide_db::{famous_defs::FamousDefs, FileRange};
5+
6+
use span::EditionedFileId;
7+
use syntax::ast::{self, AstNode, HasTypeBounds};
8+
9+
use crate::{
10+
InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,
11+
TryToNav,
12+
};
13+
14+
pub(super) fn hints(
15+
acc: &mut Vec<InlayHint>,
16+
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
17+
config: &InlayHintsConfig,
18+
_file_id: EditionedFileId,
19+
params: ast::GenericParamList,
20+
) -> Option<()> {
21+
if !config.sized_bound {
22+
return None;
23+
}
24+
25+
let linked_location =
26+
famous_defs.core_marker_Sized().and_then(|it| it.try_to_nav(sema.db)).map(|it| {
27+
let n = it.call_site();
28+
FileRange { file_id: n.file_id, range: n.focus_or_full_range() }
29+
});
30+
31+
for param in params.type_or_const_params() {
32+
match param {
33+
ast::TypeOrConstParam::Type(type_param) => {
34+
let c = type_param.colon_token().map(|it| it.text_range());
35+
let has_bounds =
36+
type_param.type_bound_list().is_some_and(|it| it.bounds().next().is_some());
37+
acc.push(InlayHint {
38+
range: c.unwrap_or_else(|| type_param.syntax().text_range()),
39+
kind: InlayKind::Type,
40+
label: {
41+
let mut hint = InlayHintLabel::default();
42+
if c.is_none() {
43+
hint.parts.push(InlayHintLabelPart {
44+
text: ": ".to_owned(),
45+
linked_location: None,
46+
tooltip: None,
47+
});
48+
}
49+
hint.parts.push(InlayHintLabelPart {
50+
text: "Sized".to_owned(),
51+
linked_location,
52+
tooltip: None,
53+
});
54+
if has_bounds {
55+
hint.parts.push(InlayHintLabelPart {
56+
text: " +".to_owned(),
57+
linked_location: None,
58+
tooltip: None,
59+
});
60+
}
61+
hint
62+
},
63+
text_edit: None,
64+
position: InlayHintPosition::After,
65+
pad_left: c.is_some(),
66+
pad_right: has_bounds,
67+
resolve_parent: Some(params.syntax().text_range()),
68+
});
69+
}
70+
ast::TypeOrConstParam::Const(_) => (),
71+
}
72+
}
73+
74+
Some(())
75+
}
76+
77+
#[cfg(test)]
78+
mod tests {
79+
use expect_test::expect;
80+
81+
use crate::inlay_hints::InlayHintsConfig;
82+
83+
use crate::inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG};
84+
85+
#[track_caller]
86+
fn check(ra_fixture: &str) {
87+
check_with_config(InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG }, ra_fixture);
88+
}
89+
90+
#[test]
91+
fn smoke() {
92+
check(
93+
r#"
94+
fn foo<T>() {}
95+
// ^ : Sized
96+
"#,
97+
);
98+
}
99+
100+
#[test]
101+
fn with_colon() {
102+
check(
103+
r#"
104+
fn foo<T:>() {}
105+
// ^ Sized
106+
"#,
107+
);
108+
}
109+
110+
#[test]
111+
fn with_colon_and_bounds() {
112+
check(
113+
r#"
114+
fn foo<T: 'static>() {}
115+
// ^ Sized +
116+
"#,
117+
);
118+
}
119+
120+
#[test]
121+
fn location_works() {
122+
check_expect(
123+
InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG },
124+
r#"
125+
//- minicore: sized
126+
fn foo<T>() {}
127+
"#,
128+
expect![[r#"
129+
[
130+
(
131+
7..8,
132+
[
133+
": ",
134+
InlayHintLabelPart {
135+
text: "Sized",
136+
linked_location: Some(
137+
FileRangeWrapper {
138+
file_id: FileId(
139+
1,
140+
),
141+
range: 135..140,
142+
},
143+
),
144+
tooltip: "",
145+
},
146+
],
147+
),
148+
]
149+
"#]],
150+
);
151+
}
152+
}

crates/ide/src/inlay_hints/chaining.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ mod tests {
8181

8282
use crate::{
8383
fixture,
84-
inlay_hints::tests::{check_with_config, DISABLED_CONFIG, TEST_CONFIG},
84+
inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG},
8585
InlayHintsConfig,
8686
};
8787

@@ -90,15 +90,6 @@ mod tests {
9090
check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
9191
}
9292

93-
#[track_caller]
94-
pub(super) fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
95-
let (analysis, file_id) = fixture::file(ra_fixture);
96-
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
97-
let filtered =
98-
inlay_hints.into_iter().map(|hint| (hint.range, hint.label)).collect::<Vec<_>>();
99-
expect.assert_debug_eq(&filtered)
100-
}
101-
10293
#[track_caller]
10394
pub(super) fn check_expect_clear_loc(
10495
config: InlayHintsConfig,

crates/ide/src/static_index.rs

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ impl StaticIndex<'_> {
138138
render_colons: true,
139139
discriminant_hints: crate::DiscriminantHints::Fieldless,
140140
type_hints: true,
141+
sized_bound: false,
141142
parameter_hints: true,
142143
generic_parameter_hints: crate::GenericParameterHints {
143144
type_hints: false,

crates/rust-analyzer/src/cli/analysis_stats.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,7 @@ impl flags::AnalysisStats {
10511051
&InlayHintsConfig {
10521052
render_colons: false,
10531053
type_hints: true,
1054+
sized_bound: false,
10541055
discriminant_hints: ide::DiscriminantHints::Always,
10551056
parameter_hints: true,
10561057
generic_parameter_hints: ide::GenericParameterHints {

crates/rust-analyzer/src/config.rs

+3
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ config_data! {
185185
inlayHints_genericParameterHints_type_enable: bool = false,
186186
/// Whether to show implicit drop hints.
187187
inlayHints_implicitDrops_enable: bool = false,
188+
/// Whether to show inlay hints for the implied type parameter `Sized` bound.
189+
inlayHints_implicitSizedBoundHints_enable: bool = false,
188190
/// Whether to show inlay type hints for elided lifetimes in function signatures.
189191
inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = LifetimeElisionDef::Never,
190192
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
@@ -1621,6 +1623,7 @@ impl Config {
16211623
InlayHintsConfig {
16221624
render_colons: self.inlayHints_renderColons().to_owned(),
16231625
type_hints: self.inlayHints_typeHints_enable().to_owned(),
1626+
sized_bound: self.inlayHints_implicitSizedBoundHints_enable().to_owned(),
16241627
parameter_hints: self.inlayHints_parameterHints_enable().to_owned(),
16251628
generic_parameter_hints: GenericParameterHints {
16261629
type_hints: self.inlayHints_genericParameterHints_type_enable().to_owned(),

docs/user/generated_config.adoc

+5
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,11 @@ Whether to show generic type parameter name inlay hints.
716716
--
717717
Whether to show implicit drop hints.
718718
--
719+
[[rust-analyzer.inlayHints.implicitSizedBoundHints.enable]]rust-analyzer.inlayHints.implicitSizedBoundHints.enable (default: `false`)::
720+
+
721+
--
722+
Whether to show inlay hints for the implied type parameter `Sized` bound.
723+
--
719724
[[rust-analyzer.inlayHints.lifetimeElisionHints.enable]]rust-analyzer.inlayHints.lifetimeElisionHints.enable (default: `"never"`)::
720725
+
721726
--

editors/code/package.json

+10
Original file line numberDiff line numberDiff line change
@@ -2105,6 +2105,16 @@
21052105
}
21062106
}
21072107
},
2108+
{
2109+
"title": "inlayHints",
2110+
"properties": {
2111+
"rust-analyzer.inlayHints.implicitSizedBoundHints.enable": {
2112+
"markdownDescription": "Whether to show inlay hints for the implied type parameter `Sized` bound.",
2113+
"default": false,
2114+
"type": "boolean"
2115+
}
2116+
}
2117+
},
21082118
{
21092119
"title": "inlayHints",
21102120
"properties": {

0 commit comments

Comments
 (0)