Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inlay parameter name hints for call expr #2843

Merged
merged 3 commits into from
Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions crates/ra_ide/src/display/function_signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub struct FunctionSignature {
pub generic_parameters: Vec<String>,
/// Parameters of the function
pub parameters: Vec<String>,
/// Parameter names of the function
pub parameter_names: Vec<String>,
/// Optional return type
pub ret_type: Option<String>,
/// Where predicates
Expand Down Expand Up @@ -75,6 +77,7 @@ impl FunctionSignature {
name: node.name().map(|n| n.text().to_string()),
ret_type: node.name().map(|n| n.text().to_string()),
parameters: params,
parameter_names: vec![],
generic_parameters: generic_parameters(&node),
where_predicates: where_predicates(&node),
doc: None,
Expand Down Expand Up @@ -114,6 +117,7 @@ impl FunctionSignature {
name: Some(name),
ret_type: None,
parameters: params,
parameter_names: vec![],
generic_parameters: vec![],
where_predicates: vec![],
doc: None,
Expand All @@ -134,6 +138,7 @@ impl FunctionSignature {
name: node.name().map(|n| n.text().to_string()),
ret_type: None,
parameters: params,
parameter_names: vec![],
generic_parameters: vec![],
where_predicates: vec![],
doc: None,
Expand All @@ -157,6 +162,22 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
res
}

fn param_name_list(node: &ast::FnDef) -> Vec<String> {
let mut res = vec![];
if let Some(param_list) = node.param_list() {
if let Some(self_param) = param_list.self_param() {
res.push(self_param.syntax().text().to_string())
}

res.extend(
param_list
.params()
.map(|param| param.pat().unwrap().syntax().text().to_string()),
imtsuki marked this conversation as resolved.
Show resolved Hide resolved
);
}
res
}

FunctionSignature {
kind: CallableKind::Function,
visibility: node.visibility().map(|n| n.syntax().text().to_string()),
Expand All @@ -166,6 +187,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
.and_then(|r| r.type_ref())
.map(|n| n.syntax().text().to_string()),
parameters: param_list(node),
parameter_names: param_name_list(node),
generic_parameters: generic_parameters(node),
where_predicates: where_predicates(node),
// docs are processed separately
Expand Down
141 changes: 139 additions & 2 deletions crates/ra_ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ use hir::{HirDisplay, SourceAnalyzer};
use once_cell::unsync::Lazy;
use ra_prof::profile;
use ra_syntax::{
ast::{self, AstNode, TypeAscriptionOwner},
ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner},
match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
};

use crate::{db::RootDatabase, FileId};
use crate::{db::RootDatabase, FileId, FunctionSignature};

#[derive(Debug, PartialEq, Eq)]
pub enum InlayKind {
TypeHint,
ParameterHint,
}

#[derive(Debug)]
Expand Down Expand Up @@ -87,10 +88,79 @@ fn get_inlay_hints(
.collect(),
)
},
ast::CallExpr(it) => {
get_param_name_hints(db, &analyzer, ast::Expr::from(it))
},
ast::MethodCallExpr(it) => {
get_param_name_hints(db, &analyzer, ast::Expr::from(it))
},
_ => None,
}
}
}
fn get_param_name_hints(
db: &RootDatabase,
analyzer: &SourceAnalyzer,
expr: ast::Expr,
) -> Option<Vec<InlayHint>> {
let args = match &expr {
ast::Expr::CallExpr(expr) => Some(expr.arg_list()?.args()),
ast::Expr::MethodCallExpr(expr) => Some(expr.arg_list()?.args()),
_ => None,
}?;

let mut parameters = get_fn_signature(db, analyzer, &expr)?.parameter_names.into_iter();

if let ast::Expr::MethodCallExpr(_) = &expr {
parameters.next();
};

let hints = parameters
.zip(args)
.filter_map(|(param, arg)| {
if arg.syntax().kind() == SyntaxKind::LITERAL {
Some((arg.syntax().text_range(), param))
} else {
None
}
})
.map(|(range, param_name)| InlayHint {
range,
kind: InlayKind::ParameterHint,
label: param_name.into(),
})
.collect();

Some(hints)
}

fn get_fn_signature(
db: &RootDatabase,
analyzer: &SourceAnalyzer,
expr: &ast::Expr,
) -> Option<FunctionSignature> {
match expr {
ast::Expr::CallExpr(expr) => {
// FIXME: Type::as_callable is broken for closures
let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
match callable_def {
hir::CallableDef::FunctionId(it) => {
let fn_def = it.into();
Some(FunctionSignature::from_hir(db, fn_def))
}
hir::CallableDef::StructId(it) => FunctionSignature::from_struct(db, it.into()),
hir::CallableDef::EnumVariantId(it) => {
FunctionSignature::from_enum_variant(db, it.into())
}
}
}
ast::Expr::MethodCallExpr(expr) => {
let fn_def = analyzer.resolve_method_call(&expr)?;
Some(FunctionSignature::from_hir(db, fn_def))
}
_ => None,
}
}

