From bd17933c31b7e4e8b1750c59a8de2e4ab0268c33 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 16 Mar 2022 21:16:55 +0100 Subject: [PATCH] feat: Add return type hints for closures with block bodies --- crates/ide/src/inlay_hints.rs | 178 ++++++++++++--------------- crates/ide/src/static_index.rs | 1 + crates/rust-analyzer/src/config.rs | 7 +- crates/rust-analyzer/src/to_proto.rs | 13 +- docs/user/generated_config.adoc | 15 ++- editors/code/package.json | 15 ++- editors/code/src/config.ts | 1 + editors/code/src/inlay_hints.ts | 3 +- 8 files changed, 117 insertions(+), 116 deletions(-) diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 428b8d1109f..d04bd87b7bc 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -16,6 +16,7 @@ pub struct InlayHintsConfig { pub type_hints: bool, pub parameter_hints: bool, pub chaining_hints: bool, + pub closure_return_type_hints: bool, pub hide_named_constructor_hints: bool, pub max_length: Option, } @@ -24,6 +25,7 @@ pub struct InlayHintsConfig { pub enum InlayKind { TypeHint, ParameterHint, + ClosureReturnTypeHint, ChainingHint, } @@ -67,48 +69,86 @@ pub(crate) fn inlay_hints( let file = sema.parse(file_id); let file = file.syntax(); - let mut hints = Vec::new(); + let mut acc = Vec::new(); - let get_hints = |node| get_hints(&mut hints, &sema, config, node); + let hints = |node| hints(&mut acc, &sema, config, node); match range_limit { Some(FileRange { range, .. }) => match file.covering_element(range) { - NodeOrToken::Token(_) => return hints, + NodeOrToken::Token(_) => return acc, NodeOrToken::Node(n) => n .descendants() .filter(|descendant| range.contains_range(descendant.text_range())) - .for_each(get_hints), + .for_each(hints), }, - None => file.descendants().for_each(get_hints), + None => file.descendants().for_each(hints), }; - hints + acc } -fn get_hints( +fn hints( hints: &mut Vec, sema: &Semantics, config: &InlayHintsConfig, node: SyntaxNode, ) { + let krate = sema.scope(&node).module().map(|it| it.krate()); + let famous_defs = FamousDefs(sema, krate); if let Some(expr) = ast::Expr::cast(node.clone()) { - get_chaining_hints(hints, sema, config, &expr); + chaining_hints(hints, sema, &famous_defs, config, &expr); match expr { ast::Expr::CallExpr(it) => { - get_param_name_hints(hints, sema, config, ast::Expr::from(it)); + param_name_hints(hints, sema, config, ast::Expr::from(it)); } ast::Expr::MethodCallExpr(it) => { - get_param_name_hints(hints, sema, config, ast::Expr::from(it)); + param_name_hints(hints, sema, config, ast::Expr::from(it)); + } + ast::Expr::ClosureExpr(it) => { + closure_ret_hints(hints, sema, &famous_defs, config, it); } _ => (), } } else if let Some(it) = ast::IdentPat::cast(node) { - get_bind_pat_hints(hints, sema, config, &it); + bind_pat_hints(hints, sema, config, &it); } } -fn get_chaining_hints( +fn closure_ret_hints( acc: &mut Vec, sema: &Semantics, + famous_defs: &FamousDefs, + config: &InlayHintsConfig, + closure: ast::ClosureExpr, +) -> Option<()> { + if !config.closure_return_type_hints { + return None; + } + + let closure = sema.descend_node_into_attributes(closure.clone()).pop()?; + + let param_list = match closure.body() { + Some(ast::Expr::BlockExpr(_)) => closure.param_list()?, + _ => return None, + }; + let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted(); + let callable = ty.as_callable(sema.db)?; + let ty = callable.return_type(); + if ty.is_unit() { + return None; + } + acc.push(InlayHint { + range: param_list.syntax().text_range(), + kind: InlayKind::ClosureReturnTypeHint, + label: hint_iterator(sema, &famous_defs, config, &ty) + .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string().into()), + }); + Some(()) +} + +fn chaining_hints( + acc: &mut Vec, + sema: &Semantics, + famous_defs: &FamousDefs, config: &InlayHintsConfig, expr: &ast::Expr, ) -> Option<()> { @@ -122,8 +162,6 @@ fn get_chaining_hints( let descended = sema.descend_node_into_attributes(expr.clone()).pop(); let desc_expr = descended.as_ref().unwrap_or(expr); - let krate = sema.scope(desc_expr.syntax()).module().map(|it| it.krate()); - let famous_defs = FamousDefs(sema, krate); let mut tokens = expr .syntax() @@ -167,7 +205,7 @@ fn get_chaining_hints( Some(()) } -fn get_param_name_hints( +fn param_name_hints( acc: &mut Vec, sema: &Semantics, config: &InlayHintsConfig, @@ -207,7 +245,7 @@ fn get_param_name_hints( Some(()) } -fn get_bind_pat_hints( +fn bind_pat_hints( acc: &mut Vec, sema: &Semantics, config: &InlayHintsConfig, @@ -567,13 +605,21 @@ mod tests { use crate::{fixture, inlay_hints::InlayHintsConfig}; + const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { + render_colons: false, + type_hints: false, + parameter_hints: false, + chaining_hints: false, + hide_named_constructor_hints: false, + closure_return_type_hints: false, + max_length: None, + }; const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { - render_colons: true, type_hints: true, parameter_hints: true, chaining_hints: true, - hide_named_constructor_hints: false, - max_length: None, + closure_return_type_hints: true, + ..DISABLED_CONFIG }; #[track_caller] @@ -584,46 +630,19 @@ mod tests { #[track_caller] fn check_params(ra_fixture: &str) { check_with_config( - InlayHintsConfig { - render_colons: true, - parameter_hints: true, - type_hints: false, - chaining_hints: false, - hide_named_constructor_hints: false, - max_length: None, - }, + InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG }, ra_fixture, ); } #[track_caller] fn check_types(ra_fixture: &str) { - check_with_config( - InlayHintsConfig { - render_colons: true, - parameter_hints: false, - type_hints: true, - chaining_hints: false, - hide_named_constructor_hints: false, - max_length: None, - }, - ra_fixture, - ); + check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture); } #[track_caller] fn check_chains(ra_fixture: &str) { - check_with_config( - InlayHintsConfig { - render_colons: true, - parameter_hints: false, - type_hints: false, - chaining_hints: true, - hide_named_constructor_hints: false, - max_length: None, - }, - ra_fixture, - ); + check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture); } #[track_caller] @@ -646,14 +665,7 @@ mod tests { #[test] fn hints_disabled() { check_with_config( - InlayHintsConfig { - render_colons: true, - type_hints: false, - parameter_hints: false, - chaining_hints: false, - hide_named_constructor_hints: false, - max_length: None, - }, + InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG }, r#" fn foo(a: i32, b: i32) -> i32 { a + b } fn main() { @@ -1102,14 +1114,7 @@ fn main() { let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); let inlay_hints = analysis .inlay_hints( - &InlayHintsConfig { - render_colons: true, - parameter_hints: false, - type_hints: true, - chaining_hints: false, - hide_named_constructor_hints: false, - max_length: None, - }, + &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, file_id, Some(FileRange { file_id, @@ -1418,7 +1423,7 @@ fn main() { parameter_hints: true, chaining_hints: true, hide_named_constructor_hints: true, - max_length: None, + ..DISABLED_CONFIG }, r#" //- minicore: try, option @@ -1546,13 +1551,14 @@ fn fallible() -> ControlFlow<()> { fn main() { let mut start = 0; //^^^^^ i32 - (0..2).for_each(|increment| { start += increment; }); + (0..2).for_each(|increment | { start += increment; }); //^^^^^^^^^ i32 let multiply = //^^^^^^^^ |i32, i32| -> i32 | a, b| a * b //^ i32 ^ i32 + ; let _: i32 = multiply(1, 2); @@ -1561,6 +1567,8 @@ fn main() { let return_42 = || 42; //^^^^^^^^^ || -> i32 + || { 42 }; + //^^ i32 }"#, ); } @@ -1590,14 +1598,7 @@ fn main() { #[test] fn chaining_hints_ignore_comments() { check_expect( - InlayHintsConfig { - render_colons: true, - parameter_hints: false, - type_hints: false, - chaining_hints: true, - hide_named_constructor_hints: false, - max_length: None, - }, + InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG }, r#" struct A(B); impl A { fn into_b(self) -> B { self.0 } } @@ -1648,14 +1649,7 @@ fn main() { #[test] fn struct_access_chaining_hints() { check_expect( - InlayHintsConfig { - render_colons: true, - parameter_hints: false, - type_hints: false, - chaining_hints: true, - hide_named_constructor_hints: false, - max_length: None, - }, + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, r#" struct A { pub b: B } struct B { pub c: C } @@ -1694,14 +1688,7 @@ fn main() { #[test] fn generic_chaining_hints() { check_expect( - InlayHintsConfig { - render_colons: true, - parameter_hints: false, - type_hints: false, - chaining_hints: true, - hide_named_constructor_hints: false, - max_length: None, - }, + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, r#" struct A(T); struct B(T); @@ -1741,14 +1728,7 @@ fn main() { #[test] fn shorten_iterator_chaining_hints() { check_expect( - InlayHintsConfig { - render_colons: true, - parameter_hints: false, - type_hints: false, - chaining_hints: true, - hide_named_constructor_hints: false, - max_length: None, - }, + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, r#" //- minicore: iterators use core::iter; diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index ef3487545ca..a06793cf516 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -109,6 +109,7 @@ impl StaticIndex<'_> { type_hints: true, parameter_hints: true, chaining_hints: true, + closure_return_type_hints: true, hide_named_constructor_hints: false, max_length: Some(25), }, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index a93e18a9b09..3d5737316bf 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -244,8 +244,6 @@ config_data! { /// Whether to render trailing colons for parameter hints, and trailing colons for parameter hints. inlayHints_renderColons: bool = "true", - /// Whether to show inlay type hints for method chains. - inlayHints_chainingHints: bool = "true", /// Maximum length for inlay hints. Set to null to have an unlimited length. inlayHints_maxLength: Option = "25", /// Whether to show function parameter name inlay hints at the call @@ -253,6 +251,10 @@ config_data! { inlayHints_parameterHints: bool = "true", /// Whether to show inlay type hints for variables. inlayHints_typeHints: bool = "true", + /// Whether to show inlay type hints for method chains. + inlayHints_chainingHints: bool = "true", + /// Whether to show inlay type hints for return types of closures with blocks. + inlayHints_closureReturnTypeHints: bool = "false", /// Whether to hide inlay hints for constructors. inlayHints_hideNamedConstructorHints: bool = "false", @@ -852,6 +854,7 @@ impl Config { type_hints: self.data.inlayHints_typeHints, parameter_hints: self.data.inlayHints_parameterHints, chaining_hints: self.data.inlayHints_chainingHints, + closure_return_type_hints: self.data.inlayHints_closureReturnTypeHints, hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints, max_length: self.data.inlayHints_maxLength, } diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 6a1c2f56ca5..e9e8bdb6c5f 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -422,26 +422,31 @@ pub(crate) fn inlay_hint( label: lsp_ext::InlayHintLabel::String(match inlay_hint.kind { InlayKind::ParameterHint if render_colons => format!("{}:", inlay_hint.label), InlayKind::TypeHint if render_colons => format!(": {}", inlay_hint.label), + InlayKind::ClosureReturnTypeHint => format!(" -> {}", inlay_hint.label), _ => inlay_hint.label.to_string(), }), position: match inlay_hint.kind { InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()), - InlayKind::TypeHint | InlayKind::ChainingHint => { + InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => { position(line_index, inlay_hint.range.end()) } }, kind: match inlay_hint.kind { InlayKind::ParameterHint => Some(lsp_ext::InlayHintKind::PARAMETER), - InlayKind::TypeHint | InlayKind::ChainingHint => Some(lsp_ext::InlayHintKind::TYPE), + InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => { + Some(lsp_ext::InlayHintKind::TYPE) + } }, tooltip: None, padding_left: Some(match inlay_hint.kind { InlayKind::TypeHint => !render_colons, - InlayKind::ParameterHint => false, + InlayKind::ParameterHint | InlayKind::ClosureReturnTypeHint => false, InlayKind::ChainingHint => true, }), padding_right: Some(match inlay_hint.kind { - InlayKind::TypeHint | InlayKind::ChainingHint => false, + InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => { + false + } InlayKind::ParameterHint => true, }), } diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index dd819abdd29..5793b4c057d 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -352,11 +352,6 @@ Whether to show `Run` action. Only applies when -- Whether to render trailing colons for parameter hints, and trailing colons for parameter hints. -- -[[rust-analyzer.inlayHints.chainingHints]]rust-analyzer.inlayHints.chainingHints (default: `true`):: -+ --- -Whether to show inlay type hints for method chains. --- [[rust-analyzer.inlayHints.maxLength]]rust-analyzer.inlayHints.maxLength (default: `25`):: + -- @@ -373,6 +368,16 @@ site. -- Whether to show inlay type hints for variables. -- +[[rust-analyzer.inlayHints.chainingHints]]rust-analyzer.inlayHints.chainingHints (default: `true`):: ++ +-- +Whether to show inlay type hints for method chains. +-- +[[rust-analyzer.inlayHints.closureReturnTypeHints]]rust-analyzer.inlayHints.closureReturnTypeHints (default: `false`):: ++ +-- +Whether to show inlay type hints for return types of closures with blocks. +-- [[rust-analyzer.inlayHints.hideNamedConstructorHints]]rust-analyzer.inlayHints.hideNamedConstructorHints (default: `false`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 6ccd6af80c0..83158197741 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -771,11 +771,6 @@ "default": true, "type": "boolean" }, - "rust-analyzer.inlayHints.chainingHints": { - "markdownDescription": "Whether to show inlay type hints for method chains.", - "default": true, - "type": "boolean" - }, "rust-analyzer.inlayHints.maxLength": { "markdownDescription": "Maximum length for inlay hints. Set to null to have an unlimited length.", "default": 25, @@ -795,6 +790,16 @@ "default": true, "type": "boolean" }, + "rust-analyzer.inlayHints.chainingHints": { + "markdownDescription": "Whether to show inlay type hints for method chains.", + "default": true, + "type": "boolean" + }, + "rust-analyzer.inlayHints.closureReturnTypeHints": { + "markdownDescription": "Whether to show inlay type hints for return types of closures with blocks.", + "default": false, + "type": "boolean" + }, "rust-analyzer.inlayHints.hideNamedConstructorHints": { "markdownDescription": "Whether to hide inlay hints for constructors.", "default": false, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index cdbaa67fe22..5d3ef404594 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -103,6 +103,7 @@ export class Config { typeHints: this.get("inlayHints.typeHints"), parameterHints: this.get("inlayHints.parameterHints"), chainingHints: this.get("inlayHints.chainingHints"), + closureReturnTypeHints: this.get("inlayHints.closureReturnTypeHints"), hideNamedConstructorHints: this.get("inlayHints.hideNamedConstructorHints"), smallerHints: this.get("inlayHints.smallerHints"), maxLength: this.get("inlayHints.maxLength"), diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 441370a677a..3f7ddedddf3 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -14,7 +14,8 @@ export function activateInlayHints(ctx: Ctx) { const anyEnabled = ctx.config.inlayHints.typeHints || ctx.config.inlayHints.parameterHints - || ctx.config.inlayHints.chainingHints; + || ctx.config.inlayHints.chainingHints + || ctx.config.inlayHints.closureReturnTypeHints; const enabled = ctx.config.inlayHints.enable && anyEnabled; if (!enabled) return;