Improve inlay hint resolution reliability

This commit is contained in:
Lukas Wirth 2024-08-30 15:45:12 +02:00
parent 13ac53e73d
commit 5ca5d52697
20 changed files with 282 additions and 216 deletions

View file

@ -15,7 +15,7 @@ use span::{Edition, EditionedFileId};
use stdx::never;
use syntax::{
ast::{self, AstNode},
match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize, WalkEvent,
};
use text_edit::TextEdit;
@ -36,6 +36,192 @@ mod implicit_static;
mod param_name;
mod range_exclusive;
// Feature: Inlay Hints
//
// rust-analyzer shows additional information inline with the source code.
// Editors usually render this using read-only virtual text snippets interspersed with code.
//
// rust-analyzer by default shows hints for
//
// * types of local variables
// * names of function arguments
// * names of const generic parameters
// * types of chained expressions
//
// Optionally, one can enable additional hints for
//
// * return types of closure expressions
// * elided lifetimes
// * compiler inserted reborrows
// * names of generic type and lifetime parameters
//
// Note: inlay hints for function argument names are heuristically omitted to reduce noise and will not appear if
// any of the
// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L92-L99[following criteria]
// are met:
//
// * the parameter name is a suffix of the function's name
// * the argument is a qualified constructing or call expression where the qualifier is an ADT
// * exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
// of argument with _ splitting it off
// * the parameter name starts with `ra_fixture`
// * the parameter name is a
// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L200[well known name]
// in a unary function
// * the parameter name is a
// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L201[single character]
// in a unary function
//
// image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
pub(crate) fn inlay_hints(
db: &RootDatabase,
file_id: FileId,
range_limit: Option<TextRange>,
config: &InlayHintsConfig,
) -> Vec<InlayHint> {
let _p = tracing::info_span!("inlay_hints").entered();
let sema = Semantics::new(db);
let file_id = sema
.attach_first_edition(file_id)
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
let file = sema.parse(file_id);
let file = file.syntax();
let mut acc = Vec::new();
let Some(scope) = sema.scope(file) else {
return acc;
};
let famous_defs = FamousDefs(&sema, scope.krate());
let parent_impl = &mut None;
let hints = |node| hints(&mut acc, parent_impl, &famous_defs, config, file_id, node);
match range_limit {
// FIXME: This can miss some hints that require the parent of the range to calculate
Some(range) => match file.covering_element(range) {
NodeOrToken::Token(_) => return acc,
NodeOrToken::Node(n) => n
.preorder()
.filter(|event| matches!(event, WalkEvent::Enter(node) if range.intersect(node.text_range()).is_some()))
.for_each(hints),
},
None => file.preorder().for_each(hints),
};
acc
}
pub(crate) fn inlay_hints_resolve(
db: &RootDatabase,
file_id: FileId,
resolve_range: TextRange,
hash: u64,
config: &InlayHintsConfig,
hasher: impl Fn(&InlayHint) -> u64,
) -> Option<InlayHint> {
let _p = tracing::info_span!("inlay_hints_resolve").entered();
let sema = Semantics::new(db);
let file_id = sema
.attach_first_edition(file_id)
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
let file = sema.parse(file_id);
let file = file.syntax();
let scope = sema.scope(file)?;
let famous_defs = FamousDefs(&sema, scope.krate());
let mut acc = Vec::new();
let parent_impl = &mut None;
let hints = |node| hints(&mut acc, parent_impl, &famous_defs, config, file_id, node);
let mut res = file.clone();
let res = loop {
res = match res.child_or_token_at_range(resolve_range) {
Some(NodeOrToken::Node(n)) if n.text_range() == resolve_range => break n,
Some(NodeOrToken::Node(n)) => n,
_ => break res,
};
};
res.preorder().for_each(hints);
acc.into_iter().find(|hint| hasher(hint) == hash)
}
fn hints(
hints: &mut Vec<InlayHint>,
parent_impl: &mut Option<ast::Impl>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
file_id: EditionedFileId,
node: WalkEvent<SyntaxNode>,
) {
let node = match node {
WalkEvent::Enter(node) => node,
WalkEvent::Leave(n) => {
if ast::Impl::can_cast(n.kind()) {
parent_impl.take();
}
return;
}
};
closing_brace::hints(hints, sema, config, file_id, node.clone());
if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
generic_param::hints(hints, sema, config, any_has_generic_args);
}
match_ast! {
match node {
ast::Expr(expr) => {
chaining::hints(hints, famous_defs, config, file_id, &expr);
adjustment::hints(hints, famous_defs, config, file_id, &expr);
match expr {
ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
ast::Expr::MethodCallExpr(it) => {
param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
}
ast::Expr::ClosureExpr(it) => {
closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
closure_ret::hints(hints, famous_defs, config, file_id, it)
},
ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, file_id, it),
_ => None,
}
},
ast::Pat(it) => {
binding_mode::hints(hints, famous_defs, config, file_id, &it);
match it {
ast::Pat::IdentPat(it) => {
bind_pat::hints(hints, famous_defs, config, file_id, &it);
}
ast::Pat::RangePat(it) => {
range_exclusive::hints(hints, famous_defs, config, file_id, it);
}
_ => {}
}
Some(())
},
ast::Item(it) => match it {
// FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
ast::Item::Impl(impl_) => {
*parent_impl = Some(impl_);
None
},
ast::Item::Fn(it) => {
implicit_drop::hints(hints, famous_defs, config, file_id, &it);
fn_lifetime_fn::hints(hints, famous_defs, config, file_id, it)
},
// static type elisions
ast::Item::Static(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)),
ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)),
ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it),
_ => None,
},
// FIXME: fn-ptr type, dyn fn type, and trait object type elisions
ast::Type(_) => None,
_ => None,
}
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InlayHintsConfig {
pub render_colons: bool,
@ -162,6 +348,9 @@ pub struct InlayHint {
pub label: InlayHintLabel,
/// Text edit to apply when "accepting" this inlay hint.
pub text_edit: Option<TextEdit>,
/// Range to recompute inlay hints when trying to resolve for this hint. If this is none, the
/// hint does not support resolving.
pub resolve_parent: Option<TextRange>,
}
impl std::hash::Hash for InlayHint {
@ -186,6 +375,7 @@ impl InlayHint {
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: None,
}
}
@ -198,11 +388,12 @@ impl InlayHint {
position: InlayHintPosition::Before,
pad_left: false,
pad_right: false,
resolve_parent: None,
}
}
pub fn needs_resolve(&self) -> bool {
self.text_edit.is_some() || self.label.needs_resolve()
pub fn needs_resolve(&self) -> Option<TextRange> {
self.resolve_parent.filter(|_| self.text_edit.is_some() || self.label.needs_resolve())
}
}
@ -434,190 +625,6 @@ fn label_of_ty(
Some(r)
}
fn ty_to_text_edit(
sema: &Semantics<'_, RootDatabase>,
node_for_hint: &SyntaxNode,
ty: &hir::Type,
offset_to_insert: TextSize,
prefix: String,
) -> Option<TextEdit> {
let scope = sema.scope(node_for_hint)?;
// FIXME: Limit the length and bail out on excess somehow?
let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?;
let mut builder = TextEdit::builder();
builder.insert(offset_to_insert, prefix);
builder.insert(offset_to_insert, rendered);
Some(builder.finish())
}
// Feature: Inlay Hints
//
// rust-analyzer shows additional information inline with the source code.
// Editors usually render this using read-only virtual text snippets interspersed with code.
//
// rust-analyzer by default shows hints for
//
// * types of local variables
// * names of function arguments
// * names of const generic parameters
// * types of chained expressions
//
// Optionally, one can enable additional hints for
//
// * return types of closure expressions
// * elided lifetimes
// * compiler inserted reborrows
// * names of generic type and lifetime parameters
//
// Note: inlay hints for function argument names are heuristically omitted to reduce noise and will not appear if
// any of the
// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L92-L99[following criteria]
// are met:
//
// * the parameter name is a suffix of the function's name
// * the argument is a qualified constructing or call expression where the qualifier is an ADT
// * exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
// of argument with _ splitting it off
// * the parameter name starts with `ra_fixture`
// * the parameter name is a
// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L200[well known name]
// in a unary function
// * the parameter name is a
// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L201[single character]
// in a unary function
//
// image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
pub(crate) fn inlay_hints(
db: &RootDatabase,
file_id: FileId,
range_limit: Option<TextRange>,
config: &InlayHintsConfig,
) -> Vec<InlayHint> {
let _p = tracing::info_span!("inlay_hints").entered();
let sema = Semantics::new(db);
let file_id = sema
.attach_first_edition(file_id)
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
let file = sema.parse(file_id);
let file = file.syntax();
let mut acc = Vec::new();
if let Some(scope) = sema.scope(file) {
let famous_defs = FamousDefs(&sema, scope.krate());
let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
match range_limit {
Some(range) => match file.covering_element(range) {
NodeOrToken::Token(_) => return acc,
NodeOrToken::Node(n) => n
.descendants()
.filter(|descendant| range.intersect(descendant.text_range()).is_some())
.for_each(hints),
},
None => file.descendants().for_each(hints),
};
}
acc
}
pub(crate) fn inlay_hints_resolve(
db: &RootDatabase,
file_id: FileId,
position: TextSize,
hash: u64,
config: &InlayHintsConfig,
hasher: impl Fn(&InlayHint) -> u64,
) -> Option<InlayHint> {
let _p = tracing::info_span!("inlay_hints_resolve").entered();
let sema = Semantics::new(db);
let file_id = sema
.attach_first_edition(file_id)
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
let file = sema.parse(file_id);
let file = file.syntax();
let scope = sema.scope(file)?;
let famous_defs = FamousDefs(&sema, scope.krate());
let mut acc = Vec::new();
let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
let token = file.token_at_offset(position).left_biased()?;
if let Some(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast) {
parent_block.syntax().descendants().for_each(hints)
} else if let Some(parent_item) = token.parent_ancestors().find_map(ast::Item::cast) {
parent_item.syntax().descendants().for_each(hints)
} else {
return None;
}
acc.into_iter().find(|hint| hasher(hint) == hash)
}
fn hints(
hints: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
file_id: EditionedFileId,
node: SyntaxNode,
) {
closing_brace::hints(hints, sema, config, file_id, node.clone());
if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
generic_param::hints(hints, sema, config, any_has_generic_args);
}
match_ast! {
match node {
ast::Expr(expr) => {
chaining::hints(hints, famous_defs, config, file_id, &expr);
adjustment::hints(hints, sema, config, file_id, &expr);
match expr {
ast::Expr::CallExpr(it) => param_name::hints(hints, sema, config, ast::Expr::from(it)),
ast::Expr::MethodCallExpr(it) => {
param_name::hints(hints, sema, config, ast::Expr::from(it))
}
ast::Expr::ClosureExpr(it) => {
closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
closure_ret::hints(hints, famous_defs, config, file_id, it)
},
ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, config, it),
_ => None,
}
},
ast::Pat(it) => {
binding_mode::hints(hints, sema, config, &it);
match it {
ast::Pat::IdentPat(it) => {
bind_pat::hints(hints, famous_defs, config, file_id, &it);
}
ast::Pat::RangePat(it) => {
range_exclusive::hints(hints, config, it);
}
_ => {}
}
Some(())
},
ast::Item(it) => match it {
// FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
ast::Item::Impl(_) => None,
ast::Item::Fn(it) => {
implicit_drop::hints(hints, sema, config, file_id, &it);
fn_lifetime_fn::hints(hints, config, it)
},
// static type elisions
ast::Item::Static(it) => implicit_static::hints(hints, config, Either::Left(it)),
ast::Item::Const(it) => implicit_static::hints(hints, config, Either::Right(it)),
ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it),
_ => None,
},
// FIXME: fn-ptr type, dyn fn type, and trait object type elisions
ast::Type(_) => None,
_ => None,
}
};
}
/// Checks if the type is an Iterator from std::iter and returns the iterator trait and the item type of the concrete iterator.
fn hint_iterator(
sema: &Semantics<'_, RootDatabase>,
@ -653,6 +660,23 @@ fn hint_iterator(
None
}
fn ty_to_text_edit(
sema: &Semantics<'_, RootDatabase>,
node_for_hint: &SyntaxNode,
ty: &hir::Type,
offset_to_insert: TextSize,
prefix: String,
) -> Option<TextEdit> {
let scope = sema.scope(node_for_hint)?;
// FIXME: Limit the length and bail out on excess somehow?
let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?;
let mut builder = TextEdit::builder();
builder.insert(offset_to_insert, prefix);
builder.insert(offset_to_insert, rendered);
Some(builder.finish())
}
fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
}

