Skip to content

Commit

Permalink
Merge #11445
Browse files Browse the repository at this point in the history
11445: Upstream inlay hints r=lnicola a=lnicola

Closes #2797
Closes #3394 (since now resolve the hints for the range given only, not for the whole document. We don't actually resolve anything due to [hard requirement](#11445 (comment)) on label being immutable. Any further heavy actions could go to the `resolve` method that's now available via the official Code API for hints)

Based on `@SomeoneToIgnore's` branch, with a couple of updates:

 - I squashed, more or less successfully, the commits on that branch
 - downloading the `.d.ts` no longer works, but you can get it manually from https://raw.githubusercontent.com/microsoft/vscode/release/1.64/src/vscode-dts/vscode.proposed.inlayHints.d.ts
 - you might need to pass `--enable-proposed-api matklad.rust-analyzer`
 - if I'm reading the definition right, `InlayHintKind` needs to be serialized as a number, not string
 - this doesn't work anyway -- the client-side gets the hints, but they don't display

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Co-authored-by: Laurențiu Nicola <lnicola@dend.ro>
  • Loading branch information
3 people authored Mar 7, 2022
2 parents 18d0faf + 88a2141 commit 49646b7
Show file tree
Hide file tree
Showing 13 changed files with 555 additions and 643 deletions.
113 changes: 95 additions & 18 deletions crates/ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use itertools::Itertools;
use stdx::to_lower_snake_case;
use syntax::{
ast::{self, AstNode, HasArgList, HasName, UnaryOp},
match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T,
match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, TextRange, T,
};

use crate::FileId;
Expand Down Expand Up @@ -58,32 +58,58 @@ pub struct InlayHint {
pub(crate) fn inlay_hints(
db: &RootDatabase,
file_id: FileId,
range_limit: Option<FileRange>,
config: &InlayHintsConfig,
) -> Vec<InlayHint> {
let _p = profile::span("inlay_hints");
let sema = Semantics::new(db);
let file = sema.parse(file_id);
let file = file.syntax();

let mut res = Vec::new();

for node in file.descendants() {
if let Some(expr) = ast::Expr::cast(node.clone()) {
get_chaining_hints(&mut res, &sema, config, &expr);
match expr {
ast::Expr::CallExpr(it) => {
get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it));
}
ast::Expr::MethodCallExpr(it) => {
get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it));
let mut hints = Vec::new();

if let Some(range_limit) = range_limit {
let range_limit = range_limit.range;
match file.covering_element(range_limit) {
NodeOrToken::Token(_) => return hints,
NodeOrToken::Node(n) => {
for node in n
.descendants()
.filter(|descendant| range_limit.contains_range(descendant.text_range()))
{
get_hints(&mut hints, &sema, config, node);
}
_ => (),
}
} else if let Some(it) = ast::IdentPat::cast(node.clone()) {
get_bind_pat_hints(&mut res, &sema, config, &it);
}
} else {
for node in file.descendants() {
get_hints(&mut hints, &sema, config, node);
}
}

hints
}

fn get_hints(
hints: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>,
config: &InlayHintsConfig,
node: SyntaxNode,
) {
if let Some(expr) = ast::Expr::cast(node.clone()) {
get_chaining_hints(hints, sema, config, &expr);
match expr {
ast::Expr::CallExpr(it) => {
get_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));
}
_ => (),
}
} else if let Some(it) = ast::IdentPat::cast(node) {
get_bind_pat_hints(hints, sema, config, &it);
}
res
}

fn get_chaining_hints(
Expand Down Expand Up @@ -541,6 +567,8 @@ fn get_callable(
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use ide_db::base_db::FileRange;
use syntax::{TextRange, TextSize};
use test_utils::extract_annotations;

use crate::{fixture, inlay_hints::InlayHintsConfig};
Expand Down Expand Up @@ -604,7 +632,7 @@ mod tests {
fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
let (analysis, file_id) = fixture::file(ra_fixture);
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
let inlay_hints = analysis.inlay_hints(&config, file_id).unwrap();
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
let actual =
inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
Expand All @@ -613,7 +641,7 @@ mod tests {
#[track_caller]
fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
let (analysis, file_id) = fixture::file(ra_fixture);
let inlay_hints = analysis.inlay_hints(&config, file_id).unwrap();
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
expect.assert_debug_eq(&inlay_hints)
}

Expand Down Expand Up @@ -1045,6 +1073,55 @@ fn main() {
)
}

#[test]
fn check_hint_range_limit() {
let fixture = r#"
//- minicore: fn, sized
fn foo() -> impl Fn() { loop {} }
fn foo1() -> impl Fn(f64) { loop {} }
fn foo2() -> impl Fn(f64, f64) { loop {} }
fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
fn main() {
let foo = foo();
let foo = foo1();
let foo = foo2();
let foo = foo3();
// ^^^ impl Fn(f64, f64) -> u32
let foo = foo4();
// ^^^ &dyn Fn(f64, f64) -> u32
let foo = foo5();
let foo = foo6();
let foo = foo7();
}
"#;
let (analysis, file_id) = fixture::file(fixture);
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
let inlay_hints = analysis
.inlay_hints(
&InlayHintsConfig {
parameter_hints: false,
type_hints: true,
chaining_hints: false,
hide_named_constructor_hints: false,
max_length: None,
},
file_id,
Some(FileRange {
file_id,
range: TextRange::new(TextSize::from(500), TextSize::from(600)),
}),
)
.unwrap();
let actual =
inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
}

#[test]
fn fn_hints_ptr_rpit_fn_parentheses() {
check_types(
Expand Down
3 changes: 2 additions & 1 deletion crates/ide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,9 @@ impl Analysis {
&self,
config: &InlayHintsConfig,
file_id: FileId,
range: Option<FileRange>,
) -> Cancellable<Vec<InlayHint>> {
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config))
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config))
}

