Added new inlay hint kind and rules for method chaining

This commit is contained in:
Matt Hooper 2020-03-23 20:32:05 +01:00
parent fae627174a
commit a197abbc7a
5 changed files with 98 additions and 5 deletions

View file

@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase;
use ra_prof::profile; use ra_prof::profile;
use ra_syntax::{ use ra_syntax::{
ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner},
match_ast, SmolStr, TextRange, match_ast, SmolStr, TextRange, NodeOrToken, SyntaxKind, Direction
}; };
use crate::{FileId, FunctionSignature}; use crate::{FileId, FunctionSignature};
@ -14,12 +14,18 @@ use crate::{FileId, FunctionSignature};
pub struct InlayHintsOptions { pub struct InlayHintsOptions {
pub type_hints: bool, pub type_hints: bool,
pub parameter_hints: bool, pub parameter_hints: bool,
pub chaining_hints: bool,
pub max_length: Option<usize>, pub max_length: Option<usize>,
} }
impl Default for InlayHintsOptions { impl Default for InlayHintsOptions {
fn default() -> Self { fn default() -> Self {
Self { type_hints: true, parameter_hints: true, max_length: None } Self {
type_hints: true,
parameter_hints: true,
chaining_hints: true,
max_length: None
}
} }
} }
@ -27,6 +33,7 @@ impl Default for InlayHintsOptions {
pub enum InlayKind { pub enum InlayKind {
TypeHint, TypeHint,
ParameterHint, ParameterHint,
ChainingHint,
} }
#[derive(Debug)] #[derive(Debug)]
@ -47,6 +54,10 @@ pub(crate) fn inlay_hints(
let mut res = Vec::new(); let mut res = Vec::new();
for node in file.syntax().descendants() { for node in file.syntax().descendants() {
if let Some(expr) = ast::Expr::cast(node.clone()) {
get_chaining_hints(&mut res, &sema, options, expr);
}
match_ast! { match_ast! {
match node { match node {
ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); },
@ -222,6 +233,45 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<
} }
} }
fn get_chaining_hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>,
options: &InlayHintsOptions,
expr: ast::Expr,
) -> Option<()> {
if !options.chaining_hints {
return None;
}
let ty = sema.type_of_expr(&expr)?;
let label = ty.display_truncated(sema.db, options.max_length).to_string();
if ty.is_unknown() {
return None;
}
let mut tokens = expr.syntax()
.siblings_with_tokens(Direction::Next)
.filter_map(NodeOrToken::into_token)
.filter(|t| match t.kind() {
SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
SyntaxKind::COMMENT => false,
_ => true,
});
// Chaining can be defined as an expression whose next sibling tokens are newline and dot
// Ignoring extra whitespace and comments
let next = tokens.next()?.kind();
let next_next = tokens.next()?.kind();
if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT {
acc.push(InlayHint {
range: expr.syntax().text_range(),
kind: InlayKind::ChainingHint,
label: label.into(),
});
}
Some(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::inlay_hints::InlayHintsOptions; use crate::inlay_hints::InlayHintsOptions;
@ -229,6 +279,43 @@ mod tests {
use crate::mock_analysis::single_file; use crate::mock_analysis::single_file;
#[test]
fn generic_chaining_hints() {
let (analysis, file_id) = single_file(
r#"
struct A<T>(T);
struct B<T>(T);
struct C<T>(T);
struct X<T,R>(T, R);
impl<T> A<T> {
fn new(t: T) -> Self { A(t) }
fn into_b(self) -> B<T> { B(self.0) }
}
impl<T> B<T> {
fn into_c(self) -> C<T> { C(self.0) }
}
fn test() {
let c = A::new(X(42, true))
.into_b() // All the from A -> B -> C
.into_c();
}"#,
);
assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
[
InlayHint {
range: [416; 465),
kind: ChainingHint,
label: "B<X<i32, bool>>",
},
InlayHint {
range: [416; 435),
kind: ChainingHint,
label: "A<X<i32, bool>>",
},
]"###);
}
#[test] #[test]
fn param_hints_only() { fn param_hints_only() {
let (analysis, file_id) = single_file( let (analysis, file_id) = single_file(
@ -238,7 +325,7 @@ mod tests {
let _x = foo(4, 4); let _x = foo(4, 4);
}"#, }"#,
); );
assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, max_length: None}).unwrap(), @r###" assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
[ [
InlayHint { InlayHint {
range: [106; 107), range: [106; 107),
@ -262,7 +349,7 @@ mod tests {
let _x = foo(4, 4); let _x = foo(4, 4);
}"#, }"#,
); );
assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, max_length: None}).unwrap(), @r###"[]"###); assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###);
} }
#[test] #[test]
@ -274,7 +361,7 @@ mod tests {
let _x = foo(4, 4); let _x = foo(4, 4);
}"#, }"#,
); );
assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, max_length: None}).unwrap(), @r###" assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
[ [
InlayHint { InlayHint {
range: [97; 99), range: [97; 99),

View file

@ -34,6 +34,8 @@ pub struct ServerConfig {
pub inlay_hints_type: bool, pub inlay_hints_type: bool,
#[serde(deserialize_with = "nullable_bool_true")] #[serde(deserialize_with = "nullable_bool_true")]
pub inlay_hints_parameter: bool, pub inlay_hints_parameter: bool,
#[serde(deserialize_with = "nullable_bool_true")]
pub inlay_hints_chaining: bool,
pub inlay_hints_max_length: Option<usize>, pub inlay_hints_max_length: Option<usize>,
pub cargo_watch_enable: bool, pub cargo_watch_enable: bool,
@ -66,6 +68,7 @@ impl Default for ServerConfig {
lru_capacity: None, lru_capacity: None,
inlay_hints_type: true, inlay_hints_type: true,
inlay_hints_parameter: true, inlay_hints_parameter: true,
inlay_hints_chaining: true,
inlay_hints_max_length: None, inlay_hints_max_length: None,
cargo_watch_enable: true, cargo_watch_enable: true,
cargo_watch_args: Vec::new(), cargo_watch_args: Vec::new(),

View file

@ -332,6 +332,7 @@ impl ConvWith<&LineIndex> for InlayHint {
kind: match self.kind { kind: match self.kind {
InlayKind::ParameterHint => req::InlayKind::ParameterHint, InlayKind::ParameterHint => req::InlayKind::ParameterHint,
InlayKind::TypeHint => req::InlayKind::TypeHint, InlayKind::TypeHint => req::InlayKind::TypeHint,
InlayKind::ChainingHint => req::InlayKind::ChainingHint,
}, },
} }
} }

View file

@ -183,6 +183,7 @@ pub fn main_loop(
inlay_hints: InlayHintsOptions { inlay_hints: InlayHintsOptions {
type_hints: config.inlay_hints_type, type_hints: config.inlay_hints_type,
parameter_hints: config.inlay_hints_parameter, parameter_hints: config.inlay_hints_parameter,
chaining_hints: config.inlay_hints_chaining,
max_length: config.inlay_hints_max_length, max_length: config.inlay_hints_max_length,
}, },
cargo_watch: CheckOptions { cargo_watch: CheckOptions {

View file

@ -200,6 +200,7 @@ pub struct InlayHintsParams {
pub enum InlayKind { pub enum InlayKind {
TypeHint, TypeHint,
ParameterHint, ParameterHint,
ChainingHint,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]