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::()?; 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 { + self.find_trait("core:marker:Sized") + } + pub fn core_future_Future(&self) -> Option { 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::>(); + 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, + 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() {} + // ^ : Sized +"#, + ); + } + + #[test] + fn with_colon() { + check( + r#" +fn foo() {} + // ^ Sized +"#, + ); + } + + #[test] + fn with_colon_and_bounds() { + check( + r#" +fn foo() {} + // ^ Sized + +"#, + ); + } + + #[test] + fn location_works() { + check_expect( + InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG }, + r#" +//- minicore: sized +fn foo() {} +"#, + 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, }; @@ -90,15 +90,6 @@ mod tests { check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture); } - #[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::>(); - expect.assert_debug_eq(&filtered) - } - #[track_caller] pub(super) fn check_expect_clear_loc( config: InlayHintsConfig, 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 @@ -2105,6 +2105,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": {