Skip to content

feat: Visualize compiler inserted reborrows via inlay hints #11771

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

Merged
merged 2 commits into from
Mar 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use base_db::{FileId, FileRange};
use hir_def::{
body, macro_id_to_def_id,
resolver::{self, HasResolver, Resolver, TypeNs},
type_ref::Mutability,
AsMacroCall, FunctionId, MacroId, TraitId, VariantId,
};
use hir_expand::{
Expand Down Expand Up @@ -313,6 +314,11 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
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> {
self.imp.type_of_expr(expr)
}
Expand Down Expand Up @@ -900,6 +906,10 @@ impl<'db> SemanticsImpl<'db> {
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> {
self.analyze(expr.syntax())
.type_of_expr(self.db, expr)
Expand Down
21 changes: 20 additions & 1 deletion crates/hir/src/source_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ use hir_def::{
macro_id_to_def_id,
path::{ModPath, Path, PathKind},
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
type_ref::Mutability,
AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, ModuleDefId, VariantId,
};
use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
use hir_ty::{
diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
InferenceResult, Interner, Substitution, TyExt, TyLoweringContext,
Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt,
TyLoweringContext,
};
use syntax::{
ast::{self, AstNode},
Expand Down Expand Up @@ -139,6 +141,23 @@ impl SourceAnalyzer {
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(
&self,
db: &dyn HirDatabase,
Expand Down
4 changes: 3 additions & 1 deletion crates/hir_ty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ use crate::{db::HirDatabase, utils::generics};
pub use autoderef::autoderef;
pub use builder::{ParamKind, TyBuilder};
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 lower::{
associated_type_shorthand_candidates, callable_item_sig, CallableDefId, ImplTraitLoweringMode,
Expand Down
83 changes: 70 additions & 13 deletions crates/ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct InlayHintsConfig {
pub type_hints: bool,
pub parameter_hints: bool,
pub chaining_hints: bool,
pub reborrow_hints: bool,
pub closure_return_type_hints: bool,
pub lifetime_elision_hints: LifetimeElisionHints,
pub param_names_for_lifetime_elision_hints: bool,
Expand All @@ -35,6 +36,7 @@ pub enum LifetimeElisionHints {

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InlayKind {
ImplicitReborrow,
TypeHint,
ParameterHint,
ClosureReturnTypeHint,
Expand Down Expand Up @@ -65,10 +67,7 @@ pub struct InlayHint {
//
// * return types of closure expressions with blocks
// * elided lifetimes
//
// **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].
// * compiler inserted reborrows
//
// |===
// | Editor | Action Name
Expand Down Expand Up @@ -116,17 +115,16 @@ fn hints(
if let Some(expr) = ast::Expr::cast(node.clone()) {
chaining_hints(hints, sema, &famous_defs, config, &expr);
match expr {
ast::Expr::CallExpr(it) => {
param_name_hints(hints, sema, config, ast::Expr::from(it));
}
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));
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()) {
bind_pat_hints(hints, sema, config, &it);
} else if let Some(it) = ast::Fn::cast(node) {
Expand Down Expand Up @@ -365,6 +363,28 @@ fn closure_ret_hints(
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(
acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>,
Expand Down Expand Up @@ -834,13 +854,15 @@ mod tests {
lifetime_elision_hints: LifetimeElisionHints::Never,
hide_named_constructor_hints: false,
closure_return_type_hints: false,
reborrow_hints: false,
param_names_for_lifetime_elision_hints: false,
max_length: None,
};
const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
type_hints: true,
parameter_hints: true,
chaining_hints: true,
reborrow_hints: true,
closure_return_type_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always,
..DISABLED_CONFIG
Expand Down Expand Up @@ -2115,6 +2137,41 @@ impl () {
// ^^^<'0, '1>
// ^'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
}
"#,
);
}
Expand Down
1 change: 1 addition & 0 deletions crates/ide/src/static_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ impl StaticIndex<'_> {
chaining_hints: true,
closure_return_type_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Never,
reborrow_hints: false,
hide_named_constructor_hints: false,
param_names_for_lifetime_elision_hints: false,
max_length: Some(25),
Expand Down
3 changes: 3 additions & 0 deletions crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ config_data! {
inlayHints_chainingHints: bool = "true",
/// Whether to show inlay type hints for return types of closures with blocks.
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.
inlayHints_lifetimeElisionHints: LifetimeElisionDef = "\"never\"",
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
Expand Down Expand Up @@ -866,6 +868,7 @@ impl Config {
LifetimeElisionDef::SkipTrivial => LifetimeElisionHints::SkipTrivial,
},
hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints,
reborrow_hints: self.data.inlayHints_reborrowHints,
param_names_for_lifetime_elision_hints: self
.data
.inlayHints_lifetimeElisionHints_useParameterNames,
Expand Down
12 changes: 10 additions & 2 deletions crates/rust-analyzer/src/to_proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,11 @@ pub(crate) fn inlay_hint(
_ => inlay_hint.label.to_string(),
}),
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::TypeHint
| InlayKind::ChainingHint
Expand All @@ -438,7 +442,9 @@ pub(crate) fn inlay_hint(
InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => {
Some(lsp_ext::InlayHintKind::TYPE)
}
InlayKind::GenericParamListHint | InlayKind::LifetimeHint => None,
InlayKind::GenericParamListHint
| InlayKind::LifetimeHint
| InlayKind::ImplicitReborrow => None,
},
tooltip: None,
padding_left: Some(match inlay_hint.kind {
Expand All @@ -447,6 +453,7 @@ pub(crate) fn inlay_hint(
InlayKind::ChainingHint => true,
InlayKind::GenericParamListHint => false,
InlayKind::LifetimeHint => false,
InlayKind::ImplicitReborrow => false,
}),
padding_right: Some(match inlay_hint.kind {
InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => {
Expand All @@ -455,6 +462,7 @@ pub(crate) fn inlay_hint(
InlayKind::ParameterHint => true,
InlayKind::LifetimeHint => true,
InlayKind::GenericParamListHint => false,
InlayKind::ImplicitReborrow => false,
}),
}
}
Expand Down
5 changes: 5 additions & 0 deletions docs/user/generated_config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
--
[[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"`)::
+
--
Expand Down
5 changes: 5 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,11 @@
"default": false,
"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": {
"markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.",
"default": "never",
Expand Down