diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 3f10bed511..944951f26a 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -29,6 +29,7 @@ mod closure_captures; mod closure_ret; mod discriminant; mod fn_lifetime_fn; +mod generic_param; mod implicit_drop; mod implicit_static; mod param_name; @@ -40,6 +41,7 @@ pub struct InlayHintsConfig { pub type_hints: bool, pub discriminant_hints: DiscriminantHints, pub parameter_hints: bool, + pub generic_parameter_hints: GenericParameterHints, pub chaining_hints: bool, pub adjustment_hints: AdjustmentHints, pub adjustment_hints_mode: AdjustmentHintsMode, @@ -94,6 +96,13 @@ pub enum DiscriminantHints { Fieldless, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GenericParameterHints { + pub type_hints: bool, + pub lifetime_hints: bool, + pub const_hints: bool, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum LifetimeElisionHints { Always, @@ -127,6 +136,7 @@ pub enum InlayKind { GenericParamList, Lifetime, Parameter, + GenericParameter, Type, Drop, RangeExclusive, @@ -447,6 +457,7 @@ fn ty_to_text_edit( // // * types of local variables // * names of function arguments +// * names of const generic parameters // * types of chained expressions // // Optionally, one can enable additional hints for @@ -454,6 +465,7 @@ fn ty_to_text_edit( // * return types of closure expressions // * elided lifetimes // * compiler inserted reborrows +// * names of generic type and lifetime parameters // // Note: inlay hints for function argument names are heuristically omitted to reduce noise and will not appear if // any of the @@ -543,6 +555,9 @@ fn hints( node: SyntaxNode, ) { closing_brace::hints(hints, sema, config, file_id, node.clone()); + if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) { + generic_param::hints(hints, sema, config, any_has_generic_args); + } match_ast! { match node { ast::Expr(expr) => { @@ -645,13 +660,18 @@ mod tests { use crate::DiscriminantHints; use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints}; - use super::{ClosureReturnTypeHints, InlayFieldsToResolve}; + use super::{ClosureReturnTypeHints, GenericParameterHints, InlayFieldsToResolve}; pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { discriminant_hints: DiscriminantHints::Never, render_colons: false, type_hints: false, parameter_hints: false, + generic_parameter_hints: GenericParameterHints { + type_hints: false, + lifetime_hints: false, + const_hints: false, + }, chaining_hints: false, lifetime_elision_hints: LifetimeElisionHints::Never, closure_return_type_hints: ClosureReturnTypeHints::Never, diff --git a/crates/ide/src/inlay_hints/generic_param.rs b/crates/ide/src/inlay_hints/generic_param.rs new file mode 100644 index 0000000000..51855eeae2 --- /dev/null +++ b/crates/ide/src/inlay_hints/generic_param.rs @@ -0,0 +1,315 @@ +//! Implementation of inlay hints for generic parameters. +use ide_db::{active_parameter::generic_def_for_node, RootDatabase}; +use syntax::{ + ast::{self, AnyHasGenericArgs, HasGenericArgs, HasName}, + AstNode, +}; + +use crate::{inlay_hints::GenericParameterHints, InlayHint, InlayHintsConfig, InlayKind}; + +use super::param_name::{is_argument_similar_to_param_name, render_label}; + +pub(crate) fn hints( + acc: &mut Vec, + sema: &hir::Semantics<'_, RootDatabase>, + config: &InlayHintsConfig, + node: AnyHasGenericArgs, +) -> Option<()> { + let GenericParameterHints { type_hints, lifetime_hints, const_hints } = + config.generic_parameter_hints; + if !(type_hints || lifetime_hints || const_hints) { + return None; + } + + let generic_arg_list = node.generic_arg_list()?; + + let (generic_def, _, _, _) = + generic_def_for_node(sema, &generic_arg_list, &node.syntax().first_token()?)?; + + let mut args = generic_arg_list.generic_args().peekable(); + let start_with_lifetime = matches!(args.peek()?, ast::GenericArg::LifetimeArg(_)); + let params = generic_def.params(sema.db).into_iter().filter(|p| { + if let hir::GenericParam::TypeParam(it) = p { + if it.is_implicit(sema.db) { + return false; + } + } + if !start_with_lifetime { + return !matches!(p, hir::GenericParam::LifetimeParam(_)); + } + true + }); + + let hints = params.zip(args).filter_map(|(param, arg)| { + if matches!(arg, ast::GenericArg::AssocTypeArg(_)) { + return None; + } + + let name = param.name(sema.db); + let param_name = name.as_str()?; + + let should_hide = { + let argument = get_string_representation(&arg)?; + is_argument_similar_to_param_name(&argument, param_name) + }; + + if should_hide { + return None; + } + + let range = sema.original_range_opt(arg.syntax())?.range; + + let source_syntax = match param { + hir::GenericParam::TypeParam(it) => { + if !type_hints || !matches!(arg, ast::GenericArg::TypeArg(_)) { + return None; + } + sema.source(it.merge())?.value.syntax().clone() + } + hir::GenericParam::ConstParam(it) => { + if !const_hints || !matches!(arg, ast::GenericArg::ConstArg(_)) { + return None; + } + let syntax = sema.source(it.merge())?.value.syntax().clone(); + let const_param = ast::ConstParam::cast(syntax)?; + const_param.name()?.syntax().clone() + } + hir::GenericParam::LifetimeParam(it) => { + if !lifetime_hints || !matches!(arg, ast::GenericArg::LifetimeArg(_)) { + return None; + } + sema.source(it)?.value.syntax().clone() + } + }; + let linked_location = sema.original_range_opt(&source_syntax); + let label = render_label(param_name, config, linked_location); + + Some(InlayHint { + range, + position: crate::InlayHintPosition::Before, + pad_left: false, + pad_right: true, + kind: InlayKind::GenericParameter, + label, + text_edit: None, + }) + }); + + acc.extend(hints); + Some(()) +} + +fn get_string_representation(arg: &ast::GenericArg) -> Option { + return match arg { + ast::GenericArg::AssocTypeArg(_) => None, + ast::GenericArg::ConstArg(const_arg) => Some(const_arg.to_string()), + ast::GenericArg::LifetimeArg(lifetime_arg) => { + let lifetime = lifetime_arg.lifetime()?; + Some(lifetime.to_string()) + } + ast::GenericArg::TypeArg(type_arg) => { + let ty = type_arg.ty()?; + Some( + type_path_segment(&ty) + .map_or_else(|| type_arg.to_string(), |segment| segment.to_string()), + ) + } + }; + + fn type_path_segment(ty: &ast::Type) -> Option { + match ty { + ast::Type::ArrayType(it) => type_path_segment(&it.ty()?), + ast::Type::ForType(it) => type_path_segment(&it.ty()?), + ast::Type::ParenType(it) => type_path_segment(&it.ty()?), + ast::Type::PathType(path_type) => path_type.path()?.segment(), + ast::Type::PtrType(it) => type_path_segment(&it.ty()?), + ast::Type::RefType(it) => type_path_segment(&it.ty()?), + ast::Type::SliceType(it) => type_path_segment(&it.ty()?), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + inlay_hints::{ + tests::{check_with_config, DISABLED_CONFIG}, + GenericParameterHints, + }, + InlayHintsConfig, + }; + + #[track_caller] + fn generic_param_name_hints_always(ra_fixture: &str) { + check_with_config( + InlayHintsConfig { + generic_parameter_hints: GenericParameterHints { + type_hints: true, + lifetime_hints: true, + const_hints: true, + }, + ..DISABLED_CONFIG + }, + ra_fixture, + ); + } + + #[track_caller] + fn generic_param_name_hints_const_only(ra_fixture: &str) { + check_with_config( + InlayHintsConfig { + generic_parameter_hints: GenericParameterHints { + type_hints: false, + lifetime_hints: false, + const_hints: true, + }, + ..DISABLED_CONFIG + }, + ra_fixture, + ); + } + + #[test] + fn type_only() { + generic_param_name_hints_always( + r#" +struct A { + x: X, + y: Y, +} + +fn foo(a: A) {} + //^^^^^ X ^^^ Y +"#, + ) + } + + #[test] + fn lifetime_and_type() { + generic_param_name_hints_always( + r#" +struct A<'a, X> { + x: &'a X +} + +fn foo<'b>(a: A<'b, u32>) {} + //^^ 'a^^^ X +"#, + ) + } + + #[test] + fn omit_lifetime() { + generic_param_name_hints_always( + r#" +struct A<'a, X> { + x: &'a X +} + +fn foo() { + let x: i32 = 1; + let a: A = A { x: &x }; + // ^^^ X +} +"#, + ) + } + + #[test] + fn const_only() { + generic_param_name_hints_always( + r#" +struct A {}; + +fn foo(a: A<12, 2>) {} + //^^ X^ Y +"#, + ) + } + + #[test] + fn lifetime_and_type_and_const() { + generic_param_name_hints_always( + r#" +struct A<'a, X, const LEN: usize> { + x: &'a [X; LEN], +} + +fn foo<'b>(a: A< + 'b, + // ^^ 'a + u32, + // ^^^ X + 3 + // ^ LEN + >) {} +"#, + ) + } + + #[test] + fn const_only_config() { + generic_param_name_hints_const_only( + r#" +struct A<'a, X, const LEN: usize> { + x: &'a [X; LEN], +} + +fn foo<'b>(a: A< + 'b, + u32, + 3 + // ^ LEN + >) {} +"#, + ) + } + + #[test] + fn assoc_type() { + generic_param_name_hints_always( + r#" +trait Trait { + type Assoc1; + type Assoc2; +} + +fn foo() -> impl Trait {} + // ^^^ T +"#, + ) + } + + #[test] + fn hide_similar() { + generic_param_name_hints_always( + r#" +struct A<'a, X, const N: usize> { + x: &'a [X; N], +} + +const N: usize = 3; + +mod m { + type X = u32; +} + +fn foo<'a>(a: A<'a, m::X, N>) {} +"#, + ) + } + + #[test] + fn mismatching_args() { + generic_param_name_hints_always( + r#" +struct A { + x: [X; N] +} + +type InvalidType = A<3, i32>; +"#, + ) + } +} diff --git a/crates/ide/src/inlay_hints/param_name.rs b/crates/ide/src/inlay_hints/param_name.rs index 9819d0e3fb..2e2a64c552 100644 --- a/crates/ide/src/inlay_hints/param_name.rs +++ b/crates/ide/src/inlay_hints/param_name.rs @@ -3,6 +3,8 @@ //! fn max(x: i32, y: i32) -> i32 { x + y } //! _ = max(/*x*/4, /*y*/4); //! ``` +use std::fmt::Display; + use either::Either; use hir::{Callable, Semantics}; use ide_db::{base_db::FileRange, RootDatabase}; @@ -46,9 +48,7 @@ pub(super) fn hints( .map(|(param, param_name, _, FileRange { range, .. })| { let linked_location = param.and_then(|name| sema.original_range_opt(name.syntax())); - let colon = if config.render_colons { ":" } else { "" }; - let label = - InlayHintLabel::simple(format!("{param_name}{colon}"), None, linked_location); + let label = render_label(¶m_name, config, linked_location); InlayHint { range, kind: InlayKind::Parameter, @@ -64,6 +64,16 @@ pub(super) fn hints( Some(()) } +pub(super) fn render_label( + param_name: impl Display, + config: &InlayHintsConfig, + linked_location: Option, +) -> InlayHintLabel { + let colon = if config.render_colons { ":" } else { "" }; + + InlayHintLabel::simple(format!("{param_name}{colon}"), None, linked_location) +} + fn get_callable( sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr, @@ -113,7 +123,7 @@ fn should_hide_param_name_hint( }; let fn_name = fn_name.as_deref(); is_param_name_suffix_of_fn_name(param_name, callable, fn_name) - || is_argument_similar_to_param_name(argument, param_name) + || is_argument_expr_similar_to_param_name(argument, param_name) || param_name.starts_with("ra_fixture") || (callable.n_params() == 1 && is_obvious_param(param_name)) || is_adt_constructor_similar_to_param_name(sema, argument, param_name) @@ -143,14 +153,17 @@ fn is_param_name_suffix_of_fn_name( } } -fn is_argument_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool { - // check whether param_name and argument are the same or - // whether param_name is a prefix/suffix of argument(split at `_`) +fn is_argument_expr_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool { let argument = match get_string_representation(argument) { Some(argument) => argument, None => return false, }; + is_argument_similar_to_param_name(&argument, param_name) +} +/// Check whether param_name and argument are the same or +/// whether param_name is a prefix/suffix of argument(split at `_`). +pub(super) fn is_argument_similar_to_param_name(argument: &str, param_name: &str) -> bool { // std is honestly too panic happy... let str_split_at = |str: &str, at| str.is_char_boundary(at).then(|| argument.split_at(at)); diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 28ae0dce98..f0b35903f3 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -89,8 +89,8 @@ pub use crate::{ }, inlay_hints::{ AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, - InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, - InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, + GenericParameterHints, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, + InlayHintPosition, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, }, join_lines::JoinLinesConfig, markup::Markup, diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 0d2311b6e9..5eb5c87f13 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -131,6 +131,11 @@ impl StaticIndex<'_> { discriminant_hints: crate::DiscriminantHints::Fieldless, type_hints: true, parameter_hints: true, + generic_parameter_hints: crate::GenericParameterHints { + type_hints: false, + lifetime_hints: false, + const_hints: true, + }, chaining_hints: true, closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock, lifetime_elision_hints: crate::LifetimeElisionHints::Never, diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 16a880e18b..90316f3b89 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -1008,6 +1008,11 @@ impl flags::AnalysisStats { type_hints: true, discriminant_hints: ide::DiscriminantHints::Always, parameter_hints: true, + generic_parameter_hints: ide::GenericParameterHints { + type_hints: true, + lifetime_hints: true, + const_hints: true, + }, chaining_hints: true, adjustment_hints: ide::AdjustmentHints::Always, adjustment_hints_mode: ide::AdjustmentHintsMode::Postfix, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 94e0115862..4c919b5876 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -10,9 +10,9 @@ use dirs::config_dir; use flycheck::{CargoOptions, FlycheckConfig}; use ide::{ AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, - HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, - InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, - Snippet, SnippetScope, SourceRootId, + GenericParameterHints, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, + InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, + MemoryLayoutHoverRenderKind, Snippet, SnippetScope, SourceRootId, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, @@ -509,6 +509,12 @@ config_data! { inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = false, /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = AdjustmentHintsModeDef::Prefix, + /// Whether to show const generic parameter name inlay hints. + inlayHints_genericParameterHints_const_enable: bool= false, + /// Whether to show generic lifetime parameter name inlay hints. + inlayHints_genericParameterHints_lifetime_enable: bool = true, + /// Whether to show generic type parameter name inlay hints. + inlayHints_genericParameterHints_type_enable: bool = false, /// Whether to show implicit drop hints. inlayHints_implicitDrops_enable: bool = false, /// Whether to show inlay type hints for elided lifetimes in function signatures. @@ -1391,6 +1397,11 @@ impl Config { render_colons: self.inlayHints_renderColons().to_owned(), type_hints: self.inlayHints_typeHints_enable().to_owned(), parameter_hints: self.inlayHints_parameterHints_enable().to_owned(), + generic_parameter_hints: GenericParameterHints { + type_hints: self.inlayHints_genericParameterHints_type_enable().to_owned(), + lifetime_hints: self.inlayHints_genericParameterHints_lifetime_enable().to_owned(), + const_hints: self.inlayHints_genericParameterHints_const_enable().to_owned(), + }, chaining_hints: self.inlayHints_chainingHints_enable().to_owned(), discriminant_hints: match self.inlayHints_discriminantHints_enable() { DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, @@ -2874,6 +2885,19 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "Only show discriminant hints on fieldless enum variants." ] }, + "GenericParameterHintsDef" => set! { + "type": "string", + "enum": [ + "always", + "never", + "const_only" + ], + "enumDescriptions": [ + "Always show generic parameter hints.", + "Never show generic parameter hints.", + "Only show const generic parameter hints." + ] + }, "AdjustmentHintsModeDef" => set! { "type": "string", "enum": [ diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index c82ce110d1..de394d3d11 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -501,7 +501,9 @@ pub(crate) fn inlay_hint( padding_left: Some(inlay_hint.pad_left), padding_right: Some(inlay_hint.pad_right), kind: match inlay_hint.kind { - InlayKind::Parameter => Some(lsp_types::InlayHintKind::PARAMETER), + InlayKind::Parameter | InlayKind::GenericParameter => { + Some(lsp_types::InlayHintKind::PARAMETER) + } InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE), _ => None, }, diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index a95c897991..edb95abdb8 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -655,6 +655,21 @@ Whether to hide inlay hints for type adjustments outside of `unsafe` blocks. -- Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). -- +[[rust-analyzer.inlayHints.genericParameterHints.const.enable]]rust-analyzer.inlayHints.genericParameterHints.const.enable (default: `false`):: ++ +-- +Whether to show const generic parameter name inlay hints. +-- +[[rust-analyzer.inlayHints.genericParameterHints.lifetime.enable]]rust-analyzer.inlayHints.genericParameterHints.lifetime.enable (default: `true`):: ++ +-- +Whether to show generic lifetime parameter name inlay hints. +-- +[[rust-analyzer.inlayHints.genericParameterHints.type.enable]]rust-analyzer.inlayHints.genericParameterHints.type.enable (default: `false`):: ++ +-- +Whether to show generic type parameter name inlay hints. +-- [[rust-analyzer.inlayHints.implicitDrops.enable]]rust-analyzer.inlayHints.implicitDrops.enable (default: `false`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 1fec6f621d..a06ca8d8f6 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1909,6 +1909,36 @@ } } }, + { + "title": "inlayHints", + "properties": { + "rust-analyzer.inlayHints.genericParameterHints.const.enable": { + "markdownDescription": "Whether to show const generic parameter name inlay hints.", + "default": false, + "type": "boolean" + } + } + }, + { + "title": "inlayHints", + "properties": { + "rust-analyzer.inlayHints.genericParameterHints.lifetime.enable": { + "markdownDescription": "Whether to show generic lifetime parameter name inlay hints.", + "default": true, + "type": "boolean" + } + } + }, + { + "title": "inlayHints", + "properties": { + "rust-analyzer.inlayHints.genericParameterHints.type.enable": { + "markdownDescription": "Whether to show generic type parameter name inlay hints.", + "default": false, + "type": "boolean" + } + } + }, { "title": "inlayHints", "properties": {