View file

@ -6,9 +6,8 @@
use either::Either;
use hir::{
Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, OverloadedDeref, PointerCast, Safety,
Semantics,
};
use ide_db::RootDatabase;
use ide_db::famous_defs::FamousDefs;
use span::EditionedFileId;
use stdx::never;
@ -24,7 +23,7 @@ use crate::{
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
file_id: EditionedFileId,
expr: &ast::Expr,
@ -156,6 +155,7 @@ pub(super) fn hints(
kind: InlayKind::Adjustment,
label,
text_edit: None,
resolve_parent: Some(expr.syntax().text_range()),
});
}
if !postfix && needs_inner_parens {

View file

@ -110,6 +110,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: !render_colons,
pad_right: false,
resolve_parent: Some(pat.syntax().text_range()),
});
Some(())

View file

@ -2,17 +2,19 @@
//! ```no_run
//! let /* & */ (/* ref */ x,) = &(0,);
//! ```
use hir::{Mutability, Semantics};
use ide_db::RootDatabase;
use hir::Mutability;
use ide_db::famous_defs::FamousDefs;
use span::EditionedFileId;
use syntax::ast::{self, AstNode};
use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
pat: &ast::Pat,
) -> Option<()> {
if !config.binding_mode_hints {
@ -57,6 +59,7 @@ pub(super) fn hints(
position: InlayHintPosition::Before,
pad_left: false,
pad_right: mut_reference,
resolve_parent: Some(pat.syntax().text_range()),
});
});
match pat {
@ -75,6 +78,7 @@ pub(super) fn hints(
position: InlayHintPosition::Before,
pad_left: false,
pad_right: true,
resolve_parent: Some(pat.syntax().text_range()),
});
}
ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {

View file

@ -67,6 +67,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: true,
pad_right: false,
resolve_parent: Some(expr.syntax().text_range()),
});
}
}

