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 hints backend into the server #1549

Merged
merged 12 commits into from
Jul 23, 2019
180 changes: 180 additions & 0 deletions crates/ra_ide_api/src/inlay_hints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use crate::{db::RootDatabase, FileId};
use hir::{HirDisplay, Ty};
use ra_syntax::ast::Pat;
use ra_syntax::{
algo::visit::{visitor, Visitor},
ast::{self, PatKind, TypeAscriptionOwner},
AstNode, SmolStr, SourceFile, SyntaxNode, TextRange,
};

#[derive(Debug, PartialEq, Eq)]
pub enum InlayKind {
LetBindingType,
ClosureParameterType,
}

#[derive(Debug)]
pub struct InlayHint {
pub range: TextRange,
pub kind: InlayKind,
pub label: SmolStr,
}

pub(crate) fn inlay_hints(db: &RootDatabase, file_id: FileId, file: &SourceFile) -> Vec<InlayHint> {
file.syntax()
.descendants()
.map(|node| get_inlay_hints(db, file_id, &node).unwrap_or_default())
.flatten()
.collect()
}

fn get_inlay_hints(
db: &RootDatabase,
file_id: FileId,
node: &SyntaxNode,
) -> Option<Vec<InlayHint>> {
visitor()
.visit(|let_statement: ast::LetStmt| {
let let_syntax = let_statement.syntax();

if let_statement.ascribed_type().is_some() {
return None;
}

let let_pat = let_statement.pat()?;
let inlay_type_string = get_node_displayable_type(db, file_id, let_syntax, &let_pat)?
.display(db)
.to_string()
.into();

let pat_range = match let_pat.kind() {
PatKind::BindPat(bind_pat) => bind_pat.syntax().text_range(),
PatKind::TuplePat(tuple_pat) => tuple_pat.syntax().text_range(),
_ => return None,
};

Some(vec![InlayHint {
range: pat_range,
kind: InlayKind::LetBindingType,
label: inlay_type_string,
}])
})
.visit(|closure_parameter: ast::LambdaExpr| match closure_parameter.param_list() {
Some(param_list) => Some(
param_list
.params()
.filter(|closure_param| closure_param.ascribed_type().is_none())
.filter_map(|closure_param| {
let closure_param_syntax = closure_param.syntax();
let inlay_type_string = get_node_displayable_type(
db,
file_id,
closure_param_syntax,
&closure_param.pat()?,
)?
.display(db)
.to_string()
.into();

Some(InlayHint {
range: closure_param_syntax.text_range(),
kind: InlayKind::ClosureParameterType,
label: inlay_type_string,
})
})
.collect(),
),
None => None,
})
.accept(&node)?
}

fn get_node_displayable_type(
db: &RootDatabase,
file_id: FileId,
node_syntax: &SyntaxNode,
node_pat: &Pat,
) -> Option<Ty> {
let analyzer = hir::SourceAnalyzer::new(db, file_id, node_syntax, None);
analyzer.type_of_pat(db, node_pat).and_then(|resolved_type| {
if let Ty::Apply(_) = resolved_type {
Some(resolved_type)
} else {
None
}
})
}

#[cfg(test)]
mod tests {
use crate::mock_analysis::single_file;
use insta::assert_debug_snapshot_matches;

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

fn main() {
struct InnerStruct {}

let test = 54;
let test = InnerStruct {};
let test = OuterStruct {};
let test = vec![222];
let mut test = Vec::new();
test.push(333);
let test = test.into_iter().map(|i| i * i).collect::<Vec<_>>();
let mut test = 33;
let _ = 22;
let test: Vec<_> = (0..3).collect();

let _ = (0..23).map(|i: u32| {
let i_squared = i * i;
i_squared
});

let test: i32 = 33;

let (x, c) = (42, 'a');
let test = (42, 'a');
}
"#,
);

assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
InlayHint {
range: [71; 75),
kind: LetBindingType,
label: "i32",
},
InlayHint {
range: [121; 125),
kind: LetBindingType,
label: "OuterStruct",
},
InlayHint {
range: [297; 305),
kind: LetBindingType,
label: "i32",
},
InlayHint {
range: [417; 426),
kind: LetBindingType,
label: "u32",
},
InlayHint {
range: [496; 502),
kind: LetBindingType,
label: "(i32, char)",
},
InlayHint {
range: [524; 528),
kind: LetBindingType,
label: "(i32, char)",
},
]"#
);
}
}
7 changes: 7 additions & 0 deletions crates/ra_ide_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mod join_lines;
mod typing;
mod matching_brace;
mod display;
mod inlay_hints;

#[cfg(test)]
mod marks;
Expand All @@ -64,6 +65,7 @@ pub use crate::{
display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
folding_ranges::{Fold, FoldKind},
hover::HoverResult,
inlay_hints::{InlayHint, InlayKind},
line_index::{LineCol, LineIndex},
line_index_utils::translate_offset_with_edit,
references::ReferenceSearchResult,
Expand Down Expand Up @@ -396,6 +398,11 @@ impl Analysis {
file_structure(&parse.tree())
}

/// Returns a list of the places in the file where type hints can be displayed.
pub fn inlay_hints(&self, file_id: FileId) -> Cancelable<Vec<InlayHint>> {
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree()))
}

/// Returns the set of folding ranges.
pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> {
let parse = self.db.parse(file_id);
Expand Down
1 change: 1 addition & 0 deletions crates/ra_lsp_server/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ fn on_request(
.on::<req::References>(handlers::handle_references)?
.on::<req::Formatting>(handlers::handle_formatting)?
.on::<req::DocumentHighlightRequest>(handlers::handle_document_highlight)?
.on::<req::InlayHints>(handlers::handle_inlay_hints)?
.finish();
Ok(())
}
Expand Down
23 changes: 22 additions & 1 deletion crates/ra_lsp_server/src/main_loop/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use url_serde::Ser;
use crate::{
cargo_target_spec::{runnable_args, CargoTargetSpec},
conv::{to_location, Conv, ConvWith, MapConvWith, TryConvWith, TryConvWithToVec},
req::{self, Decoration},
req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
world::WorldSnapshot,
LspError, Result,
};
Expand Down Expand Up @@ -874,3 +874,24 @@ fn to_diagnostic_severity(severity: Severity) -> DiagnosticSeverity {
WeakWarning => DiagnosticSeverity::Hint,
}
}

pub fn handle_inlay_hints(
world: WorldSnapshot,
params: InlayHintsParams,
) -> Result<Vec<InlayHint>> {
let file_id = params.text_document.try_conv_with(&world)?;
let analysis = world.analysis();
let line_index = analysis.file_line_index(file_id);
Ok(analysis
.inlay_hints(file_id)?
.into_iter()
.map(|api_type| InlayHint {
label: api_type.label.to_string(),
range: api_type.range.conv_with(&line_index),
kind: match api_type.kind {
ra_ide_api::InlayKind::LetBindingType => InlayKind::LetBindingType,
ra_ide_api::InlayKind::ClosureParameterType => InlayKind::ClosureParameterType,
},
})
.collect())
}
27 changes: 27 additions & 0 deletions crates/ra_lsp_server/src/req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,30 @@ pub struct SourceChange {
pub workspace_edit: WorkspaceEdit,
pub cursor_position: Option<TextDocumentPositionParams>,
}

pub enum InlayHints {}

impl Request for InlayHints {
type Params = InlayHintsParams;
type Result = Vec<InlayHint>;
const METHOD: &'static str = "rust-analyzer/inlayHints";
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct InlayHintsParams {
pub text_document: TextDocumentIdentifier,
}

#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum InlayKind {
LetBindingType,
ClosureParameterType,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct InlayHint {
pub range: Range,
pub kind: InlayKind,
pub label: String,
}