Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/inlay_hints/generic_param.rs')
| -rw-r--r-- | crates/ide/src/inlay_hints/generic_param.rs | 315 |
1 files changed, 315 insertions, 0 deletions
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<InlayHint>, + 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<String> { + 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<ast::PathSegment> { + 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, Y> { + x: X, + y: Y, +} + +fn foo(a: A<usize, u32>) {} + //^^^^^ 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<i32> = A { x: &x }; + // ^^^ X +} +"#, + ) + } + + #[test] + fn const_only() { + generic_param_name_hints_always( + r#" +struct A<const X: usize, const Y: usize> {}; + +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<T> { + type Assoc1; + type Assoc2; +} + +fn foo() -> impl Trait<i32, Assoc1 = u32, Assoc2 = u32> {} + // ^^^ 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, const N: usize> { + x: [X; N] +} + +type InvalidType = A<3, i32>; +"#, + ) + } +} |