View file

@ -18,12 +18,13 @@ pub(super) fn hints(
sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig,
file_id: EditionedFileId,
mut node: SyntaxNode,
original_node: SyntaxNode,
) -> Option<()> {
let min_lines = config.closing_brace_hints_min_lines?;
let name = |it: ast::Name| it.syntax().text_range();
let mut node = original_node.clone();
let mut closing_token;
let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
closing_token = item_list.r_curly_token()?;
@ -145,6 +146,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: true,
pad_right: false,
resolve_parent: Some(original_node.text_range()),
});
None

View file

@ -40,6 +40,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: Some(closure.syntax().text_range()),
});
range
}
@ -52,6 +53,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: None,
});
let last = captures.len() - 1;
for (idx, capture) in captures.into_iter().enumerate() {
@ -85,6 +87,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: Some(closure.syntax().text_range()),
});
if idx != last {
@ -96,6 +99,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: None,
});
}
}
@ -107,6 +111,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: true,
resolve_parent: None,
});
Some(())

View file

@ -72,6 +72,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: Some(closure.syntax().text_range()),
});
Some(())
}

View file

@ -35,7 +35,7 @@ pub(super) fn enum_hints(
return None;
}
for variant in enum_.variant_list()?.variants() {
variant_hints(acc, sema, &variant);
variant_hints(acc, sema, &enum_, &variant);
}
Some(())
}
@ -43,6 +43,7 @@ pub(super) fn enum_hints(
fn variant_hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
enum_: &ast::Enum,
variant: &ast::Variant,
) -> Option<()> {
if variant.expr().is_some() {
@ -90,6 +91,7 @@ fn variant_hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: Some(enum_.syntax().text_range()),
});
Some(())

