From 37b48ceb8f57dc9826e48e72e718c38264f5ccb7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 20 Mar 2022 14:38:16 +0100 Subject: [PATCH] feat: Visualize compiler inserted reborrows via inlay hints --- crates/hir/src/semantics.rs | 10 ++++ crates/hir/src/source_analyzer.rs | 21 +++++++- crates/hir_ty/src/lib.rs | 4 +- crates/ide/src/inlay_hints.rs | 78 ++++++++++++++++++++++++---- crates/ide/src/static_index.rs | 1 + crates/rust-analyzer/src/config.rs | 3 ++ crates/rust-analyzer/src/to_proto.rs | 12 ++++- docs/user/generated_config.adoc | 5 ++ editors/code/package.json | 5 ++ 9 files changed, 126 insertions(+), 13 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 7857edb521..c2b7e9bb52 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -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::{ @@ -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 { + self.imp.is_implicit_reborrow(expr) + } + pub fn type_of_expr(&self, expr: &ast::Expr) -> Option { self.imp.type_of_expr(expr) } @@ -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 { + self.analyze(expr.syntax()).is_implicit_reborrow(self.db, expr) + } + fn type_of_expr(&self, expr: &ast::Expr) -> Option { self.analyze(expr.syntax()) .type_of_expr(self.db, expr) diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 499817b6b8..576d063c43 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -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}, @@ -139,6 +141,23 @@ impl SourceAnalyzer { Some(res) } + pub(crate) fn is_implicit_reborrow( + &self, + db: &dyn HirDatabase, + expr: &ast::Expr, + ) -> Option { + 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, diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs index d6a524d593..59e6fe2a04 100644 --- a/crates/hir_ty/src/lib.rs +++ b/crates/hir_ty/src/lib.rs @@ -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, diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 565bee88f3..ef1c155143 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -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, @@ -35,6 +36,7 @@ pub enum LifetimeElisionHints { #[derive(Clone, Debug, PartialEq, Eq)] pub enum InlayKind { + ImplicitReborrow, TypeHint, ParameterHint, ClosureReturnTypeHint, @@ -116,17 +118,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) { @@ -365,6 +366,28 @@ fn closure_ret_hints( Some(()) } +fn reborrow_hints( + acc: &mut Vec, + sema: &Semantics, + 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, sema: &Semantics, @@ -834,6 +857,7 @@ 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, }; @@ -841,6 +865,7 @@ mod tests { type_hints: true, parameter_hints: true, chaining_hints: true, + reborrow_hints: true, closure_return_type_hints: true, lifetime_elision_hints: LifetimeElisionHints::Always, ..DISABLED_CONFIG @@ -2115,6 +2140,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 +} +fn ref_mut_id(x: &mut ()) -> &mut () { + x + //^ &mut * +} +fn ref_id(x: &()) -> &() { + x +} "#, ); } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 3c81bfa3a9..0980b87155 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -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), diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3afbeac47c..ab197c671d 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -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. @@ -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, diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 976542a491..b9182c4bc1 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -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 @@ -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 { @@ -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 => { @@ -455,6 +462,7 @@ pub(crate) fn inlay_hint( InlayKind::ParameterHint => true, InlayKind::LifetimeHint => true, InlayKind::GenericParamListHint => false, + InlayKind::ImplicitReborrow => false, }), } } diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 42f485b511..61bd36202d 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -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"`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 0343abd0a3..18d4b39b83 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -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",