fn get_pat_type_hints(
db: &RootDatabase,
Expand Down Expand Up @@ -605,4 +675,71 @@ fn main() {
"###
);
}

#[test]
fn function_call_parameter_hint() {
let (analysis, file_id) = single_file(
r#"
struct Test {}

impl Test {
fn method(&self, param: i32) -> i32 {
param * 2
}
}

fn test_func(foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
foo + bar
}

fn main() {
let not_literal = 1;
let _: i32 = test_func(1, 2, "hello", 3, not_literal);
let t: Test = Test {};
t.method(123);
Test::method(&t, 3456);
}"#,
);

assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
[
InlayHint {
range: [207; 218),
kind: TypeHint,
label: "i32",
},
InlayHint {
range: [251; 252),
kind: ParameterHint,
label: "foo",
},
InlayHint {
range: [254; 255),
kind: ParameterHint,
label: "bar",
},
InlayHint {
range: [257; 264),
kind: ParameterHint,
label: "msg",
},
InlayHint {
range: [266; 267),
kind: ParameterHint,
label: "_",
},
InlayHint {
range: [323; 326),
kind: ParameterHint,
label: "param",
},
InlayHint {
range: [350; 354),
kind: ParameterHint,
label: "param",
},
]
"###
);
}
}
1 change: 1 addition & 0 deletions crates/ra_lsp_server/src/main_loop/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,7 @@ pub fn handle_inlay_hints(
range: api_type.range.conv_with(&line_index),
kind: match api_type.kind {
ra_ide::InlayKind::TypeHint => InlayKind::TypeHint,
ra_ide::InlayKind::ParameterHint => InlayKind::ParameterHint,
},
})
.collect())
Expand Down
1 change: 1 addition & 0 deletions crates/ra_lsp_server/src/req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ pub struct InlayHintsParams {
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum InlayKind {
TypeHint,
ParameterHint,
}

#[derive(Debug, Deserialize, Serialize)]
Expand Down
2 changes: 1 addition & 1 deletion editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
"rust-analyzer.displayInlayHints": {
"type": "boolean",
"default": true,
"description": "Display additional type information in the editor"
"description": "Display additional type and parameter information in the editor"
},
"rust-analyzer.maxInlayHintLength": {
"type": "number",
Expand Down
51 changes: 41 additions & 10 deletions editors/code/src/inlay_hints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
},
});

const parameterHintDecorationType = vscode.window.createTextEditorDecorationType({
before: {
color: new vscode.ThemeColor('rust_analyzer.inlayHint'),
}
})

class HintsUpdater {
private pending: Map<string, vscode.CancellationTokenSource> = new Map();
private ctx: Ctx;
Expand All @@ -55,7 +61,10 @@ class HintsUpdater {
if (this.enabled) {
await this.refresh();
} else {
this.allEditors.forEach(it => this.setDecorations(it, []));
this.allEditors.forEach(it => {
this.setTypeDecorations(it, []);
this.setParameterDecorations(it, []);
});
}
}

Expand All @@ -68,15 +77,27 @@ class HintsUpdater {
private async refreshEditor(editor: vscode.TextEditor): Promise<void> {
const newHints = await this.queryHints(editor.document.uri.toString());
if (newHints == null) return;
const newDecorations = newHints.map(hint => ({
range: hint.range,
renderOptions: {
after: {
contentText: `: ${hint.label}`,
const newTypeDecorations = newHints.filter(hint => hint.kind === 'TypeHint')
.map(hint => ({
range: hint.range,
renderOptions: {
after: {
contentText: `: ${hint.label}`,
},
},
},
}));
this.setDecorations(editor, newDecorations);
}));
this.setTypeDecorations(editor, newTypeDecorations);

const newParameterDecorations = newHints.filter(hint => hint.kind === 'ParameterHint')
.map(hint => ({
range: hint.range,
renderOptions: {
before: {
contentText: `${hint.label}: `,
},
},
}));
this.setParameterDecorations(editor, newParameterDecorations);
}

private get allEditors(): vscode.TextEditor[] {
Expand All @@ -85,7 +106,7 @@ class HintsUpdater {
);
}

private setDecorations(
private setTypeDecorations(
editor: vscode.TextEditor,
decorations: vscode.DecorationOptions[],
) {
Expand All @@ -95,6 +116,16 @@ class HintsUpdater {
);
}

private setParameterDecorations(
editor: vscode.TextEditor,
decorations: vscode.DecorationOptions[],
) {
editor.setDecorations(
parameterHintDecorationType,
this.enabled ? decorations : [],
);
}

private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
let client = this.ctx.client;
if (!client) return null;
Expand Down