11771: feat: Visualize compiler inserted reborrows via inlay hints r=Veykril a=Veykril

Disabled by default.

![image](https://user-images.githubusercontent.com/3757771/159165178-baaf968a-4381-468e-933f-5326ca1b203d.png)

Closes https://github.com/rust-analyzer/rust-analyzer/issues/11275


Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2022-03-20 13:47:16 +00:00 committed by GitHub
commit 966b692422
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 17 deletions

View file

@ -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)

View file

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

View file

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

View file

@ -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,
@ -65,10 +67,7 @@ pub struct InlayHint {
// //
// * return types of closure expressions with blocks // * return types of closure expressions with blocks
// * elided lifetimes // * elided lifetimes
// // * compiler inserted reborrows
// **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations.
// This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
// https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2].
// //
// |=== // |===
// | Editor | Action Name // | Editor | Action Name
@ -116,17 +115,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) => {
closure_ret_hints(hints, sema, &famous_defs, config, it);
}
_ => (),
} }
ast::Expr::ClosureExpr(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 +363,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 +854,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 +862,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 +2137,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
}
"#, "#,
); );
} }

View file

@ -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),

View file

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

View file

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

View file

@ -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"`)::
+ +
-- --

View file

@ -795,6 +795,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",