Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #18903 from Veykril/push-mqmworppxuyw
Implement implicit sized bound inlay hints
Lukas Wirth 2025-01-10
parent 1b52a66 · parent d4fa92e · commit 67fd72d
-rw-r--r--crates/ide-assists/src/handlers/inline_macro.rs4
-rw-r--r--crates/ide-db/src/famous_defs.rs4
-rw-r--r--crates/ide/src/inlay_hints.rs13
-rw-r--r--crates/ide/src/inlay_hints/bounds.rs152
-rw-r--r--crates/ide/src/inlay_hints/chaining.rs11
-rw-r--r--crates/ide/src/static_index.rs1
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs3
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json10
10 files changed, 192 insertions, 12 deletions
diff --git a/crates/ide-assists/src/handlers/inline_macro.rs b/crates/ide-assists/src/handlers/inline_macro.rs
index df56f8904b..cd6f900ba1 100644
--- a/crates/ide-assists/src/handlers/inline_macro.rs
+++ b/crates/ide-assists/src/handlers/inline_macro.rs
@@ -38,8 +38,6 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
let macro_call = ctx.sema.to_def(&unexpanded)?;
- let expanded = ctx.sema.parse_or_expand(macro_call.as_file());
- let span_map = ctx.sema.db.expansion_span_map(macro_call.as_macro_file());
let target_crate_id = ctx.sema.file_to_module_def(ctx.file_id())?.krate().into();
let text_range = unexpanded.syntax().text_range();
@@ -48,6 +46,8 @@ pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
"Inline macro".to_owned(),
text_range,
|builder| {
+ let expanded = ctx.sema.parse_or_expand(macro_call.as_file());
+ let span_map = ctx.sema.db.expansion_span_map(macro_call.as_macro_file());
// Don't call `prettify_macro_expansion()` outside the actual assist action; it does some heavy rowan tree manipulation,
// which can be very costly for big macros when it is done *even without the assist being invoked*.
let expanded = prettify_macro_expansion(ctx.db(), expanded, &span_map, target_crate_id);
diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs
index ba6e50abf6..9e3506d6f5 100644
--- a/crates/ide-db/src/famous_defs.rs
+++ b/crates/ide-db/src/famous_defs.rs
@@ -106,6 +106,10 @@ impl FamousDefs<'_, '_> {
self.find_trait("core:marker:Copy")
}
+ pub fn core_marker_Sized(&self) -> Option<Trait> {
+ self.find_trait("core:marker:Sized")
+ }
+
pub fn core_future_Future(&self) -> Option<Trait> {
self.find_trait("core:future:Future")
}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 31b1774bb7..1c08e0b560 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -24,6 +24,7 @@ use crate::{navigation_target::TryToNav, FileId};
mod adjustment;
mod bind_pat;
mod binding_mode;
+mod bounds;
mod chaining;
mod closing_brace;
mod closure_captures;
@@ -264,6 +265,7 @@ fn hints(
ast::Type::PathType(path) => lifetime::fn_path_hints(hints, ctx, famous_defs, config, file_id, path),
_ => Some(()),
},
+ ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, file_id, it),
_ => Some(()),
}
};
@@ -273,6 +275,7 @@ fn hints(
pub struct InlayHintsConfig {
pub render_colons: bool,
pub type_hints: bool,
+ pub sized_bound: bool,
pub discriminant_hints: DiscriminantHints,
pub parameter_hints: bool,
pub generic_parameter_hints: GenericParameterHints,
@@ -760,6 +763,7 @@ mod tests {
render_colons: false,
type_hints: false,
parameter_hints: false,
+ sized_bound: false,
generic_parameter_hints: GenericParameterHints {
type_hints: false,
lifetime_hints: false,
@@ -814,6 +818,15 @@ mod tests {
assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}");
}
+ #[track_caller]
+ pub(super) fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
+ let filtered =
+ inlay_hints.into_iter().map(|hint| (hint.range, hint.label)).collect::<Vec<_>>();
+ expect.assert_debug_eq(&filtered)
+ }
+
/// Computes inlay hints for the fixture, applies all the provided text edits and then runs
/// expect test.
#[track_caller]
diff --git a/crates/ide/src/inlay_hints/bounds.rs b/crates/ide/src/inlay_hints/bounds.rs
new file mode 100644
index 0000000000..334f3ca631
--- /dev/null
+++ b/crates/ide/src/inlay_hints/bounds.rs
@@ -0,0 +1,152 @@
+//! Implementation of trait bound hints.
+//!
+//! Currently this renders the implied `Sized` bound.
+use ide_db::{famous_defs::FamousDefs, FileRange};
+
+use span::EditionedFileId;
+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,
+ _file_id: EditionedFileId,
+ params: ast::GenericParamList,
+) -> Option<()> {
+ if !config.sized_bound {
+ return None;
+ }
+
+ let linked_location =
+ famous_defs.core_marker_Sized().and_then(|it| 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() }
+ });
+
+ 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,
+ 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::{check_expect, check_with_config, DISABLED_CONFIG};
+
+ #[track_caller]
+ fn check(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(
+ FileRangeWrapper {
+ file_id: FileId(
+ 1,
+ ),
+ range: 135..140,
+ },
+ ),
+ tooltip: "",
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+ }
+}
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index 028ed1650f..8eeacb6e8d 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -81,7 +81,7 @@ mod tests {
use crate::{
fixture,
- inlay_hints::tests::{check_with_config, DISABLED_CONFIG, TEST_CONFIG},
+ inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG},
InlayHintsConfig,
};
@@ -91,15 +91,6 @@ mod tests {
}
#[track_caller]
- pub(super) fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
- let (analysis, file_id) = fixture::file(ra_fixture);
- let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
- let filtered =
- inlay_hints.into_iter().map(|hint| (hint.range, hint.label)).collect::<Vec<_>>();
- expect.assert_debug_eq(&filtered)
- }
-
- #[track_caller]
pub(super) fn check_expect_clear_loc(
config: InlayHintsConfig,
ra_fixture: &str,
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index 700e166b23..60d8259c53 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -138,6 +138,7 @@ impl StaticIndex<'_> {
render_colons: true,
discriminant_hints: crate::DiscriminantHints::Fieldless,
type_hints: true,
+ sized_bound: false,
parameter_hints: true,
generic_parameter_hints: crate::GenericParameterHints {
type_hints: false,
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index afe3455b78..bcaec52019 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -1051,6 +1051,7 @@ impl flags::AnalysisStats {
&InlayHintsConfig {
render_colons: false,
type_hints: true,
+ sized_bound: false,
discriminant_hints: ide::DiscriminantHints::Always,
parameter_hints: true,
generic_parameter_hints: ide::GenericParameterHints {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 9c5af5ff6a..72d021db5a 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -185,6 +185,8 @@ config_data! {
inlayHints_genericParameterHints_type_enable: bool = false,
/// Whether to show implicit drop hints.
inlayHints_implicitDrops_enable: bool = false,
+ /// Whether to show inlay hints for the implied type parameter `Sized` bound.
+ inlayHints_implicitSizedBoundHints_enable: bool = false,
/// Whether to show inlay type hints for elided lifetimes in function signatures.
inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = LifetimeElisionDef::Never,
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
@@ -1621,6 +1623,7 @@ impl Config {
InlayHintsConfig {
render_colons: self.inlayHints_renderColons().to_owned(),
type_hints: self.inlayHints_typeHints_enable().to_owned(),
+ sized_bound: self.inlayHints_implicitSizedBoundHints_enable().to_owned(),
parameter_hints: self.inlayHints_parameterHints_enable().to_owned(),
generic_parameter_hints: GenericParameterHints {
type_hints: self.inlayHints_genericParameterHints_type_enable().to_owned(),
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index acaf43b987..45eb38cd4f 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -716,6 +716,11 @@ Whether to show generic type parameter name inlay hints.
--
Whether to show implicit drop hints.
--
+[[rust-analyzer.inlayHints.implicitSizedBoundHints.enable]]rust-analyzer.inlayHints.implicitSizedBoundHints.enable (default: `false`)::
++
+--
+Whether to show inlay hints for the implied type parameter `Sized` bound.
+--
[[rust-analyzer.inlayHints.lifetimeElisionHints.enable]]rust-analyzer.inlayHints.lifetimeElisionHints.enable (default: `"never"`)::
+
--
diff --git a/editors/code/package.json b/editors/code/package.json
index 26cd49d9d2..76d85a661e 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -2108,6 +2108,16 @@
{
"title": "inlayHints",
"properties": {
+ "rust-analyzer.inlayHints.implicitSizedBoundHints.enable": {
+ "markdownDescription": "Whether to show inlay hints for the implied type parameter `Sized` bound.",
+ "default": false,
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "title": "inlayHints",
+ "properties": {
"rust-analyzer.inlayHints.lifetimeElisionHints.enable": {
"markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.",
"default": "never",