mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 12:33:33 +00:00
feat: Visualize compiler inserted reborrows via inlay hints
This commit is contained in:
parent
2598575a35
commit
37b48ceb8f
9 changed files with 126 additions and 13 deletions
|
@ -8,6 +8,7 @@ use base_db::{FileId, FileRange};
|
||||||
use hir_def::{
|
use hir_def::{
|
||||||
body, macro_id_to_def_id,
|
body, macro_id_to_def_id,
|
||||||
resolver::{self, HasResolver, Resolver, TypeNs},
|
resolver::{self, HasResolver, Resolver, TypeNs},
|
||||||
|
type_ref::Mutability,
|
||||||
AsMacroCall, FunctionId, MacroId, TraitId, VariantId,
|
AsMacroCall, FunctionId, MacroId, TraitId, VariantId,
|
||||||
};
|
};
|
||||||
use hir_expand::{
|
use hir_expand::{
|
||||||
|
@ -313,6 +314,11 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
||||||
self.imp.resolve_type(ty)
|
self.imp.resolve_type(ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Figure out a nice interface to inspect adjustments
|
||||||
|
pub fn is_implicit_reborrow(&self, expr: &ast::Expr) -> Option<Mutability> {
|
||||||
|
self.imp.is_implicit_reborrow(expr)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {
|
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {
|
||||||
self.imp.type_of_expr(expr)
|
self.imp.type_of_expr(expr)
|
||||||
}
|
}
|
||||||
|
@ -900,6 +906,10 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
Type::new_with_resolver(self.db, &scope.resolver, ty)
|
Type::new_with_resolver(self.db, &scope.resolver, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_implicit_reborrow(&self, expr: &ast::Expr) -> Option<Mutability> {
|
||||||
|
self.analyze(expr.syntax()).is_implicit_reborrow(self.db, expr)
|
||||||
|
}
|
||||||
|
|
||||||
fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {
|
fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {
|
||||||
self.analyze(expr.syntax())
|
self.analyze(expr.syntax())
|
||||||
.type_of_expr(self.db, expr)
|
.type_of_expr(self.db, expr)
|
||||||
|
|
|
@ -20,12 +20,14 @@ use hir_def::{
|
||||||
macro_id_to_def_id,
|
macro_id_to_def_id,
|
||||||
path::{ModPath, Path, PathKind},
|
path::{ModPath, Path, PathKind},
|
||||||
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
|
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
|
||||||
|
type_ref::Mutability,
|
||||||
AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, ModuleDefId, VariantId,
|
AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, ModuleDefId, VariantId,
|
||||||
};
|
};
|
||||||
use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
|
use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
|
||||||
use hir_ty::{
|
use hir_ty::{
|
||||||
diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
|
diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
|
||||||
InferenceResult, Interner, Substitution, TyExt, TyLoweringContext,
|
Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt,
|
||||||
|
TyLoweringContext,
|
||||||
};
|
};
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, AstNode},
|
ast::{self, AstNode},
|
||||||
|
@ -139,6 +141,23 @@ impl SourceAnalyzer {
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_implicit_reborrow(
|
||||||
|
&self,
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
expr: &ast::Expr,
|
||||||
|
) -> Option<Mutability> {
|
||||||
|
let expr_id = self.expr_id(db, expr)?;
|
||||||
|
let infer = self.infer.as_ref()?;
|
||||||
|
let adjustments = infer.expr_adjustments.get(&expr_id)?;
|
||||||
|
adjustments.windows(2).find_map(|slice| match slice {
|
||||||
|
&[Adjustment {kind: Adjust::Deref(None), ..}, Adjustment {kind: Adjust::Borrow(AutoBorrow::Ref(m)), ..}] => Some(match m {
|
||||||
|
hir_ty::Mutability::Mut => Mutability::Mut,
|
||||||
|
hir_ty::Mutability::Not => Mutability::Shared,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn type_of_expr(
|
pub(crate) fn type_of_expr(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn HirDatabase,
|
db: &dyn HirDatabase,
|
||||||
|
|
|
@ -50,7 +50,9 @@ use crate::{db::HirDatabase, utils::generics};
|
||||||
pub use autoderef::autoderef;
|
pub use autoderef::autoderef;
|
||||||
pub use builder::{ParamKind, TyBuilder};
|
pub use builder::{ParamKind, TyBuilder};
|
||||||
pub use chalk_ext::*;
|
pub use chalk_ext::*;
|
||||||
pub use infer::{could_unify, InferenceDiagnostic, InferenceResult};
|
pub use infer::{
|
||||||
|
could_unify, Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, InferenceResult,
|
||||||
|
};
|
||||||
pub use interner::Interner;
|
pub use interner::Interner;
|
||||||
pub use lower::{
|
pub use lower::{
|
||||||
associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode,
|
associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode,
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub struct InlayHintsConfig {
|
||||||
pub type_hints: bool,
|
pub type_hints: bool,
|
||||||
pub parameter_hints: bool,
|
pub parameter_hints: bool,
|
||||||
pub chaining_hints: bool,
|
pub chaining_hints: bool,
|
||||||
|
pub reborrow_hints: bool,
|
||||||
pub closure_return_type_hints: bool,
|
pub closure_return_type_hints: bool,
|
||||||
pub lifetime_elision_hints: LifetimeElisionHints,
|
pub lifetime_elision_hints: LifetimeElisionHints,
|
||||||
pub param_names_for_lifetime_elision_hints: bool,
|
pub param_names_for_lifetime_elision_hints: bool,
|
||||||
|
@ -35,6 +36,7 @@ pub enum LifetimeElisionHints {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum InlayKind {
|
pub enum InlayKind {
|
||||||
|
ImplicitReborrow,
|
||||||
TypeHint,
|
TypeHint,
|
||||||
ParameterHint,
|
ParameterHint,
|
||||||
ClosureReturnTypeHint,
|
ClosureReturnTypeHint,
|
||||||
|
@ -116,17 +118,16 @@ fn hints(
|
||||||
if let Some(expr) = ast::Expr::cast(node.clone()) {
|
if let Some(expr) = ast::Expr::cast(node.clone()) {
|
||||||
chaining_hints(hints, sema, &famous_defs, config, &expr);
|
chaining_hints(hints, sema, &famous_defs, config, &expr);
|
||||||
match expr {
|
match expr {
|
||||||
ast::Expr::CallExpr(it) => {
|
ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)),
|
||||||
param_name_hints(hints, sema, config, ast::Expr::from(it));
|
|
||||||
}
|
|
||||||
ast::Expr::MethodCallExpr(it) => {
|
ast::Expr::MethodCallExpr(it) => {
|
||||||
param_name_hints(hints, sema, config, ast::Expr::from(it));
|
param_name_hints(hints, sema, config, ast::Expr::from(it))
|
||||||
}
|
}
|
||||||
ast::Expr::ClosureExpr(it) => {
|
ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, it),
|
||||||
closure_ret_hints(hints, sema, &famous_defs, config, it);
|
// We could show reborrows for all expressions, but usually that is just noise to the user
|
||||||
}
|
// and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
|
||||||
_ => (),
|
ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
|
||||||
}
|
_ => None,
|
||||||
|
};
|
||||||
} else if let Some(it) = ast::IdentPat::cast(node.clone()) {
|
} else if let Some(it) = ast::IdentPat::cast(node.clone()) {
|
||||||
bind_pat_hints(hints, sema, config, &it);
|
bind_pat_hints(hints, sema, config, &it);
|
||||||
} else if let Some(it) = ast::Fn::cast(node) {
|
} else if let Some(it) = ast::Fn::cast(node) {
|
||||||
|
@ -365,6 +366,28 @@ fn closure_ret_hints(
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reborrow_hints(
|
||||||
|
acc: &mut Vec<InlayHint>,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
config: &InlayHintsConfig,
|
||||||
|
expr: &ast::Expr,
|
||||||
|
) -> Option<()> {
|
||||||
|
if !config.reborrow_hints {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mutability = sema.is_implicit_reborrow(expr)?;
|
||||||
|
acc.push(InlayHint {
|
||||||
|
range: expr.syntax().text_range(),
|
||||||
|
kind: InlayKind::ImplicitReborrow,
|
||||||
|
label: match mutability {
|
||||||
|
hir::Mutability::Shared => SmolStr::new_inline("&*"),
|
||||||
|
hir::Mutability::Mut => SmolStr::new_inline("&mut *"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
fn chaining_hints(
|
fn chaining_hints(
|
||||||
acc: &mut Vec<InlayHint>,
|
acc: &mut Vec<InlayHint>,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
|
@ -834,6 +857,7 @@ mod tests {
|
||||||
lifetime_elision_hints: LifetimeElisionHints::Never,
|
lifetime_elision_hints: LifetimeElisionHints::Never,
|
||||||
hide_named_constructor_hints: false,
|
hide_named_constructor_hints: false,
|
||||||
closure_return_type_hints: false,
|
closure_return_type_hints: false,
|
||||||
|
reborrow_hints: false,
|
||||||
param_names_for_lifetime_elision_hints: false,
|
param_names_for_lifetime_elision_hints: false,
|
||||||
max_length: None,
|
max_length: None,
|
||||||
};
|
};
|
||||||
|
@ -841,6 +865,7 @@ mod tests {
|
||||||
type_hints: true,
|
type_hints: true,
|
||||||
parameter_hints: true,
|
parameter_hints: true,
|
||||||
chaining_hints: true,
|
chaining_hints: true,
|
||||||
|
reborrow_hints: true,
|
||||||
closure_return_type_hints: true,
|
closure_return_type_hints: true,
|
||||||
lifetime_elision_hints: LifetimeElisionHints::Always,
|
lifetime_elision_hints: LifetimeElisionHints::Always,
|
||||||
..DISABLED_CONFIG
|
..DISABLED_CONFIG
|
||||||
|
@ -2115,6 +2140,41 @@ impl () {
|
||||||
// ^^^<'0, '1>
|
// ^^^<'0, '1>
|
||||||
// ^'0 ^'1 ^'0
|
// ^'0 ^'1 ^'0
|
||||||
}
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hints_implicit_reborrow() {
|
||||||
|
check_with_config(
|
||||||
|
InlayHintsConfig { reborrow_hints: true, ..DISABLED_CONFIG },
|
||||||
|
r#"
|
||||||
|
fn __() {
|
||||||
|
let unique = &mut ();
|
||||||
|
let r_mov = unique;
|
||||||
|
let foo: &mut _ = unique;
|
||||||
|
//^^^^^^ &mut *
|
||||||
|
ref_mut_id(unique);
|
||||||
|
//^^^^^^ &mut *
|
||||||
|
let shared = ref_id(unique);
|
||||||
|
//^^^^^^ &*
|
||||||
|
let mov = shared;
|
||||||
|
let r_mov: &_ = shared;
|
||||||
|
ref_id(shared);
|
||||||
|
|
||||||
|
identity(unique);
|
||||||
|
identity(shared);
|
||||||
|
}
|
||||||
|
fn identity<T>(t: T) -> T {
|
||||||
|
t
|
||||||
|
}
|
||||||
|
fn ref_mut_id(x: &mut ()) -> &mut () {
|
||||||
|
x
|
||||||
|
//^ &mut *
|
||||||
|
}
|
||||||
|
fn ref_id(x: &()) -> &() {
|
||||||
|
x
|
||||||
|
}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,7 @@ impl StaticIndex<'_> {
|
||||||
chaining_hints: true,
|
chaining_hints: true,
|
||||||
closure_return_type_hints: true,
|
closure_return_type_hints: true,
|
||||||
lifetime_elision_hints: LifetimeElisionHints::Never,
|
lifetime_elision_hints: LifetimeElisionHints::Never,
|
||||||
|
reborrow_hints: false,
|
||||||
hide_named_constructor_hints: false,
|
hide_named_constructor_hints: false,
|
||||||
param_names_for_lifetime_elision_hints: false,
|
param_names_for_lifetime_elision_hints: false,
|
||||||
max_length: Some(25),
|
max_length: Some(25),
|
||||||
|
|
|
@ -256,6 +256,8 @@ config_data! {
|
||||||
inlayHints_chainingHints: bool = "true",
|
inlayHints_chainingHints: bool = "true",
|
||||||
/// Whether to show inlay type hints for return types of closures with blocks.
|
/// Whether to show inlay type hints for return types of closures with blocks.
|
||||||
inlayHints_closureReturnTypeHints: bool = "false",
|
inlayHints_closureReturnTypeHints: bool = "false",
|
||||||
|
/// Whether to show inlay type hints for compiler inserted reborrows.
|
||||||
|
inlayHints_reborrowHints: bool = "false",
|
||||||
/// Whether to show inlay type hints for elided lifetimes in function signatures.
|
/// Whether to show inlay type hints for elided lifetimes in function signatures.
|
||||||
inlayHints_lifetimeElisionHints: LifetimeElisionDef = "\"never\"",
|
inlayHints_lifetimeElisionHints: LifetimeElisionDef = "\"never\"",
|
||||||
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
|
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
|
||||||
|
@ -866,6 +868,7 @@ impl Config {
|
||||||
LifetimeElisionDef::SkipTrivial => LifetimeElisionHints::SkipTrivial,
|
LifetimeElisionDef::SkipTrivial => LifetimeElisionHints::SkipTrivial,
|
||||||
},
|
},
|
||||||
hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints,
|
hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints,
|
||||||
|
reborrow_hints: self.data.inlayHints_reborrowHints,
|
||||||
param_names_for_lifetime_elision_hints: self
|
param_names_for_lifetime_elision_hints: self
|
||||||
.data
|
.data
|
||||||
.inlayHints_lifetimeElisionHints_useParameterNames,
|
.inlayHints_lifetimeElisionHints_useParameterNames,
|
||||||
|
|
|
@ -426,7 +426,11 @@ pub(crate) fn inlay_hint(
|
||||||
_ => inlay_hint.label.to_string(),
|
_ => inlay_hint.label.to_string(),
|
||||||
}),
|
}),
|
||||||
position: match inlay_hint.kind {
|
position: match inlay_hint.kind {
|
||||||
InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()),
|
// before annotated thing
|
||||||
|
InlayKind::ParameterHint | InlayKind::ImplicitReborrow => {
|
||||||
|
position(line_index, inlay_hint.range.start())
|
||||||
|
}
|
||||||
|
// after annotated thing
|
||||||
InlayKind::ClosureReturnTypeHint
|
InlayKind::ClosureReturnTypeHint
|
||||||
| InlayKind::TypeHint
|
| InlayKind::TypeHint
|
||||||
| InlayKind::ChainingHint
|
| InlayKind::ChainingHint
|
||||||
|
@ -438,7 +442,9 @@ pub(crate) fn inlay_hint(
|
||||||
InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => {
|
InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => {
|
||||||
Some(lsp_ext::InlayHintKind::TYPE)
|
Some(lsp_ext::InlayHintKind::TYPE)
|
||||||
}
|
}
|
||||||
InlayKind::GenericParamListHint | InlayKind::LifetimeHint => None,
|
InlayKind::GenericParamListHint
|
||||||
|
| InlayKind::LifetimeHint
|
||||||
|
| InlayKind::ImplicitReborrow => None,
|
||||||
},
|
},
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
padding_left: Some(match inlay_hint.kind {
|
padding_left: Some(match inlay_hint.kind {
|
||||||
|
@ -447,6 +453,7 @@ pub(crate) fn inlay_hint(
|
||||||
InlayKind::ChainingHint => true,
|
InlayKind::ChainingHint => true,
|
||||||
InlayKind::GenericParamListHint => false,
|
InlayKind::GenericParamListHint => false,
|
||||||
InlayKind::LifetimeHint => false,
|
InlayKind::LifetimeHint => false,
|
||||||
|
InlayKind::ImplicitReborrow => false,
|
||||||
}),
|
}),
|
||||||
padding_right: Some(match inlay_hint.kind {
|
padding_right: Some(match inlay_hint.kind {
|
||||||
InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => {
|
InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => {
|
||||||
|
@ -455,6 +462,7 @@ pub(crate) fn inlay_hint(
|
||||||
InlayKind::ParameterHint => true,
|
InlayKind::ParameterHint => true,
|
||||||
InlayKind::LifetimeHint => true,
|
InlayKind::LifetimeHint => true,
|
||||||
InlayKind::GenericParamListHint => false,
|
InlayKind::GenericParamListHint => false,
|
||||||
|
InlayKind::ImplicitReborrow => false,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,6 +378,11 @@ Whether to show inlay type hints for method chains.
|
||||||
--
|
--
|
||||||
Whether to show inlay type hints for return types of closures with blocks.
|
Whether to show inlay type hints for return types of closures with blocks.
|
||||||
--
|
--
|
||||||
|
[[rust-analyzer.inlayHints.reborrowHints]]rust-analyzer.inlayHints.reborrowHints (default: `false`)::
|
||||||
|
+
|
||||||
|
--
|
||||||
|
Whether to show inlay type hints for compiler inserted reborrows.
|
||||||
|
--
|
||||||
[[rust-analyzer.inlayHints.lifetimeElisionHints]]rust-analyzer.inlayHints.lifetimeElisionHints (default: `"never"`)::
|
[[rust-analyzer.inlayHints.lifetimeElisionHints]]rust-analyzer.inlayHints.lifetimeElisionHints (default: `"never"`)::
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
|
|
|
@ -800,6 +800,11 @@
|
||||||
"default": false,
|
"default": false,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"rust-analyzer.inlayHints.reborrowHints": {
|
||||||
|
"markdownDescription": "Whether to show inlay type hints for compiler inserted reborrows.",
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"rust-analyzer.inlayHints.lifetimeElisionHints": {
|
"rust-analyzer.inlayHints.lifetimeElisionHints": {
|
||||||
"markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.",
|
"markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.",
|
||||||
"default": "never",
|
"default": "never",
|
||||||
|
|
Loading…
Reference in a new issue