View file

@ -2,8 +2,9 @@
//! ```no_run
//! fn example/* <'0> */(a: &/* '0 */()) {}
//! ```
use ide_db::{syntax_helpers::node_ext::walk_ty, FxHashMap};
use ide_db::{famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap};
use itertools::Itertools;
use span::EditionedFileId;
use syntax::{
ast::{self, AstNode, HasGenericParams, HasName},
SyntaxToken,
@ -14,7 +15,9 @@ use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeE
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(_, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
func: ast::Fn,
) -> Option<()> {
if config.lifetime_elision_hints == LifetimeElisionHints::Never {
@ -29,6 +32,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: true,
resolve_parent: None,
};
let param_list = func.param_list()?;
@ -195,6 +199,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: true,
resolve_parent: None,
});
}
(None, allocated_lifetimes) => acc.push(InlayHint {
@ -205,6 +210,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: false,
resolve_parent: None,
}),
}
Some(())

View file

@ -92,6 +92,7 @@ pub(crate) fn hints(
kind: InlayKind::GenericParameter,
label,
text_edit: None,
resolve_parent: Some(node.syntax().text_range()),
})
});

View file

@ -8,9 +8,9 @@
use hir::{
db::{DefDatabase as _, HirDatabase as _},
mir::{MirSpan, TerminatorKind},
ChalkTyInterner, DefWithBody, Semantics,
ChalkTyInterner, DefWithBody,
};
use ide_db::{FileRange, RootDatabase};
use ide_db::{famous_defs::FamousDefs, FileRange};
use span::EditionedFileId;
use syntax::{
@ -22,16 +22,16 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
file_id: EditionedFileId,
def: &ast::Fn,
node: &ast::Fn,
) -> Option<()> {
if !config.implicit_drop_hints {
return None;
}
let def = sema.to_def(def)?;
let def = sema.to_def(node)?;
let def: DefWithBody = def.into();
let (hir, source_map) = sema.db.body_with_source_map(def.into());
@ -121,6 +121,7 @@ pub(super) fn hints(
kind: InlayKind::Drop,
label,
text_edit: None,
resolve_parent: Some(node.syntax().text_range()),
})
}
}

