11731: feat: Add return type hints for closures with block bodies r=Veykril a=Veykril

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11386
bors r+

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2022-03-16 20:31:47 +00:00 committed by GitHub
commit b3cfa1986b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 116 deletions

View File

@ -16,6 +16,7 @@ pub struct InlayHintsConfig {
pub type_hints: bool, pub type_hints: bool,
pub parameter_hints: bool, pub parameter_hints: bool,
pub chaining_hints: bool, pub chaining_hints: bool,
pub closure_return_type_hints: bool,
pub hide_named_constructor_hints: bool, pub hide_named_constructor_hints: bool,
pub max_length: Option<usize>, pub max_length: Option<usize>,
} }
@ -24,6 +25,7 @@ pub struct InlayHintsConfig {
pub enum InlayKind { pub enum InlayKind {
TypeHint, TypeHint,
ParameterHint, ParameterHint,
ClosureReturnTypeHint,
ChainingHint, ChainingHint,
} }
@ -67,48 +69,86 @@ pub(crate) fn inlay_hints(
let file = sema.parse(file_id); let file = sema.parse(file_id);
let file = file.syntax(); 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 { match range_limit {
Some(FileRange { range, .. }) => match file.covering_element(range) { Some(FileRange { range, .. }) => match file.covering_element(range) {
NodeOrToken::Token(_) => return hints, NodeOrToken::Token(_) => return acc,
NodeOrToken::Node(n) => n NodeOrToken::Node(n) => n
.descendants() .descendants()
.filter(|descendant| range.contains_range(descendant.text_range())) .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<InlayHint>, hints: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
node: SyntaxNode, 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()) { 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 { match expr {
ast::Expr::CallExpr(it) => { 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) => { 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) { } 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<InlayHint>, acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
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<InlayHint>,
sema: &Semantics<RootDatabase>,
famous_defs: &FamousDefs,
config: &InlayHintsConfig, config: &InlayHintsConfig,
expr: &ast::Expr, expr: &ast::Expr,
) -> Option<()> { ) -> Option<()> {
@ -122,8 +162,6 @@ fn get_chaining_hints(
let descended = sema.descend_node_into_attributes(expr.clone()).pop(); let descended = sema.descend_node_into_attributes(expr.clone()).pop();
let desc_expr = descended.as_ref().unwrap_or(expr); 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 let mut tokens = expr
.syntax() .syntax()
@ -167,7 +205,7 @@ fn get_chaining_hints(
Some(()) Some(())
} }
fn get_param_name_hints( fn param_name_hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
@ -207,7 +245,7 @@ fn get_param_name_hints(
Some(()) Some(())
} }
fn get_bind_pat_hints( fn bind_pat_hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
@ -567,13 +605,21 @@ mod tests {
use crate::{fixture, inlay_hints::InlayHintsConfig}; 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 { const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
render_colons: true,
type_hints: true, type_hints: true,
parameter_hints: true, parameter_hints: true,
chaining_hints: true, chaining_hints: true,
hide_named_constructor_hints: false, closure_return_type_hints: true,
max_length: None, ..DISABLED_CONFIG
}; };
#[track_caller] #[track_caller]
@ -584,46 +630,19 @@ mod tests {
#[track_caller] #[track_caller]
fn check_params(ra_fixture: &str) { fn check_params(ra_fixture: &str) {
check_with_config( check_with_config(
InlayHintsConfig { InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
render_colons: true,
parameter_hints: true,
type_hints: false,
chaining_hints: false,
hide_named_constructor_hints: false,
max_length: None,
},
ra_fixture, ra_fixture,
); );
} }
#[track_caller] #[track_caller]
fn check_types(ra_fixture: &str) { fn check_types(ra_fixture: &str) {
check_with_config( check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture);
InlayHintsConfig {
render_colons: true,
parameter_hints: false,
type_hints: true,
chaining_hints: false,
hide_named_constructor_hints: false,
max_length: None,
},
ra_fixture,
);
} }
#[track_caller] #[track_caller]
fn check_chains(ra_fixture: &str) { fn check_chains(ra_fixture: &str) {
check_with_config( check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
InlayHintsConfig {
render_colons: true,
parameter_hints: false,
type_hints: false,
chaining_hints: true,
hide_named_constructor_hints: false,
max_length: None,
},
ra_fixture,
);
} }
#[track_caller] #[track_caller]
@ -646,14 +665,7 @@ mod tests {
#[test] #[test]
fn hints_disabled() { fn hints_disabled() {
check_with_config( check_with_config(
InlayHintsConfig { InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG },
render_colons: true,
type_hints: false,
parameter_hints: false,
chaining_hints: false,
hide_named_constructor_hints: false,
max_length: None,
},
r#" r#"
fn foo(a: i32, b: i32) -> i32 { a + b } fn foo(a: i32, b: i32) -> i32 { a + b }
fn main() { fn main() {
@ -1102,14 +1114,7 @@ fn main() {
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
let inlay_hints = analysis let inlay_hints = analysis
.inlay_hints( .inlay_hints(
&InlayHintsConfig { &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
render_colons: true,
parameter_hints: false,
type_hints: true,
chaining_hints: false,
hide_named_constructor_hints: false,
max_length: None,
},
file_id, file_id,
Some(FileRange { Some(FileRange {
file_id, file_id,
@ -1418,7 +1423,7 @@ fn main() {
parameter_hints: true, parameter_hints: true,
chaining_hints: true, chaining_hints: true,
hide_named_constructor_hints: true, hide_named_constructor_hints: true,
max_length: None, ..DISABLED_CONFIG
}, },
r#" r#"
//- minicore: try, option //- minicore: try, option
@ -1546,13 +1551,14 @@ fn fallible() -> ControlFlow<()> {
fn main() { fn main() {
let mut start = 0; let mut start = 0;
//^^^^^ i32 //^^^^^ i32
(0..2).for_each(|increment| { start += increment; }); (0..2).for_each(|increment | { start += increment; });
//^^^^^^^^^ i32 //^^^^^^^^^ i32
let multiply = let multiply =
//^^^^^^^^ |i32, i32| -> i32 //^^^^^^^^ |i32, i32| -> i32
| a, b| a * b | a, b| a * b
//^ i32 ^ i32 //^ i32 ^ i32
; ;
let _: i32 = multiply(1, 2); let _: i32 = multiply(1, 2);
@ -1561,6 +1567,8 @@ fn main() {
let return_42 = || 42; let return_42 = || 42;
//^^^^^^^^^ || -> i32 //^^^^^^^^^ || -> i32
|| { 42 };
//^^ i32
}"#, }"#,
); );
} }
@ -1590,14 +1598,7 @@ fn main() {
#[test] #[test]
fn chaining_hints_ignore_comments() { fn chaining_hints_ignore_comments() {
check_expect( check_expect(
InlayHintsConfig { InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
render_colons: true,
parameter_hints: false,
type_hints: false,
chaining_hints: true,
hide_named_constructor_hints: false,
max_length: None,
},
r#" r#"
struct A(B); struct A(B);
impl A { fn into_b(self) -> B { self.0 } } impl A { fn into_b(self) -> B { self.0 } }
@ -1648,14 +1649,7 @@ fn main() {
#[test] #[test]
fn struct_access_chaining_hints() { fn struct_access_chaining_hints() {
check_expect( check_expect(
InlayHintsConfig { InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
render_colons: true,
parameter_hints: false,
type_hints: false,
chaining_hints: true,
hide_named_constructor_hints: false,
max_length: None,
},
r#" r#"
struct A { pub b: B } struct A { pub b: B }
struct B { pub c: C } struct B { pub c: C }
@ -1694,14 +1688,7 @@ fn main() {
#[test] #[test]
fn generic_chaining_hints() { fn generic_chaining_hints() {
check_expect( check_expect(
InlayHintsConfig { InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
render_colons: true,
parameter_hints: false,
type_hints: false,
chaining_hints: true,
hide_named_constructor_hints: false,
max_length: None,
},
r#" r#"
struct A<T>(T); struct A<T>(T);
struct B<T>(T); struct B<T>(T);
@ -1741,14 +1728,7 @@ fn main() {
#[test] #[test]
fn shorten_iterator_chaining_hints() { fn shorten_iterator_chaining_hints() {
check_expect( check_expect(
InlayHintsConfig { InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
render_colons: true,
parameter_hints: false,
type_hints: false,
chaining_hints: true,
hide_named_constructor_hints: false,
max_length: None,
},
r#" r#"
//- minicore: iterators //- minicore: iterators
use core::iter; use core::iter;

View File

@ -109,6 +109,7 @@ impl StaticIndex<'_> {
type_hints: true, type_hints: true,
parameter_hints: true, parameter_hints: true,
chaining_hints: true, chaining_hints: true,
closure_return_type_hints: true,
hide_named_constructor_hints: false, hide_named_constructor_hints: false,
max_length: Some(25), max_length: Some(25),
}, },

View File

@ -244,8 +244,6 @@ config_data! {
/// Whether to render trailing colons for parameter hints, and trailing colons for parameter hints. /// Whether to render trailing colons for parameter hints, and trailing colons for parameter hints.
inlayHints_renderColons: bool = "true", 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. /// Maximum length for inlay hints. Set to null to have an unlimited length.
inlayHints_maxLength: Option<usize> = "25", inlayHints_maxLength: Option<usize> = "25",
/// Whether to show function parameter name inlay hints at the call /// Whether to show function parameter name inlay hints at the call
@ -253,6 +251,10 @@ config_data! {
inlayHints_parameterHints: bool = "true", inlayHints_parameterHints: bool = "true",
/// Whether to show inlay type hints for variables. /// Whether to show inlay type hints for variables.
inlayHints_typeHints: bool = "true", 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. /// Whether to hide inlay hints for constructors.
inlayHints_hideNamedConstructorHints: bool = "false", inlayHints_hideNamedConstructorHints: bool = "false",
@ -852,6 +854,7 @@ impl Config {
type_hints: self.data.inlayHints_typeHints, type_hints: self.data.inlayHints_typeHints,
parameter_hints: self.data.inlayHints_parameterHints, parameter_hints: self.data.inlayHints_parameterHints,
chaining_hints: self.data.inlayHints_chainingHints, chaining_hints: self.data.inlayHints_chainingHints,
closure_return_type_hints: self.data.inlayHints_closureReturnTypeHints,
hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints, hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints,
max_length: self.data.inlayHints_maxLength, max_length: self.data.inlayHints_maxLength,
} }

View File

@ -422,26 +422,31 @@ pub(crate) fn inlay_hint(
label: lsp_ext::InlayHintLabel::String(match inlay_hint.kind { label: lsp_ext::InlayHintLabel::String(match inlay_hint.kind {
InlayKind::ParameterHint if render_colons => format!("{}:", inlay_hint.label), InlayKind::ParameterHint if render_colons => format!("{}:", inlay_hint.label),
InlayKind::TypeHint 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(), _ => inlay_hint.label.to_string(),
}), }),
position: match inlay_hint.kind { position: match inlay_hint.kind {
InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()), 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()) position(line_index, inlay_hint.range.end())
} }
}, },
kind: match inlay_hint.kind { kind: match inlay_hint.kind {
InlayKind::ParameterHint => Some(lsp_ext::InlayHintKind::PARAMETER), 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, tooltip: None,
padding_left: Some(match inlay_hint.kind { padding_left: Some(match inlay_hint.kind {
InlayKind::TypeHint => !render_colons, InlayKind::TypeHint => !render_colons,
InlayKind::ParameterHint => false, InlayKind::ParameterHint | InlayKind::ClosureReturnTypeHint => false,
InlayKind::ChainingHint => true, InlayKind::ChainingHint => true,
}), }),
padding_right: Some(match inlay_hint.kind { padding_right: Some(match inlay_hint.kind {
InlayKind::TypeHint | InlayKind::ChainingHint => false, InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => {
false
}
InlayKind::ParameterHint => true, InlayKind::ParameterHint => true,
}), }),
} }

View File

@ -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. 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`):: [[rust-analyzer.inlayHints.maxLength]]rust-analyzer.inlayHints.maxLength (default: `25`)::
+ +
-- --
@ -373,6 +368,16 @@ site.
-- --
Whether to show inlay type hints for variables. 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`):: [[rust-analyzer.inlayHints.hideNamedConstructorHints]]rust-analyzer.inlayHints.hideNamedConstructorHints (default: `false`)::
+ +
-- --

View File

@ -771,11 +771,6 @@
"default": true, "default": true,
"type": "boolean" "type": "boolean"
}, },
"rust-analyzer.inlayHints.chainingHints": {
"markdownDescription": "Whether to show inlay type hints for method chains.",
"default": true,
"type": "boolean"
},
"rust-analyzer.inlayHints.maxLength": { "rust-analyzer.inlayHints.maxLength": {
"markdownDescription": "Maximum length for inlay hints. Set to null to have an unlimited length.", "markdownDescription": "Maximum length for inlay hints. Set to null to have an unlimited length.",
"default": 25, "default": 25,
@ -795,6 +790,16 @@
"default": true, "default": true,
"type": "boolean" "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": { "rust-analyzer.inlayHints.hideNamedConstructorHints": {
"markdownDescription": "Whether to hide inlay hints for constructors.", "markdownDescription": "Whether to hide inlay hints for constructors.",
"default": false, "default": false,

View File

@ -103,6 +103,7 @@ export class Config {
typeHints: this.get<boolean>("inlayHints.typeHints"), typeHints: this.get<boolean>("inlayHints.typeHints"),
parameterHints: this.get<boolean>("inlayHints.parameterHints"), parameterHints: this.get<boolean>("inlayHints.parameterHints"),
chainingHints: this.get<boolean>("inlayHints.chainingHints"), chainingHints: this.get<boolean>("inlayHints.chainingHints"),
closureReturnTypeHints: this.get<boolean>("inlayHints.closureReturnTypeHints"),
hideNamedConstructorHints: this.get<boolean>("inlayHints.hideNamedConstructorHints"), hideNamedConstructorHints: this.get<boolean>("inlayHints.hideNamedConstructorHints"),
smallerHints: this.get<boolean>("inlayHints.smallerHints"), smallerHints: this.get<boolean>("inlayHints.smallerHints"),
maxLength: this.get<null | number>("inlayHints.maxLength"), maxLength: this.get<null | number>("inlayHints.maxLength"),

View File

@ -14,7 +14,8 @@ export function activateInlayHints(ctx: Ctx) {
const anyEnabled = ctx.config.inlayHints.typeHints const anyEnabled = ctx.config.inlayHints.typeHints
|| ctx.config.inlayHints.parameterHints || ctx.config.inlayHints.parameterHints
|| ctx.config.inlayHints.chainingHints; || ctx.config.inlayHints.chainingHints
|| ctx.config.inlayHints.closureReturnTypeHints;
const enabled = ctx.config.inlayHints.enable && anyEnabled; const enabled = ctx.config.inlayHints.enable && anyEnabled;
if (!enabled) return; if (!enabled) return;