/// Returns the set of folding ranges.
Expand Down
1 change: 1 addition & 0 deletions crates/ide/src/static_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ impl StaticIndex<'_> {
max_length: Some(25),
},
file_id,
None,
)
.unwrap();
// hovers
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/caps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
experimental: Some(json!({
"externalDocs": true,
"hoverRange": true,
"inlayHints": true,
"joinLines": true,
"matchingBrace": true,
"moveItem": true,
Expand Down
15 changes: 13 additions & 2 deletions crates/rust-analyzer/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,11 +1318,22 @@ pub(crate) fn handle_inlay_hints(
params: InlayHintsParams,
) -> Result<Vec<InlayHint>> {
let _p = profile::span("handle_inlay_hints");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let document_uri = &params.text_document.uri;
let file_id = from_proto::file_id(&snap, document_uri)?;
let line_index = snap.file_line_index(file_id)?;
let range = params
.range
.map(|range| {
from_proto::file_range(
&snap,
TextDocumentIdentifier::new(document_uri.to_owned()),
range,
)
})
.transpose()?;
Ok(snap
.analysis
.inlay_hints(&snap.config.inlay_hints(), file_id)?
.inlay_hints(&snap.config.inlay_hints(), file_id, range)?
.into_iter()
.map(|it| to_proto::inlay_hint(&line_index, it))
.collect())
Expand Down
23 changes: 15 additions & 8 deletions crates/rust-analyzer/src/lsp_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,27 +233,34 @@ pub enum InlayHints {}
impl Request for InlayHints {
type Params = InlayHintsParams;
type Result = Vec<InlayHint>;
const METHOD: &'static str = "rust-analyzer/inlayHints";
const METHOD: &'static str = "experimental/inlayHints";
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct InlayHintsParams {
pub text_document: TextDocumentIdentifier,
pub range: Option<lsp_types::Range>,
}

#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum InlayKind {
TypeHint,
ParameterHint,
ChainingHint,
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct InlayHintKind(u8);

impl InlayHintKind {
pub const TYPE: InlayHintKind = InlayHintKind(1);
pub const PARAMETER: InlayHintKind = InlayHintKind(2);
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InlayHint {
pub range: Range,
pub kind: InlayKind,
pub label: String,
pub position: Position,
pub kind: Option<InlayHintKind>,
pub tooltip: Option<String>,
pub padding_left: Option<bool>,
pub padding_right: Option<bool>,
}

pub enum Ssr {}
Expand Down
14 changes: 10 additions & 4 deletions crates/rust-analyzer/src/to_proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,12 +416,18 @@ pub(crate) fn signature_help(
pub(crate) fn inlay_hint(line_index: &LineIndex, inlay_hint: InlayHint) -> lsp_ext::InlayHint {
lsp_ext::InlayHint {
label: inlay_hint.label.to_string(),
range: range(line_index, inlay_hint.range),
position: match inlay_hint.kind {
InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()),
_ => position(line_index, inlay_hint.range.end()),
},
kind: match inlay_hint.kind {
InlayKind::ParameterHint => lsp_ext::InlayKind::ParameterHint,
InlayKind::TypeHint => lsp_ext::InlayKind::TypeHint,
InlayKind::ChainingHint => lsp_ext::InlayKind::ChainingHint,
InlayKind::ParameterHint => Some(lsp_ext::InlayHintKind::PARAMETER),
InlayKind::TypeHint => Some(lsp_ext::InlayHintKind::TYPE),
InlayKind::ChainingHint => None,
},
tooltip: None,
padding_left: Some(true),
padding_right: Some(true),
}
}

Expand Down
15 changes: 9 additions & 6 deletions docs/dev/lsp-extensions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!---
lsp_ext.rs hash: 5b53b92c9f9d6650
lsp_ext.rs hash: e32fdde032ff6ebc
If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue:
Expand Down Expand Up @@ -562,11 +562,11 @@ Expands macro call at a given position.

## Inlay Hints

**Method:** `rust-analyzer/inlayHints`
**Method:** `experimental/inlayHints`

This request is sent from client to server to render "inlay hints" -- virtual text inserted into editor to show things like inferred types.
Generally, the client should re-query inlay hints after every modification.
Note that we plan to move this request to `experimental/inlayHints`, as it is not really Rust-specific, but the current API is not necessary the right one.
Until it gets upstreamed, this follows the VS Code API.
Upstream issues: https://github.com/microsoft/language-server-protocol/issues/956 , https://github.com/rust-analyzer/rust-analyzer/issues/2797

**Request:**
Expand All @@ -581,9 +581,12 @@ interface InlayHintsParams {

```typescript
interface InlayHint {
kind: "TypeHint" | "ParameterHint" | "ChainingHint",
range: Range,
label: string,
position: Position;
label: string | InlayHintLabelPart[];
tooltip?: string | MarkdownString | undefined;
kind?: InlayHintKind;
paddingLeft?: boolean;
paddingRight?: boolean;
}
```

Expand Down
2 changes: 2 additions & 0 deletions editors/code/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_modules
server
.vscode-test/
*.vsix
bundle
vscode.proposed.d.ts
Loading

0 comments on commit 49646b7

Please sign in to comment.