//! Implementation of inlay hints for generic parameters. use either::Either; use ide_db::{active_parameter::generic_def_for_node, famous_defs::FamousDefs}; use syntax::{ AstNode, ast::{self, AnyHasGenericArgs, HasGenericArgs, HasName}, }; use crate::{ InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind, inlay_hints::{GenericParameterHints, param_name}, }; use super::param_name::is_argument_similar_to_param_name; pub(crate) fn hints( acc: &mut Vec, FamousDefs(sema, krate): &FamousDefs<'_, '_>, 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 && 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 allowed = match (param, &arg) { (hir::GenericParam::TypeParam(_), ast::GenericArg::TypeArg(_)) => type_hints, (hir::GenericParam::ConstParam(_), ast::GenericArg::ConstArg(_)) => const_hints, (hir::GenericParam::LifetimeParam(_), ast::GenericArg::LifetimeArg(_)) => { lifetime_hints } _ => false, }; if !allowed { return None; } let param_name = param.name(sema.db); let should_hide = { let param_name = param_name.as_str(); get_segment_representation(&arg).map_or(false, |seg| match seg { Either::Left(Either::Left(argument)) => { is_argument_similar_to_param_name(&argument, param_name) } Either::Left(Either::Right(argument)) => argument .segment() .and_then(|it| it.name_ref()) .is_some_and(|it| it.text().eq_ignore_ascii_case(param_name)), Either::Right(lifetime) => lifetime.text().eq_ignore_ascii_case(param_name), }) }; if should_hide { return None; } let range = sema.original_range_opt(arg.syntax())?.range; let colon = if config.render_colons { ":" } else { "" }; let label = InlayHintLabel::simple( format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))), None, config.lazy_location_opt(|| { let source_syntax = match param { hir::GenericParam::TypeParam(it) => { sema.source(it.merge()).map(|it| it.value.syntax().clone()) } hir::GenericParam::ConstParam(it) => { let syntax = sema.source(it.merge())?.value.syntax().clone(); let const_param = ast::ConstParam::cast(syntax)?; const_param.name().map(|it| it.syntax().clone()) } hir::GenericParam::LifetimeParam(it) => { sema.source(it).map(|it| it.value.syntax().clone()) } }; let linked_location = source_syntax.and_then(|it| sema.original_range_opt(&it)); linked_location.map(|frange| ide_db::FileRange { file_id: frange.file_id.file_id(sema.db), range: frange.range, }) }), ); Some(InlayHint { range, position: crate::InlayHintPosition::Before, pad_left: false, pad_right: true, kind: InlayKind::GenericParameter, label, text_edit: None, resolve_parent: Some(node.syntax().text_range()), }) }); acc.extend(hints); Some(()) } fn get_segment_representation( arg: &ast::GenericArg, ) -> Option, ast::Path>, ast::Lifetime>> { return match arg { ast::GenericArg::AssocTypeArg(_) => None, ast::GenericArg::ConstArg(const_arg) => { param_name::get_segment_representation(&const_arg.expr()?).map(Either::Left) } ast::GenericArg::LifetimeArg(lifetime_arg) => { let lifetime = lifetime_arg.lifetime()?; Some(Either::Right(lifetime)) } ast::GenericArg::TypeArg(type_arg) => { let ty = type_arg.ty()?; type_path(&ty).map(Either::Right).map(Either::Left) } }; fn type_path(ty: &ast::Type) -> Option { match ty { ast::Type::ArrayType(it) => type_path(&it.ty()?), ast::Type::ForType(it) => type_path(&it.ty()?), ast::Type::ParenType(it) => type_path(&it.ty()?), ast::Type::PathType(path_type) => path_type.path(), ast::Type::PtrType(it) => type_path(&it.ty()?), ast::Type::RefType(it) => type_path(&it.ty()?), ast::Type::SliceType(it) => type_path(&it.ty()?), ast::Type::MacroType(macro_type) => macro_type.macro_call()?.path(), _ => None, } } } #[cfg(test)] mod tests { use crate::{ InlayHintsConfig, inlay_hints::{ GenericParameterHints, tests::{DISABLED_CONFIG, check_with_config}, }, }; #[track_caller] fn generic_param_name_hints_always(#[rust_analyzer::rust_fixture] 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(#[rust_analyzer::rust_fixture] 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>; "#, ) } }