From a197abbc7aed53c42cac7e9e86787e44a5026291 Mon Sep 17 00:00:00 2001 From: Matt Hooper Date: Mon, 23 Mar 2020 20:32:05 +0100 Subject: Added new inlay hint kind and rules for method chaining --- crates/ra_ide/src/inlay_hints.rs | 97 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 5 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index ecd615cf4..2353ad71f 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs @@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase; use ra_prof::profile; use ra_syntax::{ ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, - match_ast, SmolStr, TextRange, + match_ast, SmolStr, TextRange, NodeOrToken, SyntaxKind, Direction }; use crate::{FileId, FunctionSignature}; @@ -14,12 +14,18 @@ use crate::{FileId, FunctionSignature}; pub struct InlayHintsOptions { pub type_hints: bool, pub parameter_hints: bool, + pub chaining_hints: bool, pub max_length: Option, } impl Default for InlayHintsOptions { 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 { TypeHint, ParameterHint, + ChainingHint, } #[derive(Debug)] @@ -47,6 +54,10 @@ pub(crate) fn inlay_hints( let mut res = Vec::new(); 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 node { 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, expr: &ast::Expr) -> Option< } } +fn get_chaining_hints( + acc: &mut Vec, + sema: &Semantics, + 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)] mod tests { use crate::inlay_hints::InlayHintsOptions; @@ -229,6 +279,43 @@ mod tests { use crate::mock_analysis::single_file; + #[test] + fn generic_chaining_hints() { + let (analysis, file_id) = single_file( + r#" + struct A(T); + struct B(T); + struct C(T); + struct X(T, R); + + impl A { + fn new(t: T) -> Self { A(t) } + fn into_b(self) -> B { B(self.0) } + } + impl B { + fn into_c(self) -> C { 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>", + }, + InlayHint { + range: [416; 435), + kind: ChainingHint, + label: "A>", + }, + ]"###); + } + #[test] fn param_hints_only() { let (analysis, file_id) = single_file( @@ -238,7 +325,7 @@ mod tests { 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 { range: [106; 107), @@ -262,7 +349,7 @@ mod tests { 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] @@ -274,7 +361,7 @@ mod tests { 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 { range: [97; 99), -- cgit v1.2.3 From b70ce559b8d3102c3fed3ecef8edef3038a5ceed Mon Sep 17 00:00:00 2001 From: Matt Hooper Date: Tue, 24 Mar 2020 19:33:00 +0100 Subject: Added more unit tests --- crates/ra_ide/src/inlay_hints.rs | 235 ++++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 76 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 2353ad71f..293944206 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs @@ -70,6 +70,45 @@ pub(crate) fn inlay_hints( res } +fn get_chaining_hints( + acc: &mut Vec, + sema: &Semantics, + 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(()) +} + fn get_param_name_hints( acc: &mut Vec, sema: &Semantics, @@ -233,45 +272,6 @@ fn get_fn_signature(sema: &Semantics, expr: &ast::Expr) -> Option< } } -fn get_chaining_hints( - acc: &mut Vec, - sema: &Semantics, - 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)] mod tests { use crate::inlay_hints::InlayHintsOptions; @@ -279,43 +279,6 @@ mod tests { use crate::mock_analysis::single_file; - #[test] - fn generic_chaining_hints() { - let (analysis, file_id) = single_file( - r#" - struct A(T); - struct B(T); - struct C(T); - struct X(T, R); - - impl A { - fn new(t: T) -> Self { A(t) } - fn into_b(self) -> B { B(self.0) } - } - impl B { - fn into_c(self) -> C { 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>", - }, - InlayHint { - range: [416; 435), - kind: ChainingHint, - label: "A>", - }, - ]"###); - } - #[test] fn param_hints_only() { let (analysis, file_id) = single_file( @@ -1139,4 +1102,124 @@ fn main() { "### ); } + + #[test] + fn chaining_hints_ignore_comments() { + let (analysis, file_id) = single_file( + r#" + struct A(B); + impl A { fn into_b(self) -> B { self.0 } } + struct B(C) + impl B { fn into_c(self) -> C { self.0 } } + struct C; + + fn main() { + let c = A(B(C)) + .into_b() // This is a comment + .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: [231; 268), + kind: ChainingHint, + label: "B", + }, + InlayHint { + range: [231; 238), + kind: ChainingHint, + label: "A", + }, + ]"###); + } + + #[test] + fn chaining_hints_without_newlines() { + let (analysis, file_id) = single_file( + r#" + struct A(B); + impl A { fn into_b(self) -> B { self.0 } } + struct B(C) + impl B { fn into_c(self) -> C { self.0 } } + struct C; + + fn main() { + let c = A(B(C)).into_b().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###"[]"###); + } + + #[test] + fn struct_access_chaining_hints() { + let (analysis, file_id) = single_file( + r#" + struct A { pub b: B } + struct B { pub c: C } + struct C(pub bool); + + fn main() { + let x = A { b: B { c: C(true) } } + .b + .c + .0; + }"#, + ); + 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: [150; 221), + kind: ChainingHint, + label: "C", + }, + InlayHint { + range: [150; 198), + kind: ChainingHint, + label: "B", + }, + InlayHint { + range: [150; 175), + kind: ChainingHint, + label: "A", + }, + ]"###); + } + + #[test] + fn generic_chaining_hints() { + let (analysis, file_id) = single_file( + r#" + struct A(T); + struct B(T); + struct C(T); + struct X(T, R); + + impl A { + fn new(t: T) -> Self { A(t) } + fn into_b(self) -> B { B(self.0) } + } + impl B { + fn into_c(self) -> C { C(self.0) } + } + fn main() { + let c = A::new(X(42, true)) + .into_b() + .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>", + }, + InlayHint { + range: [416; 435), + kind: ChainingHint, + label: "A>", + }, + ]"###); + } } -- cgit v1.2.3 From 9d298115a62343e207992bbceb0279522324bf75 Mon Sep 17 00:00:00 2001 From: Matt Hooper Date: Tue, 24 Mar 2020 20:31:02 +0100 Subject: Fmt corrections --- crates/ra_ide/src/inlay_hints.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 293944206..07aab45ee 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs @@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase; use ra_prof::profile; use ra_syntax::{ ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, - match_ast, SmolStr, TextRange, NodeOrToken, SyntaxKind, Direction + match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, }; use crate::{FileId, FunctionSignature}; @@ -20,12 +20,7 @@ pub struct InlayHintsOptions { impl Default for InlayHintsOptions { fn default() -> Self { - Self { - type_hints: true, - parameter_hints: true, - chaining_hints: true, - max_length: None - } + Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None } } } @@ -86,7 +81,8 @@ fn get_chaining_hints( return None; } - let mut tokens = expr.syntax() + let mut tokens = expr + .syntax() .siblings_with_tokens(Direction::Next) .filter_map(NodeOrToken::into_token) .filter(|t| match t.kind() { @@ -99,7 +95,7 @@ fn get_chaining_hints( // Ignoring extra whitespace and comments let next = tokens.next()?.kind(); let next_next = tokens.next()?.kind(); - if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { + if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { acc.push(InlayHint { range: expr.syntax().text_range(), kind: InlayKind::ChainingHint, @@ -1190,11 +1186,11 @@ fn main() { fn generic_chaining_hints() { let (analysis, file_id) = single_file( r#" - struct A(T); + struct A(T); struct B(T); struct C(T); struct X(T, R); - + impl A { fn new(t: T) -> Self { A(t) } fn into_b(self) -> B { B(self.0) } @@ -1211,12 +1207,12 @@ fn main() { 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), + range: [403; 452), kind: ChainingHint, label: "B>", }, InlayHint { - range: [416; 435), + range: [403; 422), kind: ChainingHint, label: "A>", }, -- cgit v1.2.3 From 7b35da04bf56a5461321a6dca515dcd29f44b57f Mon Sep 17 00:00:00 2001 From: Matt Hooper Date: Tue, 24 Mar 2020 23:50:25 +0100 Subject: Improvements based on code review feedback --- crates/ra_ide/src/inlay_hints.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 07aab45ee..f4f0751c0 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs @@ -76,7 +76,6 @@ fn get_chaining_hints( } 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; } @@ -96,6 +95,7 @@ fn get_chaining_hints( let next = tokens.next()?.kind(); let next_next = tokens.next()?.kind(); if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { + let label = ty.display_truncated(sema.db, options.max_length).to_string(); acc.push(InlayHint { range: expr.syntax().text_range(), kind: InlayKind::ChainingHint, @@ -1105,7 +1105,7 @@ fn main() { r#" struct A(B); impl A { fn into_b(self) -> B { self.0 } } - struct B(C) + struct B(C); impl B { fn into_c(self) -> C { self.0 } } struct C; @@ -1118,12 +1118,12 @@ fn main() { 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: [231; 268), + range: [232; 269), kind: ChainingHint, label: "B", }, InlayHint { - range: [231; 238), + range: [232; 239), kind: ChainingHint, label: "A", }, @@ -1136,7 +1136,7 @@ fn main() { r#" struct A(B); impl A { fn into_b(self) -> B { self.0 } } - struct B(C) + struct B(C); impl B { fn into_c(self) -> C { self.0 } } struct C; -- cgit v1.2.3