View file

@ -3,6 +3,8 @@
//! static S: &/* 'static */str = "";
//! ```
use either::Either;
use ide_db::famous_defs::FamousDefs;
use span::EditionedFileId;
use syntax::{
ast::{self, AstNode},
SyntaxKind,
@ -12,7 +14,9 @@ use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeE
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
statik_or_const: Either<ast::Static, ast::Const>,
) -> Option<()> {
if config.lifetime_elision_hints != LifetimeElisionHints::Always {
@ -38,6 +42,7 @@ pub(super) fn hints(
position: InlayHintPosition::After,
pad_left: false,
pad_right: true,
resolve_parent: None,
});
}
}

View file

@ -7,8 +7,9 @@ use std::fmt::Display;
use either::Either;
use hir::{Callable, Semantics};
use ide_db::RootDatabase;
use ide_db::{famous_defs::FamousDefs, RootDatabase};
use span::EditionedFileId;
use stdx::to_lower_snake_case;
use syntax::{
ast::{self, AstNode, HasArgList, HasName, UnaryOp},
@ -19,8 +20,9 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
expr: ast::Expr,
) -> Option<()> {
if !config.parameter_hints {
@ -60,6 +62,7 @@ pub(super) fn hints(
position: InlayHintPosition::Before,
pad_left: false,
pad_right: true,
resolve_parent: Some(expr.syntax().text_range()),
}
});

View file

