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

Upstream inlay hints #11445

Merged
merged 16 commits into from
Mar 7, 2022
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 @@ -413,12 +413,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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do chaining hints not have their own kind? (CC)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely cause the proposal only defines type and parameter hint kinds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose a chaining hint is a kind of type hint?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},
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