mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-29 06:23:25 +00:00
Auto merge of #14533 - lowr:feat/text-edits-for-inlay-hints, r=Veykril
feat: make inlay hints insertable Part of #13812 This PR implements text edit for inlay hints. When an inlay hint contain text edit, user can "accept" it (e.g. by double-clicking in VS Code) to make the hint actual code (effectively deprecating the hint itself). This PR does not implement auto import despite the original request; text edits only insert qualified types along with necessary punctuation. I feel there are some missing pieces to implement efficient auto import (in particular, type traversal function with early exit) so left it for future work. Even without it, user can use `replace_qualified_name_with_use` assist after accepting the edit to achieve the same result. I implemented for the following inlay hints: - top-level identifier pattern in let statements - top-level identifier pattern in closure parameters - closure return type when its has block body One somewhat strange interaction can be observed when top-level identifier pattern has subpattern: text edit inserts type annotation in different place than the inlay hint. Do we want to allow it or should we not provide text edits for these cases at all? ```rust let a /* inlay hint shown here */ @ (b, c) = foo(); let a @ (b, c) /* text edit inserts types here */ = foo(); ```
This commit is contained in:
commit
1ee88db412
27 changed files with 374 additions and 49 deletions
|
@ -150,6 +150,7 @@ pub trait HirDisplay {
|
||||||
&'a self,
|
&'a self,
|
||||||
db: &'a dyn HirDatabase,
|
db: &'a dyn HirDatabase,
|
||||||
module_id: ModuleId,
|
module_id: ModuleId,
|
||||||
|
allow_opaque: bool,
|
||||||
) -> Result<String, DisplaySourceCodeError> {
|
) -> Result<String, DisplaySourceCodeError> {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
match self.hir_fmt(&mut HirFormatter {
|
match self.hir_fmt(&mut HirFormatter {
|
||||||
|
@ -160,7 +161,7 @@ pub trait HirDisplay {
|
||||||
max_size: None,
|
max_size: None,
|
||||||
omit_verbose_types: false,
|
omit_verbose_types: false,
|
||||||
closure_style: ClosureStyle::ImplFn,
|
closure_style: ClosureStyle::ImplFn,
|
||||||
display_target: DisplayTarget::SourceCode { module_id },
|
display_target: DisplayTarget::SourceCode { module_id, allow_opaque },
|
||||||
}) {
|
}) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
|
Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
|
||||||
|
@ -249,18 +250,26 @@ pub enum DisplayTarget {
|
||||||
Diagnostics,
|
Diagnostics,
|
||||||
/// Display types for inserting them in source files.
|
/// Display types for inserting them in source files.
|
||||||
/// The generated code should compile, so paths need to be qualified.
|
/// The generated code should compile, so paths need to be qualified.
|
||||||
SourceCode { module_id: ModuleId },
|
SourceCode { module_id: ModuleId, allow_opaque: bool },
|
||||||
/// Only for test purpose to keep real types
|
/// Only for test purpose to keep real types
|
||||||
Test,
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayTarget {
|
impl DisplayTarget {
|
||||||
fn is_source_code(&self) -> bool {
|
fn is_source_code(self) -> bool {
|
||||||
matches!(self, Self::SourceCode { .. })
|
matches!(self, Self::SourceCode { .. })
|
||||||
}
|
}
|
||||||
fn is_test(&self) -> bool {
|
|
||||||
|
fn is_test(self) -> bool {
|
||||||
matches!(self, Self::Test)
|
matches!(self, Self::Test)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allows_opaque(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::SourceCode { allow_opaque, .. } => allow_opaque,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -268,6 +277,7 @@ pub enum DisplaySourceCodeError {
|
||||||
PathNotFound,
|
PathNotFound,
|
||||||
UnknownType,
|
UnknownType,
|
||||||
Generator,
|
Generator,
|
||||||
|
OpaqueType,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum HirDisplayError {
|
pub enum HirDisplayError {
|
||||||
|
@ -768,7 +778,7 @@ impl HirDisplay for Ty {
|
||||||
};
|
};
|
||||||
write!(f, "{name}")?;
|
write!(f, "{name}")?;
|
||||||
}
|
}
|
||||||
DisplayTarget::SourceCode { module_id } => {
|
DisplayTarget::SourceCode { module_id, allow_opaque: _ } => {
|
||||||
if let Some(path) = find_path::find_path(
|
if let Some(path) = find_path::find_path(
|
||||||
db.upcast(),
|
db.upcast(),
|
||||||
ItemInNs::Types((*def_id).into()),
|
ItemInNs::Types((*def_id).into()),
|
||||||
|
@ -906,6 +916,11 @@ impl HirDisplay for Ty {
|
||||||
f.end_location_link();
|
f.end_location_link();
|
||||||
}
|
}
|
||||||
TyKind::OpaqueType(opaque_ty_id, parameters) => {
|
TyKind::OpaqueType(opaque_ty_id, parameters) => {
|
||||||
|
if !f.display_target.allows_opaque() {
|
||||||
|
return Err(HirDisplayError::DisplaySourceCodeError(
|
||||||
|
DisplaySourceCodeError::OpaqueType,
|
||||||
|
));
|
||||||
|
}
|
||||||
let impl_trait_id = db.lookup_intern_impl_trait_id((*opaque_ty_id).into());
|
let impl_trait_id = db.lookup_intern_impl_trait_id((*opaque_ty_id).into());
|
||||||
match impl_trait_id {
|
match impl_trait_id {
|
||||||
ImplTraitId::ReturnTypeImplTrait(func, idx) => {
|
ImplTraitId::ReturnTypeImplTrait(func, idx) => {
|
||||||
|
@ -953,8 +968,14 @@ impl HirDisplay for Ty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TyKind::Closure(id, substs) => {
|
TyKind::Closure(id, substs) => {
|
||||||
if f.display_target.is_source_code() && f.closure_style != ClosureStyle::ImplFn {
|
if f.display_target.is_source_code() {
|
||||||
never!("Only `impl Fn` is valid for displaying closures in source code");
|
if !f.display_target.allows_opaque() {
|
||||||
|
return Err(HirDisplayError::DisplaySourceCodeError(
|
||||||
|
DisplaySourceCodeError::OpaqueType,
|
||||||
|
));
|
||||||
|
} else if f.closure_style != ClosureStyle::ImplFn {
|
||||||
|
never!("Only `impl Fn` is valid for displaying closures in source code");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
match f.closure_style {
|
match f.closure_style {
|
||||||
ClosureStyle::Hide => return write!(f, "{TYPE_HINT_TRUNCATION}"),
|
ClosureStyle::Hide => return write!(f, "{TYPE_HINT_TRUNCATION}"),
|
||||||
|
@ -1053,6 +1074,11 @@ impl HirDisplay for Ty {
|
||||||
}
|
}
|
||||||
TyKind::Alias(AliasTy::Projection(p_ty)) => p_ty.hir_fmt(f)?,
|
TyKind::Alias(AliasTy::Projection(p_ty)) => p_ty.hir_fmt(f)?,
|
||||||
TyKind::Alias(AliasTy::Opaque(opaque_ty)) => {
|
TyKind::Alias(AliasTy::Opaque(opaque_ty)) => {
|
||||||
|
if !f.display_target.allows_opaque() {
|
||||||
|
return Err(HirDisplayError::DisplaySourceCodeError(
|
||||||
|
DisplaySourceCodeError::OpaqueType,
|
||||||
|
));
|
||||||
|
}
|
||||||
let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty.opaque_ty_id.into());
|
let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty.opaque_ty_id.into());
|
||||||
match impl_trait_id {
|
match impl_trait_id {
|
||||||
ImplTraitId::ReturnTypeImplTrait(func, idx) => {
|
ImplTraitId::ReturnTypeImplTrait(func, idx) => {
|
||||||
|
|
|
@ -159,7 +159,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
|
||||||
let range = node.as_ref().original_file_range(&db);
|
let range = node.as_ref().original_file_range(&db);
|
||||||
if let Some(expected) = types.remove(&range) {
|
if let Some(expected) = types.remove(&range) {
|
||||||
let actual = if display_source {
|
let actual = if display_source {
|
||||||
ty.display_source_code(&db, def.module(&db)).unwrap()
|
ty.display_source_code(&db, def.module(&db), true).unwrap()
|
||||||
} else {
|
} else {
|
||||||
ty.display_test(&db).to_string()
|
ty.display_test(&db).to_string()
|
||||||
};
|
};
|
||||||
|
@ -175,7 +175,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
|
||||||
let range = node.as_ref().original_file_range(&db);
|
let range = node.as_ref().original_file_range(&db);
|
||||||
if let Some(expected) = types.remove(&range) {
|
if let Some(expected) = types.remove(&range) {
|
||||||
let actual = if display_source {
|
let actual = if display_source {
|
||||||
ty.display_source_code(&db, def.module(&db)).unwrap()
|
ty.display_source_code(&db, def.module(&db), true).unwrap()
|
||||||
} else {
|
} else {
|
||||||
ty.display_test(&db).to_string()
|
ty.display_test(&db).to_string()
|
||||||
};
|
};
|
||||||
|
|
|
@ -69,7 +69,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
|
let inferred_type = ty.display_source_code(ctx.db(), module.into(), false).ok()?;
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("add_explicit_type", AssistKind::RefactorRewrite),
|
AssistId("add_explicit_type", AssistKind::RefactorRewrite),
|
||||||
format!("Insert explicit type `{inferred_type}`"),
|
format!("Insert explicit type `{inferred_type}`"),
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
|
||||||
if ty.is_unit() {
|
if ty.is_unit() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
|
let ty = ty.display_source_code(ctx.db(), module.into(), true).ok()?;
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("add_return_type", AssistKind::RefactorRewrite),
|
AssistId("add_return_type", AssistKind::RefactorRewrite),
|
||||||
|
|
|
@ -1884,7 +1884,7 @@ fn with_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_type(ty: &hir::Type, ctx: &AssistContext<'_>, module: hir::Module) -> String {
|
fn format_type(ty: &hir::Type, ctx: &AssistContext<'_>, module: hir::Module) -> String {
|
||||||
ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "_".to_string())
|
ty.display_source_code(ctx.db(), module.into(), true).ok().unwrap_or_else(|| "_".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_ty(ty: &hir::Type, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Type {
|
fn make_ty(ty: &hir::Type, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Type {
|
||||||
|
|
|
@ -46,7 +46,8 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
|
||||||
let ty = ctx.sema.type_of_expr(&expr)?;
|
let ty = ctx.sema.type_of_expr(&expr)?;
|
||||||
let scope = ctx.sema.scope(statement.syntax())?;
|
let scope = ctx.sema.scope(statement.syntax())?;
|
||||||
let constant_module = scope.module();
|
let constant_module = scope.module();
|
||||||
let type_name = ty.original().display_source_code(ctx.db(), constant_module.into()).ok()?;
|
let type_name =
|
||||||
|
ty.original().display_source_code(ctx.db(), constant_module.into(), false).ok()?;
|
||||||
let target = statement.syntax().parent()?.text_range();
|
let target = statement.syntax().parent()?.text_range();
|
||||||
let path = constant_token.syntax().ancestors().find_map(ast::Path::cast)?;
|
let path = constant_token.syntax().ancestors().find_map(ast::Path::cast)?;
|
||||||
|
|
||||||
|
|
|
@ -192,7 +192,7 @@ fn expr_ty(
|
||||||
scope: &hir::SemanticsScope<'_>,
|
scope: &hir::SemanticsScope<'_>,
|
||||||
) -> Option<ast::Type> {
|
) -> Option<ast::Type> {
|
||||||
let ty = ctx.sema.type_of_expr(&arg).map(|it| it.adjusted())?;
|
let ty = ctx.sema.type_of_expr(&arg).map(|it| it.adjusted())?;
|
||||||
let text = ty.display_source_code(ctx.db(), scope.module().into()).ok()?;
|
let text = ty.display_source_code(ctx.db(), scope.module().into(), false).ok()?;
|
||||||
Some(make::ty(&text))
|
Some(make::ty(&text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -438,7 +438,7 @@ fn make_return_type(
|
||||||
Some(ty) if ty.is_unit() => (None, false),
|
Some(ty) if ty.is_unit() => (None, false),
|
||||||
Some(ty) => {
|
Some(ty) => {
|
||||||
necessary_generic_params.extend(ty.generic_params(ctx.db()));
|
necessary_generic_params.extend(ty.generic_params(ctx.db()));
|
||||||
let rendered = ty.display_source_code(ctx.db(), target_module.into());
|
let rendered = ty.display_source_code(ctx.db(), target_module.into(), true);
|
||||||
match rendered {
|
match rendered {
|
||||||
Ok(rendered) => (Some(make::ty(&rendered)), false),
|
Ok(rendered) => (Some(make::ty(&rendered)), false),
|
||||||
Err(_) => (Some(make::ty_placeholder()), true),
|
Err(_) => (Some(make::ty_placeholder()), true),
|
||||||
|
@ -992,9 +992,9 @@ fn fn_arg_type(
|
||||||
let famous_defs = &FamousDefs(&ctx.sema, ctx.sema.scope(fn_arg.syntax())?.krate());
|
let famous_defs = &FamousDefs(&ctx.sema, ctx.sema.scope(fn_arg.syntax())?.krate());
|
||||||
convert_reference_type(ty.strip_references(), ctx.db(), famous_defs)
|
convert_reference_type(ty.strip_references(), ctx.db(), famous_defs)
|
||||||
.map(|conversion| conversion.convert_type(ctx.db()))
|
.map(|conversion| conversion.convert_type(ctx.db()))
|
||||||
.or_else(|| ty.display_source_code(ctx.db(), target_module.into()).ok())
|
.or_else(|| ty.display_source_code(ctx.db(), target_module.into(), true).ok())
|
||||||
} else {
|
} else {
|
||||||
ty.display_source_code(ctx.db(), target_module.into()).ok()
|
ty.display_source_code(ctx.db(), target_module.into(), true).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,11 +57,13 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
let local = ctx.sema.to_def(&pat)?;
|
let local = ctx.sema.to_def(&pat)?;
|
||||||
let ty = ctx.sema.type_of_pat(&pat.into())?.original;
|
let ty = ctx.sema.type_of_pat(&pat.into())?.original;
|
||||||
|
|
||||||
if ty.contains_unknown() || ty.is_closure() {
|
let ty = match ty.display_source_code(ctx.db(), module.into(), false) {
|
||||||
cov_mark::hit!(promote_lcoal_not_applicable_if_ty_not_inferred);
|
Ok(ty) => ty,
|
||||||
return None;
|
Err(_) => {
|
||||||
}
|
cov_mark::hit!(promote_local_not_applicable_if_ty_not_inferred);
|
||||||
let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let initializer = let_stmt.initializer()?;
|
let initializer = let_stmt.initializer()?;
|
||||||
if !is_body_const(&ctx.sema, &initializer) {
|
if !is_body_const(&ctx.sema, &initializer) {
|
||||||
|
@ -187,7 +189,7 @@ fn foo() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn not_applicable_unknown_ty() {
|
fn not_applicable_unknown_ty() {
|
||||||
cov_mark::check!(promote_lcoal_not_applicable_if_ty_not_inferred);
|
cov_mark::check!(promote_local_not_applicable_if_ty_not_inferred);
|
||||||
check_assist_not_applicable(
|
check_assist_not_applicable(
|
||||||
promote_local_to_const,
|
promote_local_to_const,
|
||||||
r"
|
r"
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub(crate) fn replace_turbofish_with_explicit_type(
|
||||||
let returned_type = match ctx.sema.type_of_expr(&initializer) {
|
let returned_type = match ctx.sema.type_of_expr(&initializer) {
|
||||||
Some(returned_type) if !returned_type.original.contains_unknown() => {
|
Some(returned_type) if !returned_type.original.contains_unknown() => {
|
||||||
let module = ctx.sema.scope(let_stmt.syntax())?.module();
|
let module = ctx.sema.scope(let_stmt.syntax())?.module();
|
||||||
returned_type.original.display_source_code(ctx.db(), module.into()).ok()?
|
returned_type.original.display_source_code(ctx.db(), module.into(), false).ok()?
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
cov_mark::hit!(fallback_to_turbofish_type_if_type_info_not_available);
|
cov_mark::hit!(fallback_to_turbofish_type_if_type_info_not_available);
|
||||||
|
|
|
@ -127,7 +127,7 @@ fn params_from_stmt_list_scope(
|
||||||
let module = scope.module().into();
|
let module = scope.module().into();
|
||||||
scope.process_all_names(&mut |name, def| {
|
scope.process_all_names(&mut |name, def| {
|
||||||
if let hir::ScopeDef::Local(local) = def {
|
if let hir::ScopeDef::Local(local) = def {
|
||||||
if let Ok(ty) = local.ty(ctx.db).display_source_code(ctx.db, module) {
|
if let Ok(ty) = local.ty(ctx.db).display_source_code(ctx.db, module, true) {
|
||||||
cb(name, ty);
|
cb(name, ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,7 +242,7 @@ pub(crate) fn complete_ascribed_type(
|
||||||
}
|
}
|
||||||
}?
|
}?
|
||||||
.adjusted();
|
.adjusted();
|
||||||
let ty_string = x.display_source_code(ctx.db, ctx.module.into()).ok()?;
|
let ty_string = x.display_source_code(ctx.db, ctx.module.into(), true).ok()?;
|
||||||
acc.add(render_type_inference(ty_string, ctx));
|
acc.add(render_type_inference(ty_string, ctx));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,9 @@ impl<'a> PathTransform<'a> {
|
||||||
Some((
|
Some((
|
||||||
k,
|
k,
|
||||||
ast::make::ty(
|
ast::make::ty(
|
||||||
&default.display_source_code(db, source_module.into()).ok()?,
|
&default
|
||||||
|
.display_source_code(db, source_module.into(), false)
|
||||||
|
.ok()?,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,9 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
|
||||||
fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
|
fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
|
||||||
let ty_str = match ty.as_adt() {
|
let ty_str = match ty.as_adt() {
|
||||||
Some(adt) => adt.name(db).to_string(),
|
Some(adt) => adt.name(db).to_string(),
|
||||||
None => ty.display_source_code(db, module.into()).ok().unwrap_or_else(|| "_".to_string()),
|
None => {
|
||||||
|
ty.display_source_code(db, module.into(), false).ok().unwrap_or_else(|| "_".to_string())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
make::ty(&ty_str)
|
make::ty(&ty_str)
|
||||||
|
|
|
@ -69,7 +69,7 @@ fn missing_record_expr_field_fixes(
|
||||||
let new_field = make::record_field(
|
let new_field = make::record_field(
|
||||||
None,
|
None,
|
||||||
make::name(record_expr_field.field_name()?.ident_token()?.text()),
|
make::name(record_expr_field.field_name()?.ident_token()?.text()),
|
||||||
make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
|
make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?),
|
||||||
);
|
);
|
||||||
|
|
||||||
let last_field = record_fields.fields().last()?;
|
let last_field = record_fields.fields().last()?;
|
||||||
|
|
|
@ -14,8 +14,9 @@ use smallvec::{smallvec, SmallVec};
|
||||||
use stdx::never;
|
use stdx::never;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, AstNode},
|
ast::{self, AstNode},
|
||||||
match_ast, NodeOrToken, SyntaxNode, TextRange,
|
match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
|
||||||
};
|
};
|
||||||
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
use crate::{navigation_target::TryToNav, FileId};
|
use crate::{navigation_target::TryToNav, FileId};
|
||||||
|
|
||||||
|
@ -113,14 +114,26 @@ pub struct InlayHint {
|
||||||
pub kind: InlayKind,
|
pub kind: InlayKind,
|
||||||
/// The actual label to show in the inlay hint.
|
/// The actual label to show in the inlay hint.
|
||||||
pub label: InlayHintLabel,
|
pub label: InlayHintLabel,
|
||||||
|
/// Text edit to apply when "accepting" this inlay hint.
|
||||||
|
pub text_edit: Option<TextEdit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlayHint {
|
impl InlayHint {
|
||||||
fn closing_paren(range: TextRange) -> InlayHint {
|
fn closing_paren(range: TextRange) -> InlayHint {
|
||||||
InlayHint { range, kind: InlayKind::ClosingParenthesis, label: InlayHintLabel::from(")") }
|
InlayHint {
|
||||||
|
range,
|
||||||
|
kind: InlayKind::ClosingParenthesis,
|
||||||
|
label: InlayHintLabel::from(")"),
|
||||||
|
text_edit: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn opening_paren(range: TextRange) -> InlayHint {
|
fn opening_paren(range: TextRange) -> InlayHint {
|
||||||
InlayHint { range, kind: InlayKind::OpeningParenthesis, label: InlayHintLabel::from("(") }
|
InlayHint {
|
||||||
|
range,
|
||||||
|
kind: InlayKind::OpeningParenthesis,
|
||||||
|
label: InlayHintLabel::from("("),
|
||||||
|
text_edit: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,6 +359,23 @@ fn label_of_ty(
|
||||||
Some(r)
|
Some(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ty_to_text_edit(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
node_for_hint: &SyntaxNode,
|
||||||
|
ty: &hir::Type,
|
||||||
|
offset_to_insert: TextSize,
|
||||||
|
prefix: String,
|
||||||
|
) -> Option<TextEdit> {
|
||||||
|
let scope = sema.scope(node_for_hint)?;
|
||||||
|
// FIXME: Limit the length and bail out on excess somehow?
|
||||||
|
let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?;
|
||||||
|
|
||||||
|
let mut builder = TextEdit::builder();
|
||||||
|
builder.insert(offset_to_insert, prefix);
|
||||||
|
builder.insert(offset_to_insert, rendered);
|
||||||
|
Some(builder.finish())
|
||||||
|
}
|
||||||
|
|
||||||
// Feature: Inlay Hints
|
// Feature: Inlay Hints
|
||||||
//
|
//
|
||||||
// rust-analyzer shows additional information inline with the source code.
|
// rust-analyzer shows additional information inline with the source code.
|
||||||
|
@ -553,6 +583,37 @@ mod tests {
|
||||||
expect.assert_debug_eq(&inlay_hints)
|
expect.assert_debug_eq(&inlay_hints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes inlay hints for the fixture, applies all the provided text edits and then runs
|
||||||
|
/// expect test.
|
||||||
|
#[track_caller]
|
||||||
|
pub(super) fn check_edit(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 edits = inlay_hints
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|hint| hint.text_edit)
|
||||||
|
.reduce(|mut acc, next| {
|
||||||
|
acc.union(next).expect("merging text edits failed");
|
||||||
|
acc
|
||||||
|
})
|
||||||
|
.expect("no edit returned");
|
||||||
|
|
||||||
|
let mut actual = analysis.file_text(file_id).unwrap().to_string();
|
||||||
|
edits.apply(&mut actual);
|
||||||
|
expect.assert_eq(&actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub(super) fn check_no_edit(config: InlayHintsConfig, ra_fixture: &str) {
|
||||||
|
let (analysis, file_id) = fixture::file(ra_fixture);
|
||||||
|
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
|
||||||
|
|
||||||
|
let edits: Vec<_> = inlay_hints.into_iter().filter_map(|hint| hint.text_edit).collect();
|
||||||
|
|
||||||
|
assert!(edits.is_empty(), "unexpected edits: {edits:?}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hints_disabled() {
|
fn hints_disabled() {
|
||||||
check_with_config(
|
check_with_config(
|
||||||
|
|
|
@ -135,6 +135,7 @@ pub(super) fn hints(
|
||||||
))),
|
))),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
|
text_edit: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if !postfix && needs_inner_parens {
|
if !postfix && needs_inner_parens {
|
||||||
|
|
|
@ -12,9 +12,10 @@ use syntax::{
|
||||||
match_ast,
|
match_ast,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind};
|
use crate::{
|
||||||
|
inlay_hints::{closure_has_block_body, label_of_ty, ty_to_text_edit},
|
||||||
use super::label_of_ty;
|
InlayHint, InlayHintsConfig, InlayKind,
|
||||||
|
};
|
||||||
|
|
||||||
pub(super) fn hints(
|
pub(super) fn hints(
|
||||||
acc: &mut Vec<InlayHint>,
|
acc: &mut Vec<InlayHint>,
|
||||||
|
@ -35,7 +36,7 @@ pub(super) fn hints(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = label_of_ty(famous_defs, config, ty)?;
|
let label = label_of_ty(famous_defs, config, ty.clone())?;
|
||||||
|
|
||||||
if config.hide_named_constructor_hints
|
if config.hide_named_constructor_hints
|
||||||
&& is_named_constructor(sema, pat, &label.to_string()).is_some()
|
&& is_named_constructor(sema, pat, &label.to_string()).is_some()
|
||||||
|
@ -43,6 +44,23 @@ pub(super) fn hints(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let type_annotation_is_valid = desc_pat
|
||||||
|
.syntax()
|
||||||
|
.parent()
|
||||||
|
.map(|it| ast::LetStmt::can_cast(it.kind()) || ast::Param::can_cast(it.kind()))
|
||||||
|
.unwrap_or(false);
|
||||||
|
let text_edit = if type_annotation_is_valid {
|
||||||
|
ty_to_text_edit(
|
||||||
|
sema,
|
||||||
|
desc_pat.syntax(),
|
||||||
|
&ty,
|
||||||
|
pat.syntax().text_range().end(),
|
||||||
|
String::from(": "),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
acc.push(InlayHint {
|
acc.push(InlayHint {
|
||||||
range: match pat.name() {
|
range: match pat.name() {
|
||||||
Some(name) => name.syntax().text_range(),
|
Some(name) => name.syntax().text_range(),
|
||||||
|
@ -50,6 +68,7 @@ pub(super) fn hints(
|
||||||
},
|
},
|
||||||
kind: InlayKind::Type,
|
kind: InlayKind::Type,
|
||||||
label,
|
label,
|
||||||
|
text_edit,
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
|
@ -176,14 +195,16 @@ fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir
|
||||||
mod tests {
|
mod tests {
|
||||||
// This module also contains tests for super::closure_ret
|
// This module also contains tests for super::closure_ret
|
||||||
|
|
||||||
|
use expect_test::expect;
|
||||||
use hir::ClosureStyle;
|
use hir::ClosureStyle;
|
||||||
use syntax::{TextRange, TextSize};
|
use syntax::{TextRange, TextSize};
|
||||||
use test_utils::extract_annotations;
|
use test_utils::extract_annotations;
|
||||||
|
|
||||||
use crate::{fixture, inlay_hints::InlayHintsConfig};
|
use crate::{fixture, inlay_hints::InlayHintsConfig, ClosureReturnTypeHints};
|
||||||
|
|
||||||
use crate::inlay_hints::tests::{check, check_with_config, DISABLED_CONFIG, TEST_CONFIG};
|
use crate::inlay_hints::tests::{
|
||||||
use crate::ClosureReturnTypeHints;
|
check, check_edit, check_no_edit, check_with_config, DISABLED_CONFIG, TEST_CONFIG,
|
||||||
|
};
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check_types(ra_fixture: &str) {
|
fn check_types(ra_fixture: &str) {
|
||||||
|
@ -1012,4 +1033,160 @@ fn main() {
|
||||||
}"#,
|
}"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edit_for_let_stmt() {
|
||||||
|
check_edit(
|
||||||
|
TEST_CONFIG,
|
||||||
|
r#"
|
||||||
|
struct S<T>(T);
|
||||||
|
fn test<F>(v: S<(S<i32>, S<()>)>, f: F) {
|
||||||
|
let a = v;
|
||||||
|
let S((b, c)) = v;
|
||||||
|
let a @ S((b, c)) = v;
|
||||||
|
let a = f;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct S<T>(T);
|
||||||
|
fn test<F>(v: S<(S<i32>, S<()>)>, f: F) {
|
||||||
|
let a: S<(S<i32>, S<()>)> = v;
|
||||||
|
let S((b, c)) = v;
|
||||||
|
let a @ S((b, c)): S<(S<i32>, S<()>)> = v;
|
||||||
|
let a: F = f;
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edit_for_closure_param() {
|
||||||
|
check_edit(
|
||||||
|
TEST_CONFIG,
|
||||||
|
r#"
|
||||||
|
fn test<T>(t: T) {
|
||||||
|
let f = |a, b, c| {};
|
||||||
|
let result = f(42, "", t);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn test<T>(t: T) {
|
||||||
|
let f = |a: i32, b: &str, c: T| {};
|
||||||
|
let result: () = f(42, "", t);
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edit_for_closure_ret() {
|
||||||
|
check_edit(
|
||||||
|
TEST_CONFIG,
|
||||||
|
r#"
|
||||||
|
struct S<T>(T);
|
||||||
|
fn test() {
|
||||||
|
let f = || { 3 };
|
||||||
|
let f = |a: S<usize>| { S(a) };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct S<T>(T);
|
||||||
|
fn test() {
|
||||||
|
let f = || -> i32 { 3 };
|
||||||
|
let f = |a: S<usize>| -> S<S<usize>> { S(a) };
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edit_prefixes_paths() {
|
||||||
|
check_edit(
|
||||||
|
TEST_CONFIG,
|
||||||
|
r#"
|
||||||
|
pub struct S<T>(T);
|
||||||
|
mod middle {
|
||||||
|
pub struct S<T, U>(T, U);
|
||||||
|
pub fn make() -> S<inner::S<i64>, super::S<usize>> { loop {} }
|
||||||
|
|
||||||
|
mod inner {
|
||||||
|
pub struct S<T>(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test() {
|
||||||
|
let a = make();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
pub struct S<T>(T);
|
||||||
|
mod middle {
|
||||||
|
pub struct S<T, U>(T, U);
|
||||||
|
pub fn make() -> S<inner::S<i64>, super::S<usize>> { loop {} }
|
||||||
|
|
||||||
|
mod inner {
|
||||||
|
pub struct S<T>(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test() {
|
||||||
|
let a: S<inner::S<i64>, crate::S<usize>> = make();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_edit_for_top_pat_where_type_annotation_is_invalid() {
|
||||||
|
check_no_edit(
|
||||||
|
TEST_CONFIG,
|
||||||
|
r#"
|
||||||
|
fn test() {
|
||||||
|
if let a = 42 {}
|
||||||
|
while let a = 42 {}
|
||||||
|
match 42 {
|
||||||
|
a => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_edit_for_opaque_type() {
|
||||||
|
check_no_edit(
|
||||||
|
TEST_CONFIG,
|
||||||
|
r#"
|
||||||
|
trait Trait {}
|
||||||
|
struct S<T>(T);
|
||||||
|
fn foo() -> impl Trait {}
|
||||||
|
fn bar() -> S<impl Trait> {}
|
||||||
|
fn test() {
|
||||||
|
let a = foo();
|
||||||
|
let a = bar();
|
||||||
|
let f = || { foo() };
|
||||||
|
let f = || { bar() };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_edit_for_closure_return_without_body_block() {
|
||||||
|
// We can lift this limitation; see FIXME in closure_ret module.
|
||||||
|
let config = InlayHintsConfig {
|
||||||
|
closure_return_type_hints: ClosureReturnTypeHints::Always,
|
||||||
|
..TEST_CONFIG
|
||||||
|
};
|
||||||
|
check_no_edit(
|
||||||
|
config,
|
||||||
|
r#"
|
||||||
|
struct S<T>(T);
|
||||||
|
fn test() {
|
||||||
|
let f = || 3;
|
||||||
|
let f = |a: S<usize>| S(a);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,12 @@ pub(super) fn hints(
|
||||||
(true, false) => "&",
|
(true, false) => "&",
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
acc.push(InlayHint { range, kind: InlayKind::BindingMode, label: r.to_string().into() });
|
acc.push(InlayHint {
|
||||||
|
range,
|
||||||
|
kind: InlayKind::BindingMode,
|
||||||
|
label: r.to_string().into(),
|
||||||
|
text_edit: None,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
match pat {
|
match pat {
|
||||||
ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
|
ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
|
||||||
|
@ -63,6 +68,7 @@ pub(super) fn hints(
|
||||||
range: pat.syntax().text_range(),
|
range: pat.syntax().text_range(),
|
||||||
kind: InlayKind::BindingMode,
|
kind: InlayKind::BindingMode,
|
||||||
label: bm.to_string().into(),
|
label: bm.to_string().into(),
|
||||||
|
text_edit: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {
|
ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {
|
||||||
|
|
|
@ -61,6 +61,7 @@ pub(super) fn hints(
|
||||||
range: expr.syntax().text_range(),
|
range: expr.syntax().text_range(),
|
||||||
kind: InlayKind::Chaining,
|
kind: InlayKind::Chaining,
|
||||||
label: label_of_ty(famous_defs, config, ty)?,
|
label: label_of_ty(famous_defs, config, ty)?,
|
||||||
|
text_edit: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +121,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 147..154,
|
range: 147..154,
|
||||||
|
@ -140,6 +142,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -205,6 +208,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 143..179,
|
range: 143..179,
|
||||||
|
@ -225,6 +229,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -274,6 +279,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 143..179,
|
range: 143..179,
|
||||||
|
@ -294,6 +300,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -357,6 +364,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"<i32, bool>>",
|
"<i32, bool>>",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 246..265,
|
range: 246..265,
|
||||||
|
@ -390,6 +398,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"<i32, bool>>",
|
"<i32, bool>>",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -455,6 +464,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
" = ()>",
|
" = ()>",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 174..224,
|
range: 174..224,
|
||||||
|
@ -488,6 +498,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
" = ()>",
|
" = ()>",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 174..206,
|
range: 174..206,
|
||||||
|
@ -521,6 +532,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
" = ()>",
|
" = ()>",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 174..189,
|
range: 174..189,
|
||||||
|
@ -541,6 +553,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -590,6 +603,16 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: Some(
|
||||||
|
TextEdit {
|
||||||
|
indels: [
|
||||||
|
Indel {
|
||||||
|
insert: ": Struct",
|
||||||
|
delete: 130..130,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 145..185,
|
range: 145..185,
|
||||||
|
@ -610,6 +633,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 145..168,
|
range: 145..168,
|
||||||
|
@ -630,6 +654,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
InlayHint {
|
InlayHint {
|
||||||
range: 222..228,
|
range: 222..228,
|
||||||
|
@ -648,6 +673,7 @@ fn main() {
|
||||||
tooltip: "",
|
tooltip: "",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
text_edit: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
|
|
@ -112,6 +112,7 @@ pub(super) fn hints(
|
||||||
range: closing_token.text_range(),
|
range: closing_token.text_range(),
|
||||||
kind: InlayKind::ClosingBrace,
|
kind: InlayKind::ClosingBrace,
|
||||||
label: InlayHintLabel::simple(label, None, linked_location),
|
label: InlayHintLabel::simple(label, None, linked_location),
|
||||||
|
text_edit: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
//! Implementation of "closure return type" inlay hints.
|
//! Implementation of "closure return type" inlay hints.
|
||||||
|
//!
|
||||||
|
//! Tests live in [`bind_pat`][super::bind_pat] module.
|
||||||
use ide_db::{base_db::FileId, famous_defs::FamousDefs};
|
use ide_db::{base_db::FileId, famous_defs::FamousDefs};
|
||||||
use syntax::ast::{self, AstNode};
|
use syntax::ast::{self, AstNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
inlay_hints::closure_has_block_body, ClosureReturnTypeHints, InlayHint, InlayHintsConfig,
|
inlay_hints::{closure_has_block_body, label_of_ty, ty_to_text_edit},
|
||||||
InlayKind,
|
ClosureReturnTypeHints, InlayHint, InlayHintsConfig, InlayKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::label_of_ty;
|
|
||||||
|
|
||||||
pub(super) fn hints(
|
pub(super) fn hints(
|
||||||
acc: &mut Vec<InlayHint>,
|
acc: &mut Vec<InlayHint>,
|
||||||
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
|
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
|
||||||
|
@ -24,25 +24,39 @@ pub(super) fn hints(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !closure_has_block_body(&closure)
|
let has_block_body = closure_has_block_body(&closure);
|
||||||
&& config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock
|
if !has_block_body && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock {
|
||||||
{
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let param_list = closure.param_list()?;
|
let param_list = closure.param_list()?;
|
||||||
|
|
||||||
let closure = sema.descend_node_into_attributes(closure).pop()?;
|
let closure = sema.descend_node_into_attributes(closure).pop()?;
|
||||||
let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted();
|
let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure.clone()))?.adjusted();
|
||||||
let callable = ty.as_callable(sema.db)?;
|
let callable = ty.as_callable(sema.db)?;
|
||||||
let ty = callable.return_type();
|
let ty = callable.return_type();
|
||||||
if ty.is_unit() {
|
if ty.is_unit() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME?: We could provide text edit to insert braces for closures with non-block body.
|
||||||
|
let text_edit = if has_block_body {
|
||||||
|
ty_to_text_edit(
|
||||||
|
sema,
|
||||||
|
closure.syntax(),
|
||||||
|
&ty,
|
||||||
|
param_list.syntax().text_range().end(),
|
||||||
|
String::from(" -> "),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
acc.push(InlayHint {
|
acc.push(InlayHint {
|
||||||
range: param_list.syntax().text_range(),
|
range: param_list.syntax().text_range(),
|
||||||
kind: InlayKind::ClosureReturnType,
|
kind: InlayKind::ClosureReturnType,
|
||||||
label: label_of_ty(famous_defs, config, ty)?,
|
label: label_of_ty(famous_defs, config, ty)?,
|
||||||
|
text_edit,
|
||||||
});
|
});
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ fn variant_hints(
|
||||||
})),
|
})),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
|
text_edit: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub(super) fn hints(
|
||||||
range: t.text_range(),
|
range: t.text_range(),
|
||||||
kind: InlayKind::Lifetime,
|
kind: InlayKind::Lifetime,
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
|
text_edit: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let param_list = func.param_list()?;
|
let param_list = func.param_list()?;
|
||||||
|
@ -189,12 +190,14 @@ pub(super) fn hints(
|
||||||
if is_empty { "" } else { ", " }
|
if is_empty { "" } else { ", " }
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
|
text_edit: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
(None, allocated_lifetimes) => acc.push(InlayHint {
|
(None, allocated_lifetimes) => acc.push(InlayHint {
|
||||||
range: func.name()?.syntax().text_range(),
|
range: func.name()?.syntax().text_range(),
|
||||||
kind: InlayKind::GenericParamList,
|
kind: InlayKind::GenericParamList,
|
||||||
label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
|
label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
|
||||||
|
text_edit: None,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
|
|
|
@ -34,6 +34,7 @@ pub(super) fn hints(
|
||||||
range: t.text_range(),
|
range: t.text_range(),
|
||||||
kind: InlayKind::Lifetime,
|
kind: InlayKind::Lifetime,
|
||||||
label: "'static".to_owned().into(),
|
label: "'static".to_owned().into(),
|
||||||
|
text_edit: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ pub(super) fn hints(
|
||||||
range,
|
range,
|
||||||
kind: InlayKind::Parameter,
|
kind: InlayKind::Parameter,
|
||||||
label: InlayHintLabel::simple(param_name, None, linked_location),
|
label: InlayHintLabel::simple(param_name, None, linked_location),
|
||||||
|
text_edit: None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -510,7 +510,7 @@ pub(crate) fn inlay_hint(
|
||||||
| InlayKind::AdjustmentPostfix
|
| InlayKind::AdjustmentPostfix
|
||||||
| InlayKind::ClosingBrace => None,
|
| InlayKind::ClosingBrace => None,
|
||||||
},
|
},
|
||||||
text_edits: None,
|
text_edits: inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)),
|
||||||
data: None,
|
data: None,
|
||||||
tooltip,
|
tooltip,
|
||||||
label,
|
label,
|
||||||
|
|
Loading…
Reference in a new issue