Merge #1549
1549: Show type lenses for the resolved let bindings r=matklad a=SomeoneToIgnore Types that are fully unresolved are not displayed: <img width="279" alt="image" src="https://user-images.githubusercontent.com/2690773/61518122-8e4ba980-aa11-11e9-9249-6d9f9b202e6a.png"> A few concerns that I have about the current implementation: * I've adjusted the `file_structure` API method to return the information about the `let` bindings. Although it works fine, I have a feeling that adding a new API method would be the better way. But this requires some prior discussion, so I've decided to go for an easy way with an MVP. Would be nice to hear your suggestions. * There's a hardcoded `{undersolved}` check that I was forced to use, since the method that resolves types returns a `String`. Is there a better typed API I can use? This will help, for instance, to add an action to the type lenses that will allow us to navigate to the type. Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
This commit is contained in:
commit
5f3ff157e3
180
crates/ra_ide_api/src/inlay_hints.rs
Normal file
180
crates/ra_ide_api/src/inlay_hints.rs
Normal file
@ -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)",
|
||||
},
|
||||
]"#
|
||||
);
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ mod join_lines;
|
||||
mod typing;
|
||||
mod matching_brace;
|
||||
mod display;
|
||||
mod inlay_hints;
|
||||
|
||||
#[cfg(test)]
|
||||
mod marks;
|
||||
@ -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,
|
||||
@ -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);
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
@ -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())
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user