@ -3,13 +3,17 @@
//! for i in 0../* < */10 {}
//! if let ../* < */100 = 50 {}
//! ```
use ide_db::famous_defs::FamousDefs;
use span::EditionedFileId;
use syntax::{ast, SyntaxToken, T};
use crate::{InlayHint, InlayHintsConfig};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
range: impl ast::RangeItem,
) -> Option<()> {
(config.range_exclusive_hints && range.end().is_some())
@ -30,6 +34,7 @@ fn inlay_hint(token: SyntaxToken) -> InlayHint {
kind: crate::InlayKind::RangeExclusive,
label: crate::InlayHintLabel::from("<"),
text_edit: None,
resolve_parent: None,
}
}

View file

@ -439,12 +439,12 @@ impl Analysis {
&self,
config: &InlayHintsConfig,
file_id: FileId,
position: TextSize,
resolve_range: TextRange,
hash: u64,
hasher: impl Fn(&InlayHint) -> u64 + Send + UnwindSafe,
) -> Cancellable<Option<InlayHint>> {
self.with_db(|db| {
inlay_hints::inlay_hints_resolve(db, file_id, position, hash, config, hasher)
inlay_hints::inlay_hints_resolve(db, file_id, resolve_range, hash, config, hasher)
})
}

View file

@ -1602,14 +1602,14 @@ pub(crate) fn handle_inlay_hints_resolve(
anyhow::ensure!(snap.file_exists(file_id), "Invalid LSP resolve data");
let line_index = snap.file_line_index(file_id)?;
let hint_position = from_proto::offset(&line_index, original_hint.position)?;
let range = from_proto::text_range(&line_index, resolve_data.resolve_range)?;
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
let resolve_hints = snap.analysis.inlay_hints_resolve(
&forced_resolve_inlay_hints_config,
file_id,
hint_position,
range,
hash,
|hint| {
std::hash::BuildHasher::hash_one(

View file

@ -819,6 +819,7 @@ pub struct InlayHintResolveData {
pub file_id: u32,
// This is a string instead of a u64 as javascript can't represent u64 fully
pub hash: String,
pub resolve_range: lsp_types::Range,
pub version: Option<i32>,
}

View file

@ -452,10 +452,13 @@ pub(crate) fn inlay_hint(
file_id: FileId,
mut inlay_hint: InlayHint,
) -> Cancellable<lsp_types::InlayHint> {
let resolve_hash = inlay_hint.needs_resolve().then(|| {
std::hash::BuildHasher::hash_one(
&std::hash::BuildHasherDefault::<FxHasher>::default(),
&inlay_hint,
let resolve_range_and_hash = inlay_hint.needs_resolve().map(|range| {
(
range,
std::hash::BuildHasher::hash_one(
&std::hash::BuildHasherDefault::<FxHasher>::default(),
&inlay_hint,
),
)
});
@ -465,7 +468,7 @@ pub(crate) fn inlay_hint(
.visual_studio_code_version()
// https://github.com/microsoft/vscode/issues/193124
.map_or(true, |version| VersionReq::parse(">=1.86.0").unwrap().matches(version))
&& resolve_hash.is_some()
&& resolve_range_and_hash.is_some()
&& fields_to_resolve.resolve_text_edits
{
something_to_resolve |= inlay_hint.text_edit.is_some();
@ -477,16 +480,17 @@ pub(crate) fn inlay_hint(
snap,
fields_to_resolve,
&mut something_to_resolve,
resolve_hash.is_some(),
resolve_range_and_hash.is_some(),
inlay_hint.label,
)?;
let data = match resolve_hash {
Some(hash) if something_to_resolve => Some(
let data = match resolve_range_and_hash {
Some((resolve_range, hash)) if something_to_resolve => Some(
to_value(lsp_ext::InlayHintResolveData {
file_id: file_id.index(),
hash: hash.to_string(),
version: snap.file_version(file_id),
resolve_range: range(line_index, resolve_range),
})
.unwrap(),
),

View file

@ -1,5 +1,5 @@
<!---
lsp/ext.rs hash: 3429c08745984b3d
lsp/ext.rs hash: c6e83d3d08d993de
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: