mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-14 02:49:40 +00:00
Implement text edits for inlay hints
This commit is contained in:
parent
fcbc250723
commit
c978d4bf0c
@ -14,7 +14,7 @@ use smallvec::{smallvec, SmallVec};
|
||||
use stdx::never;
|
||||
use syntax::{
|
||||
ast::{self, AstNode},
|
||||
match_ast, NodeOrToken, SyntaxNode, TextRange,
|
||||
match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
|
||||
};
|
||||
use text_edit::TextEdit;
|
||||
|
||||
@ -359,6 +359,23 @@ fn label_of_ty(
|
||||
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
|
||||
//
|
||||
// rust-analyzer shows additional information inline with the source code.
|
||||
@ -566,6 +583,37 @@ mod tests {
|
||||
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]
|
||||
fn hints_disabled() {
|
||||
check_with_config(
|
||||
|
@ -13,7 +13,7 @@ use syntax::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
inlay_hints::{closure_has_block_body, label_of_ty},
|
||||
inlay_hints::{closure_has_block_body, label_of_ty, ty_to_text_edit},
|
||||
InlayHint, InlayHintsConfig, InlayKind,
|
||||
};
|
||||
|
||||
@ -36,7 +36,7 @@ pub(super) fn hints(
|
||||
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
|
||||
&& is_named_constructor(sema, pat, &label.to_string()).is_some()
|
||||
@ -44,6 +44,23 @@ pub(super) fn hints(
|
||||
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 {
|
||||
range: match pat.name() {
|
||||
Some(name) => name.syntax().text_range(),
|
||||
@ -51,7 +68,7 @@ pub(super) fn hints(
|
||||
},
|
||||
kind: InlayKind::Type,
|
||||
label,
|
||||
text_edit: None,
|
||||
text_edit,
|
||||
});
|
||||
|
||||
Some(())
|
||||
@ -178,14 +195,16 @@ fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir
|
||||
mod tests {
|
||||
// This module also contains tests for super::closure_ret
|
||||
|
||||
use expect_test::expect;
|
||||
use hir::ClosureStyle;
|
||||
use syntax::{TextRange, TextSize};
|
||||
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::ClosureReturnTypeHints;
|
||||
use crate::inlay_hints::tests::{
|
||||
check, check_edit, check_no_edit, check_with_config, DISABLED_CONFIG, TEST_CONFIG,
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
fn check_types(ra_fixture: &str) {
|
||||
@ -1014,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);
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -603,7 +603,16 @@ fn main() {
|
||||
},
|
||||
"",
|
||||
],
|
||||
text_edit: None,
|
||||
text_edit: Some(
|
||||
TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: ": Struct",
|
||||
delete: 130..130,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
InlayHint {
|
||||
range: 145..185,
|
||||
|
@ -1,14 +1,14 @@
|
||||
//! 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 syntax::ast::{self, AstNode};
|
||||
|
||||
use crate::{
|
||||
inlay_hints::closure_has_block_body, ClosureReturnTypeHints, InlayHint, InlayHintsConfig,
|
||||
InlayKind,
|
||||
inlay_hints::{closure_has_block_body, label_of_ty, ty_to_text_edit},
|
||||
ClosureReturnTypeHints, InlayHint, InlayHintsConfig, InlayKind,
|
||||
};
|
||||
|
||||
use super::label_of_ty;
|
||||
|
||||
pub(super) fn hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
|
||||
@ -24,26 +24,39 @@ pub(super) fn hints(
|
||||
return None;
|
||||
}
|
||||
|
||||
if !closure_has_block_body(&closure)
|
||||
&& config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock
|
||||
{
|
||||
let has_block_body = closure_has_block_body(&closure);
|
||||
if !has_block_body && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock {
|
||||
return None;
|
||||
}
|
||||
|
||||
let param_list = closure.param_list()?;
|
||||
|
||||
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 ty = callable.return_type();
|
||||
if ty.is_unit() {
|
||||
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 {
|
||||
range: param_list.syntax().text_range(),
|
||||
kind: InlayKind::ClosureReturnType,
|
||||
label: label_of_ty(famous_defs, config, ty)?,
|
||||
text_edit: None,
|
||||
text_edit,
|
||||
});
|
||||
Some(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user