diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index d04bd87b7b..13e7a0ac88 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -1,10 +1,13 @@ use either::Either; use hir::{known, Callable, HasVisibility, HirDisplay, Semantics, TypeInfo}; -use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase}; +use ide_db::{ + base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, RootDatabase, +}; use itertools::Itertools; +use rustc_hash::FxHashMap; use stdx::to_lower_snake_case; use syntax::{ - ast::{self, AstNode, HasArgList, HasName, UnaryOp}, + ast::{self, AstNode, HasArgList, HasGenericParams, HasName, UnaryOp}, match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, TextRange, T, }; @@ -17,16 +20,27 @@ pub struct InlayHintsConfig { pub parameter_hints: bool, pub chaining_hints: bool, pub closure_return_type_hints: bool, + pub lifetime_elision_hints: LifetimeElisionHints, + pub param_names_for_lifetime_elision_hints: bool, pub hide_named_constructor_hints: bool, pub max_length: Option, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LifetimeElisionHints { + Always, + SkipTrivial, + Never, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum InlayKind { TypeHint, ParameterHint, ClosureReturnTypeHint, ChainingHint, + GenericParamListHint, + LifetimeHint, } #[derive(Debug)] @@ -41,12 +55,17 @@ pub struct InlayHint { // rust-analyzer shows additional information inline with the source code. // Editors usually render this using read-only virtual text snippets interspersed with code. // -// rust-analyzer shows hints for +// rust-analyzer by default shows hints for // // * types of local variables // * names of function arguments // * types of chained expressions // +// Optionally, one can enable additional hints for +// +// * return types of closure expressions with blocks +// * elided lifetimes +// // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. @@ -108,11 +127,212 @@ fn hints( } _ => (), } - } else if let Some(it) = ast::IdentPat::cast(node) { + } else if let Some(it) = ast::IdentPat::cast(node.clone()) { bind_pat_hints(hints, sema, config, &it); + } else if let Some(it) = ast::Fn::cast(node) { + lifetime_hints(hints, config, it); } } +fn lifetime_hints( + acc: &mut Vec, + config: &InlayHintsConfig, + func: ast::Fn, +) -> Option<()> { + if config.lifetime_elision_hints == LifetimeElisionHints::Never { + return None; + } + let param_list = func.param_list()?; + let generic_param_list = func.generic_param_list(); + let ret_type = func.ret_type(); + let self_param = param_list.self_param().filter(|it| it.amp_token().is_some()); + + let mut used_names: FxHashMap = generic_param_list + .iter() + .filter(|_| config.param_names_for_lifetime_elision_hints) + .flat_map(|gpl| gpl.lifetime_params()) + .filter_map(|param| param.lifetime()) + .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0))) + .collect(); + + let mut allocated_lifetimes = vec![]; + let mut gen_idx_name = { + let mut gen = (0u8..).map(|idx| match idx { + idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]), + idx => format!("'{idx}").into(), + }); + move || gen.next().unwrap_or_default() + }; + + let mut potential_lt_refs: Vec<_> = vec![]; + param_list + .params() + .filter_map(|it| { + Some(( + config.param_names_for_lifetime_elision_hints.then(|| it.pat()).flatten(), + it.ty()?, + )) + }) + .for_each(|(pat, ty)| { + // FIXME: check path types + walk_ty(&ty, &mut |ty| match ty { + ast::Type::RefType(r) => potential_lt_refs.push(( + pat.as_ref().and_then(|it| match it { + ast::Pat::IdentPat(p) => p.name(), + _ => None, + }), + r, + )), + _ => (), + }) + }); + + enum LifetimeKind { + Elided, + Named(SmolStr), + Static, + } + + let fetch_lt_text = |lt: Option| match lt { + Some(lt) => match lt.text().as_str() { + "'_" => LifetimeKind::Elided, + "'static" => LifetimeKind::Static, + name => LifetimeKind::Named(name.into()), + }, + None => LifetimeKind::Elided, + }; + let is_elided = |lt: Option| match lt { + Some(lt) => matches!(lt.text().as_str(), "'_"), + None => true, + }; + + // allocate names + if let Some(self_param) = &self_param { + if is_elided(self_param.lifetime()) { + allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { + // self can't be used as a lifetime, so no need to check for collisions + "'self".into() + } else { + gen_idx_name() + }); + } + } + potential_lt_refs.iter().for_each(|(name, it)| { + if is_elided(it.lifetime()) { + let name = match name { + Some(it) => { + if let Some(c) = used_names.get_mut(it.text().as_str()) { + *c += 1; + SmolStr::from(format!("'{text}{c}", text = it.text().as_str())) + } else { + used_names.insert(it.text().as_str().into(), 0); + SmolStr::from_iter(["\'", it.text().as_str()]) + } + } + _ => gen_idx_name(), + }; + allocated_lifetimes.push(name); + } + }); + + // fetch output lifetime if elision rule applies + + let output = if let Some(self_param) = &self_param { + match fetch_lt_text(self_param.lifetime()) { + LifetimeKind::Elided => allocated_lifetimes.get(0).cloned(), + LifetimeKind::Named(name) => Some(name), + LifetimeKind::Static => None, + } + } else { + match potential_lt_refs.as_slice() { + [(_, r)] => match fetch_lt_text(r.lifetime()) { + LifetimeKind::Elided => allocated_lifetimes.get(0).cloned(), + LifetimeKind::Named(name) => Some(name), + LifetimeKind::Static => None, + }, + [..] => None, + } + }; + + if allocated_lifetimes.is_empty() && output.is_none() { + return None; + } + + // apply hints + // apply output if required + let mut is_trivial = true; + if let (Some(output_lt), Some(r)) = (&output, ret_type) { + if let Some(ty) = r.ty() { + walk_ty(&ty, &mut |ty| match ty { + ast::Type::RefType(ty) if ty.lifetime().is_none() => { + if let Some(amp) = ty.amp_token() { + is_trivial = false; + acc.push(InlayHint { + range: amp.text_range(), + kind: InlayKind::LifetimeHint, + label: output_lt.clone(), + }); + } + } + _ => (), + }) + } + } + + if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial { + return None; + } + + let mut idx = match &self_param { + Some(self_param) if is_elided(self_param.lifetime()) => { + if let Some(amp) = self_param.amp_token() { + let lt = allocated_lifetimes[0].clone(); + acc.push(InlayHint { + range: amp.text_range(), + kind: InlayKind::LifetimeHint, + label: lt, + }); + } + 1 + } + _ => 0, + }; + + for (_, p) in potential_lt_refs.iter() { + if is_elided(p.lifetime()) { + let t = p.amp_token()?; + let lt = allocated_lifetimes[idx].clone(); + acc.push(InlayHint { range: t.text_range(), kind: InlayKind::LifetimeHint, label: lt }); + idx += 1; + } + } + + // generate generic param list things + match (generic_param_list, allocated_lifetimes.as_slice()) { + (_, []) => (), + (Some(gpl), allocated_lifetimes) => { + let angle_tok = gpl.l_angle_token()?; + let is_empty = gpl.generic_params().next().is_none(); + acc.push(InlayHint { + range: angle_tok.text_range(), + kind: InlayKind::GenericParamListHint, + label: format!( + "{}{}", + allocated_lifetimes.iter().format(", "), + if is_empty { "" } else { ", " } + ) + .into(), + }); + } + (None, allocated_lifetimes) => acc.push(InlayHint { + range: func.name()?.syntax().text_range(), + kind: InlayKind::GenericParamListHint, + label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(), + }), + } + Some(()) +} + fn closure_ret_hints( acc: &mut Vec, sema: &Semantics, @@ -600,18 +820,21 @@ fn get_callable( mod tests { use expect_test::{expect, Expect}; use ide_db::base_db::FileRange; + use itertools::Itertools; use syntax::{TextRange, TextSize}; use test_utils::extract_annotations; - use crate::{fixture, inlay_hints::InlayHintsConfig}; + use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints}; const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { render_colons: false, type_hints: false, parameter_hints: false, chaining_hints: false, + lifetime_elision_hints: LifetimeElisionHints::Never, hide_named_constructor_hints: false, closure_return_type_hints: false, + param_names_for_lifetime_elision_hints: false, max_length: None, }; const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { @@ -619,6 +842,7 @@ mod tests { parameter_hints: true, chaining_hints: true, closure_return_type_hints: true, + lifetime_elision_hints: LifetimeElisionHints::Always, ..DISABLED_CONFIG }; @@ -648,10 +872,15 @@ mod tests { #[track_caller] fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { let (analysis, file_id) = fixture::file(ra_fixture); - let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); + let mut expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); - let actual = - inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::>(); + let actual = inlay_hints + .into_iter() + .map(|it| (it.range, it.label.to_string())) + .sorted_by_key(|(range, _)| range.start()) + .collect::>(); + expected.sort_by_key(|(range, _)| range.start()); + assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual); } @@ -1289,41 +1518,6 @@ fn main() { ); } - #[test] - fn incomplete_for_no_hint() { - check_types( - r#" -fn main() { - let data = &[1i32, 2, 3]; - //^^^^ &[i32; 3] - for i -}"#, - ); - check( - r#" -pub struct Vec {} - -impl Vec { - pub fn new() -> Self { Vec {} } - pub fn push(&mut self, t: T) {} -} - -impl IntoIterator for Vec { - type Item=T; -} - -fn main() { - let mut data = Vec::new(); - //^^^^ Vec<&str> - data.push("foo"); - for i in - - println!("Unit expr"); -} -"#, - ); - } - #[test] fn complete_for_hint() { check_types( @@ -1825,4 +2019,103 @@ fn main() { "#]], ); } + + #[test] + fn hints_lifetimes() { + check( + r#" +fn empty() {} + +fn no_gpl(a: &()) {} + //^^^^^^<'0> + // ^'0 +fn empty_gpl<>(a: &()) {} + // ^'0 ^'0 +fn partial<'b>(a: &(), b: &'b ()) {} +// ^'0, $ ^'0 +fn partial<'a>(a: &'a (), b: &()) {} +// ^'0, $ ^'0 + +fn single_ret(a: &()) -> &() {} +// ^^^^^^^^^^<'0> + // ^'0 ^'0 +fn full_mul(a: &(), b: &()) {} +// ^^^^^^^^<'0, '1> + // ^'0 ^'1 + +fn foo<'c>(a: &'c ()) -> &() {} + // ^'c + +fn nested_in(a: & &X< &()>) {} +// ^^^^^^^^^<'0, '1, '2> + //^'0 ^'1 ^'2 +fn nested_out(a: &()) -> & &X< &()>{} +// ^^^^^^^^^^<'0> + //^'0 ^'0 ^'0 ^'0 + +impl () { + fn foo(&self) {} + // ^^^<'0> + // ^'0 + fn foo(&self) -> &() {} + // ^^^<'0> + // ^'0 ^'0 + fn foo(&self, a: &()) -> &() {} + // ^^^<'0, '1> + // ^'0 ^'1 ^'0 +} +"#, + ); + } + + #[test] + fn hints_lifetimes_named() { + check_with_config( + InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, + r#" +fn nested_in<'named>(named: & &X< &()>) {} +// ^'named1, 'named2, 'named3, $ + //^'named1 ^'named2 ^'named3 +"#, + ); + } + + #[test] + fn hints_lifetimes_trivial_skip() { + check_with_config( + InlayHintsConfig { + lifetime_elision_hints: LifetimeElisionHints::SkipTrivial, + ..TEST_CONFIG + }, + r#" +fn no_gpl(a: &()) {} +fn empty_gpl<>(a: &()) {} +fn partial<'b>(a: &(), b: &'b ()) {} +fn partial<'a>(a: &'a (), b: &()) {} + +fn single_ret(a: &()) -> &() {} +// ^^^^^^^^^^<'0> + // ^'0 ^'0 +fn full_mul(a: &(), b: &()) {} + +fn foo<'c>(a: &'c ()) -> &() {} + // ^'c + +fn nested_in(a: & &X< &()>) {} +fn nested_out(a: &()) -> & &X< &()>{} +// ^^^^^^^^^^<'0> + //^'0 ^'0 ^'0 ^'0 + +impl () { + fn foo(&self) {} + fn foo(&self) -> &() {} + // ^^^<'0> + // ^'0 ^'0 + fn foo(&self, a: &()) -> &() {} + // ^^^<'0, '1> + // ^'0 ^'1 ^'0 +} +"#, + ); + } } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 3e411e236b..7fcb09b5e2 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -80,7 +80,7 @@ pub use crate::{ folding_ranges::{Fold, FoldKind}, highlight_related::{HighlightRelatedConfig, HighlightedRange}, hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, - inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, + inlay_hints::{InlayHint, InlayHintsConfig, InlayKind, LifetimeElisionHints}, join_lines::JoinLinesConfig, markup::Markup, moniker::{MonikerKind, MonikerResult, PackageInformation}, diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index a06793cf51..3c81bfa3a9 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -12,11 +12,14 @@ use ide_db::{ use rustc_hash::FxHashSet; use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T}; -use crate::moniker::{crate_for_file, def_to_moniker, MonikerResult}; use crate::{ hover::hover_for_definition, Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig, TryToNav, }; +use crate::{ + moniker::{crate_for_file, def_to_moniker, MonikerResult}, + LifetimeElisionHints, +}; /// A static representation of fully analyzed source code. /// @@ -110,7 +113,9 @@ impl StaticIndex<'_> { parameter_hints: true, chaining_hints: true, closure_return_type_hints: true, + lifetime_elision_hints: LifetimeElisionHints::Never, hide_named_constructor_hints: false, + param_names_for_lifetime_elision_hints: false, max_length: Some(25), }, file_id, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3d5737316b..3afbeac47c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -12,7 +12,8 @@ use std::{ffi::OsString, iter, path::PathBuf}; use flycheck::FlycheckConfig; use ide::{ AssistConfig, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, HighlightRelatedConfig, - HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig, Snippet, SnippetScope, + HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig, LifetimeElisionHints, Snippet, + SnippetScope, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, @@ -243,20 +244,24 @@ config_data! { hoverActions_run: bool = "true", /// Whether to render trailing colons for parameter hints, and trailing colons for parameter hints. - inlayHints_renderColons: bool = "true", + inlayHints_renderColons: bool = "true", /// Maximum length for inlay hints. Set to null to have an unlimited length. - inlayHints_maxLength: Option = "25", + inlayHints_maxLength: Option = "25", /// Whether to show function parameter name inlay hints at the call /// site. - inlayHints_parameterHints: bool = "true", + inlayHints_parameterHints: bool = "true", /// Whether to show inlay type hints for variables. - inlayHints_typeHints: bool = "true", + inlayHints_typeHints: bool = "true", /// Whether to show inlay type hints for method chains. - inlayHints_chainingHints: bool = "true", + inlayHints_chainingHints: bool = "true", /// Whether to show inlay type hints for return types of closures with blocks. - inlayHints_closureReturnTypeHints: bool = "false", + inlayHints_closureReturnTypeHints: bool = "false", + /// Whether to show inlay type hints for elided lifetimes in function signatures. + inlayHints_lifetimeElisionHints: LifetimeElisionDef = "\"never\"", + /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. + inlayHints_lifetimeElisionHints_useParameterNames: bool = "false", /// Whether to hide inlay hints for constructors. - inlayHints_hideNamedConstructorHints: bool = "false", + inlayHints_hideNamedConstructorHints: bool = "false", /// Join lines inserts else between consecutive ifs. joinLines_joinElseIf: bool = "true", @@ -855,7 +860,15 @@ impl Config { parameter_hints: self.data.inlayHints_parameterHints, chaining_hints: self.data.inlayHints_chainingHints, closure_return_type_hints: self.data.inlayHints_closureReturnTypeHints, + lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints { + LifetimeElisionDef::Always => LifetimeElisionHints::Always, + LifetimeElisionDef::Never => LifetimeElisionHints::Never, + LifetimeElisionDef::SkipTrivial => LifetimeElisionHints::SkipTrivial, + }, hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints, + param_names_for_lifetime_elision_hints: self + .data + .inlayHints_lifetimeElisionHints_useParameterNames, max_length: self.data.inlayHints_maxLength, } } @@ -1125,6 +1138,16 @@ enum ImportGranularityDef { Module, } +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +enum LifetimeElisionDef { + #[serde(alias = "true")] + Always, + #[serde(alias = "false")] + Never, + SkipTrivial, +} + #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportPrefixDef { @@ -1377,7 +1400,16 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "minimum": 0, "maximum": 255 }, - _ => panic!("{}: {}", ty, default), + "LifetimeElisionDef" => set! { + "type": "string", + "enum": ["always", "never", "skip_trivial"], + "enumDescriptions": [ + "Always show lifetime elision hints.", + "Never show lifetime elision hints.", + "Only show lifetime elision hints if a return type is involved." + ], + }, + _ => panic!("missing entry for {}: {}", ty, default), } map.into() diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 7ea0eae053..693b394d3b 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -427,27 +427,34 @@ pub(crate) fn inlay_hint( }), position: match inlay_hint.kind { InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()), - InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => { - position(line_index, inlay_hint.range.end()) - } + InlayKind::ClosureReturnTypeHint + | InlayKind::TypeHint + | InlayKind::ChainingHint + | InlayKind::GenericParamListHint + | InlayKind::LifetimeHint => position(line_index, inlay_hint.range.end()), }, kind: match inlay_hint.kind { InlayKind::ParameterHint => Some(lsp_ext::InlayHintKind::PARAMETER), InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => { Some(lsp_ext::InlayHintKind::TYPE) } + InlayKind::GenericParamListHint | InlayKind::LifetimeHint => None, }, tooltip: None, padding_left: Some(match inlay_hint.kind { InlayKind::TypeHint => !render_colons, InlayKind::ParameterHint | InlayKind::ClosureReturnTypeHint => false, InlayKind::ChainingHint => true, + InlayKind::GenericParamListHint => false, + InlayKind::LifetimeHint => false, }), padding_right: Some(match inlay_hint.kind { InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => { false } InlayKind::ParameterHint => true, + InlayKind::LifetimeHint => true, + InlayKind::GenericParamListHint => false, }), } } diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 0fa38bb97c..4438a12093 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -306,15 +306,15 @@ fn extract_line_annotations(mut line: &str) -> Vec { let end_marker = line_no_caret.find(|c| c == '$'); let next = line_no_caret.find(marker).map_or(line.len(), |it| it + len); - let mut content = match end_marker { - Some(end_marker) - if end_marker < next - && line_no_caret[end_marker..] + let cond = |end_marker| { + end_marker < next + && (line_no_caret[end_marker + 1..].is_empty() + || line_no_caret[end_marker + 1..] .strip_prefix(|c: char| c.is_whitespace() || c == '^') - .is_some() => - { - &line_no_caret[..end_marker] - } + .is_some()) + }; + let mut content = match end_marker { + Some(end_marker) if cond(end_marker) => &line_no_caret[..end_marker], _ => line_no_caret[..next - len].trim_end(), }; diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 5793b4c057..42f485b511 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -378,6 +378,16 @@ Whether to show inlay type hints for method chains. -- Whether to show inlay type hints for return types of closures with blocks. -- +[[rust-analyzer.inlayHints.lifetimeElisionHints]]rust-analyzer.inlayHints.lifetimeElisionHints (default: `"never"`):: ++ +-- +Whether to show inlay type hints for elided lifetimes in function signatures. +-- +[[rust-analyzer.inlayHints.lifetimeElisionHints.useParameterNames]]rust-analyzer.inlayHints.lifetimeElisionHints.useParameterNames (default: `false`):: ++ +-- +Whether to prefer using parameter names as the name for elided lifetime hints if possible. +-- [[rust-analyzer.inlayHints.hideNamedConstructorHints]]rust-analyzer.inlayHints.hideNamedConstructorHints (default: `false`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 56d10f587d..ffd89d96d4 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -795,6 +795,26 @@ "default": false, "type": "boolean" }, + "rust-analyzer.inlayHints.lifetimeElisionHints": { + "markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.", + "default": "never", + "type": "string", + "enum": [ + "always", + "never", + "skip_trivial" + ], + "enumDescriptions": [ + "Always show lifetime elision hints.", + "Never show lifetime elision hints.", + "Only show lifetime elision hints if a return type is involved." + ] + }, + "rust-analyzer.inlayHints.lifetimeElisionHints.useParameterNames": { + "markdownDescription": "Whether to prefer using parameter names as the name for elided lifetime hints if possible.", + "default": false, + "type": "boolean" + }, "rust-analyzer.inlayHints.hideNamedConstructorHints": { "markdownDescription": "Whether to hide inlay hints for constructors.", "default": false,