From 6b696fced82e8f5ce80a543106f036a0828ccebe Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 14 May 2022 14:26:08 +0200 Subject: [PATCH 1/3] feat: Add binding mode inlay hints --- crates/hir-ty/src/diagnostics/match_check.rs | 2 +- crates/hir-ty/src/infer.rs | 4 +- crates/hir-ty/src/infer/pat.rs | 9 +- crates/hir-ty/src/lib.rs | 3 +- crates/hir/src/lib.rs | 6 + crates/hir/src/semantics.rs | 24 +++- crates/hir/src/source_analyzer.rs | 41 ++++++- crates/ide/src/inlay_hints.rs | 112 ++++++++++++++++++- crates/ide/src/static_index.rs | 1 + crates/rust-analyzer/src/config.rs | 3 + crates/rust-analyzer/src/to_proto.rs | 35 +++--- crates/syntax/src/ast/expr_ext.rs | 2 +- 12 files changed, 201 insertions(+), 41 deletions(-) diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs index d21ea32d9e4..e00dfdf2fa7 100644 --- a/crates/hir-ty/src/diagnostics/match_check.rs +++ b/crates/hir-ty/src/diagnostics/match_check.rs @@ -105,7 +105,7 @@ impl<'a> PatCtxt<'a> { self.infer.pat_adjustments.get(&pat).map(|it| &**it).unwrap_or_default().iter().rev().fold( unadjusted_pat, |subpattern, ref_ty| Pat { - ty: ref_ty.target.clone(), + ty: ref_ty.clone(), kind: Box::new(PatKind::Deref { subpattern }), }, ) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 2a11c9d9bf1..a19ff8bf601 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -297,7 +297,7 @@ pub struct InferenceResult { /// Interned Unknown to return references to. standard_types: InternedStandardTypes, /// Stores the types which were implicitly dereferenced in pattern binding modes. - pub pat_adjustments: FxHashMap>, + pub pat_adjustments: FxHashMap>, pub pat_binding_modes: FxHashMap, pub expr_adjustments: FxHashMap>, } @@ -445,7 +445,7 @@ impl<'a> InferenceContext<'a> { adjustment.target = table.resolve_completely(adjustment.target.clone()); } for adjustment in result.pat_adjustments.values_mut().flatten() { - adjustment.target = table.resolve_completely(adjustment.target.clone()); + *adjustment = table.resolve_completely(adjustment.clone()); } result } diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 600b82ca414..f8131314c69 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -11,9 +11,7 @@ use hir_def::{ use hir_expand::name::Name; use crate::{ - infer::{ - Adjust, Adjustment, AutoBorrow, BindingMode, Expectation, InferenceContext, TypeMismatch, - }, + infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, lower::lower_to_chalk_mutability, static_lifetime, ConcreteConst, ConstValue, Interner, Substitution, Ty, TyBuilder, TyExt, TyKind, @@ -105,10 +103,7 @@ impl<'a> InferenceContext<'a> { if is_non_ref_pat(&self.body, pat) { let mut pat_adjustments = Vec::new(); while let Some((inner, _lifetime, mutability)) = expected.as_reference() { - pat_adjustments.push(Adjustment { - target: expected.clone(), - kind: Adjust::Borrow(AutoBorrow::Ref(mutability)), - }); + pat_adjustments.push(expected.clone()); expected = self.resolve_ty_shallow(inner); default_bm = match default_bm { BindingMode::Move => BindingMode::Ref(mutability), diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 85d8ccc0772..15bcbda25de 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -47,7 +47,8 @@ pub use autoderef::autoderef; pub use builder::{ParamKind, TyBuilder}; pub use chalk_ext::*; pub use infer::{ - could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, InferenceResult, + could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic, + InferenceResult, }; pub use interner::Interner; pub use lower::{ diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 39f88937d6e..4f8fc6bf567 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3332,6 +3332,12 @@ impl Callable { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum BindingMode { + Move, + Ref(Mutability), +} + /// For IDE only #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ScopeDef { diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index d887dae99c1..48661ec4ebe 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -30,9 +30,9 @@ use crate::{ db::HirDatabase, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, source_analyzer::{resolve_hir_path, SourceAnalyzer}, - Access, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource, HirFileId, Impl, - InFile, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name, Path, ScopeDef, - ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef, + Access, BindingMode, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource, + HirFileId, Impl, InFile, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name, Path, + ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -336,6 +336,14 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.type_of_self(param) } + pub fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> { + self.imp.pattern_adjustments(pat) + } + + pub fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option { + self.imp.binding_mode_of_pat(pat) + } + pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option { self.imp.resolve_method_call(call).map(Function::from) } @@ -951,6 +959,16 @@ impl<'db> SemanticsImpl<'db> { self.analyze(param.syntax())?.type_of_self(self.db, param) } + fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> { + self.analyze(pat.syntax()) + .and_then(|it| it.pattern_adjustments(self.db, pat)) + .unwrap_or_default() + } + + fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option { + self.analyze(pat.syntax())?.binding_mode_of_pat(self.db, pat) + } + fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option { self.analyze(call.syntax())?.resolve_method_call(self.db, call).map(|(id, _)| id) } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 7a65fd99cfa..b5e6d99093d 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -34,15 +34,16 @@ use hir_ty::{ Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt, TyLoweringContext, }; +use smallvec::SmallVec; use syntax::{ ast::{self, AstNode}, SyntaxKind, SyntaxNode, TextRange, TextSize, }; use crate::{ - db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BuiltinAttr, BuiltinType, Const, - Field, Function, Local, Macro, ModuleDef, Static, Struct, ToolModule, Trait, Type, TypeAlias, - Variant, + db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr, + BuiltinType, Const, Field, Function, Local, Macro, ModuleDef, Static, Struct, ToolModule, + Trait, Type, TypeAlias, Variant, }; /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of @@ -182,7 +183,7 @@ impl SourceAnalyzer { let coerced = infer .pat_adjustments .get(&pat_id) - .and_then(|adjusts| adjusts.last().map(|adjust| adjust.target.clone())); + .and_then(|adjusts| adjusts.last().map(|adjust| adjust.clone())); let ty = infer[pat_id].clone(); let mk_ty = |ty| Type::new_with_resolver(db, &self.resolver, ty); Some((mk_ty(ty), coerced.map(mk_ty))) @@ -199,6 +200,38 @@ impl SourceAnalyzer { Some(Type::new_with_resolver(db, &self.resolver, ty)) } + pub(crate) fn binding_mode_of_pat( + &self, + _db: &dyn HirDatabase, + pat: &ast::IdentPat, + ) -> Option { + let pat_id = self.pat_id(&pat.clone().into())?; + let infer = self.infer.as_ref()?; + infer.pat_binding_modes.get(&pat_id).map(|bm| match bm { + hir_ty::BindingMode::Move => BindingMode::Move, + hir_ty::BindingMode::Ref(hir_ty::Mutability::Mut) => BindingMode::Ref(Mutability::Mut), + hir_ty::BindingMode::Ref(hir_ty::Mutability::Not) => { + BindingMode::Ref(Mutability::Shared) + } + }) + } + pub(crate) fn pattern_adjustments( + &self, + db: &dyn HirDatabase, + pat: &ast::Pat, + ) -> Option> { + let pat_id = self.pat_id(&pat)?; + let infer = self.infer.as_ref()?; + Some( + infer + .pat_adjustments + .get(&pat_id)? + .iter() + .map(|ty| Type::new_with_resolver(db, &self.resolver, ty.clone())) + .collect(), + ) + } + pub(crate) fn resolve_method_call( &self, db: &dyn HirDatabase, diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index a7cb1a599f5..6906aa60907 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -1,5 +1,5 @@ use either::Either; -use hir::{known, Callable, HasVisibility, HirDisplay, Semantics, TypeInfo}; +use hir::{known, Callable, HasVisibility, HirDisplay, Mutability, Semantics, TypeInfo}; use ide_db::{ base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap, RootDatabase, @@ -21,6 +21,7 @@ pub struct InlayHintsConfig { pub chaining_hints: bool, pub reborrow_hints: ReborrowHints, pub closure_return_type_hints: bool, + pub binding_mode_hints: bool, pub lifetime_elision_hints: LifetimeElisionHints, pub param_names_for_lifetime_elision_hints: bool, pub hide_named_constructor_hints: bool, @@ -43,10 +44,11 @@ pub enum ReborrowHints { #[derive(Clone, Debug, PartialEq, Eq)] pub enum InlayKind { + BindingModeHint, ChainingHint, ClosureReturnTypeHint, GenericParamListHint, - ImplicitReborrow, + ImplicitReborrowHint, LifetimeHint, ParameterHint, TypeHint, @@ -135,8 +137,11 @@ fn hints( 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::Pat::cast(node.clone()) { + binding_mode_hints(hints, sema, config, &it); + if let ast::Pat::IdentPat(it) = it { + bind_pat_hints(hints, sema, config, &it); + } } else if let Some(it) = ast::Fn::cast(node) { lifetime_hints(hints, config, it); } @@ -383,6 +388,8 @@ fn reborrow_hints( return None; } + // let descended = sema.descend_node_into_attributes(expr.clone()).pop(); + // let desc_expr = descended.as_ref().unwrap_or(expr); let mutability = sema.is_implicit_reborrow(expr)?; let label = match mutability { hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*", @@ -391,7 +398,7 @@ fn reborrow_hints( }; acc.push(InlayHint { range: expr.syntax().text_range(), - kind: InlayKind::ImplicitReborrow, + kind: InlayKind::ImplicitReborrowHint, label: SmolStr::new_inline(label), }); Some(()) @@ -497,6 +504,51 @@ fn param_name_hints( Some(()) } +fn binding_mode_hints( + acc: &mut Vec, + sema: &Semantics, + config: &InlayHintsConfig, + pat: &ast::Pat, +) -> Option<()> { + if !config.binding_mode_hints { + return None; + } + + let range = pat.syntax().text_range(); + sema.pattern_adjustments(&pat).iter().for_each(|ty| { + let reference = ty.is_reference(); + let mut_reference = ty.is_mutable_reference(); + let r = match (reference, mut_reference) { + (true, true) => "&mut", + (true, false) => "&", + _ => return, + }; + acc.push(InlayHint { + range, + kind: InlayKind::BindingModeHint, + label: SmolStr::new_inline(r), + }); + }); + match pat { + ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => { + let bm = sema.binding_mode_of_pat(pat)?; + let bm = match bm { + hir::BindingMode::Move => return None, + hir::BindingMode::Ref(Mutability::Mut) => "ref mut", + hir::BindingMode::Ref(Mutability::Shared) => "ref", + }; + acc.push(InlayHint { + range, + kind: InlayKind::BindingModeHint, + label: SmolStr::new_inline(bm), + }); + } + _ => (), + } + + Some(()) +} + fn bind_pat_hints( acc: &mut Vec, sema: &Semantics, @@ -681,6 +733,7 @@ fn should_not_display_type_hint( match_ast! { match node { ast::LetStmt(it) => return it.ty().is_some(), + // FIXME: We might wanna show type hints in parameters for non-top level patterns as well ast::Param(it) => return it.ty().is_some(), ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty), ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty), @@ -866,9 +919,10 @@ mod tests { parameter_hints: false, chaining_hints: false, lifetime_elision_hints: LifetimeElisionHints::Never, - hide_named_constructor_hints: false, closure_return_type_hints: false, reborrow_hints: ReborrowHints::Always, + binding_mode_hints: false, + hide_named_constructor_hints: false, param_names_for_lifetime_elision_hints: false, max_length: None, }; @@ -878,6 +932,7 @@ mod tests { chaining_hints: true, reborrow_hints: ReborrowHints::Always, closure_return_type_hints: true, + binding_mode_hints: true, lifetime_elision_hints: LifetimeElisionHints::Always, ..DISABLED_CONFIG }; @@ -2191,6 +2246,51 @@ fn ref_mut_id(mut_ref: &mut ()) -> &mut () { fn ref_id(shared_ref: &()) -> &() { shared_ref } +"#, + ); + } + + #[test] + fn hints_binding_modes() { + check_with_config( + InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG }, + r#" +fn __( + (x,): (u32,), + (x,): &(u32,), + //^^^^& + //^ ref + (x,): &mut (u32,) + //^^^^&mut + //^ ref mut +) { + let (x,) = (0,); + let (x,) = &(0,); + //^^^^ & + //^ ref + let (x,) = &mut (0,); + //^^^^ &mut + //^ ref mut + let &mut (x,) = &mut (0,); + let (ref mut x,) = &mut (0,); + //^^^^^^^^^^^^ &mut + let &mut (ref mut x,) = &mut (0,); + let (mut x,) = &mut (0,); + //^^^^^^^^ &mut + match (0,) { + (x,) => () + } + match &(0,) { + (x,) => () + //^^^^ & + //^ ref + } + match &mut (0,) { + (x,) => () + //^^^^ &mut + //^ ref mut + } +} "#, ); } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 48159f5958d..53820860f5d 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -114,6 +114,7 @@ impl StaticIndex<'_> { reborrow_hints: crate::ReborrowHints::Never, hide_named_constructor_hints: false, param_names_for_lifetime_elision_hints: false, + binding_mode_hints: false, max_length: Some(25), }, file_id, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 150cc21a17b..932b38f399c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -254,6 +254,8 @@ config_data! { /// The path structure for newly inserted paths to use. imports_prefix: ImportPrefixDef = "\"plain\"", + /// Whether to show inlay type hints for binding modes. + inlayHints_bindingModeHints_enable: bool = "false", /// Whether to show inlay type hints for method chains. inlayHints_chainingHints_enable: bool = "true", /// Whether to show inlay type hints for return types of closures with blocks. @@ -997,6 +999,7 @@ impl Config { ReborrowHintsDef::Never => ide::ReborrowHints::Never, ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly, }, + binding_mode_hints: self.data.inlayHints_bindingModeHints_enable, 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 ff348959add..cd9226d03da 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -418,9 +418,9 @@ pub(crate) fn inlay_hint( lsp_types::InlayHint { position: match inlay_hint.kind { // before annotated thing - InlayKind::ParameterHint | InlayKind::ImplicitReborrow => { - position(line_index, inlay_hint.range.start()) - } + InlayKind::ParameterHint + | InlayKind::ImplicitReborrowHint + | InlayKind::BindingModeHint => position(line_index, inlay_hint.range.start()), // after annotated thing InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint @@ -439,27 +439,30 @@ pub(crate) fn inlay_hint( InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => { Some(lsp_types::InlayHintKind::TYPE) } - InlayKind::GenericParamListHint + InlayKind::BindingModeHint + | InlayKind::GenericParamListHint | InlayKind::LifetimeHint - | InlayKind::ImplicitReborrow => None, + | InlayKind::ImplicitReborrowHint => None, }, tooltip: None, padding_left: Some(match inlay_hint.kind { InlayKind::TypeHint => !render_colons, - InlayKind::ParameterHint | InlayKind::ClosureReturnTypeHint => false, InlayKind::ChainingHint => true, - InlayKind::GenericParamListHint => false, - InlayKind::LifetimeHint => false, - InlayKind::ImplicitReborrow => false, + InlayKind::BindingModeHint + | InlayKind::ClosureReturnTypeHint + | InlayKind::GenericParamListHint + | InlayKind::ImplicitReborrowHint + | InlayKind::LifetimeHint + | InlayKind::ParameterHint => false, }), padding_right: Some(match inlay_hint.kind { - InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => { - false - } - InlayKind::ParameterHint => true, - InlayKind::LifetimeHint => true, - InlayKind::GenericParamListHint => false, - InlayKind::ImplicitReborrow => false, + InlayKind::ChainingHint + | InlayKind::ClosureReturnTypeHint + | InlayKind::GenericParamListHint + | InlayKind::ImplicitReborrowHint + | InlayKind::TypeHint => false, + InlayKind::BindingModeHint => inlay_hint.label != "&", + InlayKind::ParameterHint | InlayKind::LifetimeHint => true, }), text_edits: None, data: None, diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs index 17785152bc5..db66d08a73b 100644 --- a/crates/syntax/src/ast/expr_ext.rs +++ b/crates/syntax/src/ast/expr_ext.rs @@ -273,7 +273,7 @@ impl ast::ArrayExpr { } fn is_repeat(&self) -> bool { - self.syntax().children_with_tokens().any(|it| it.kind() == T![;]) + self.semicolon_token().is_some() } } From 7cbde1b3a50044e68ca00e427961a1a460b13442 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 14 May 2022 15:00:14 +0200 Subject: [PATCH 2/3] Enable reborrow hints in attribute calls --- crates/ide/src/inlay_hints.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 6906aa60907..23a46c02762 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -388,9 +388,9 @@ fn reborrow_hints( return None; } - // let descended = sema.descend_node_into_attributes(expr.clone()).pop(); - // let desc_expr = descended.as_ref().unwrap_or(expr); - let mutability = sema.is_implicit_reborrow(expr)?; + let descended = sema.descend_node_into_attributes(expr.clone()).pop(); + let desc_expr = descended.as_ref().unwrap_or(expr); + let mutability = sema.is_implicit_reborrow(desc_expr)?; let label = match mutability { hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*", hir::Mutability::Mut => "&mut *", From 977f0ba968203918b135c3d99380db9f9a5c2c19 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 14 May 2022 15:18:18 +0200 Subject: [PATCH 3/3] Update package.json --- docs/user/generated_config.adoc | 5 +++++ editors/code/package.json | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 35e0a4082eb..ca7e3313134 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -344,6 +344,11 @@ Whether to allow import insertion to merge new imports into single path glob imp -- The path structure for newly inserted paths to use. -- +[[rust-analyzer.inlayHints.bindingModeHints.enable]]rust-analyzer.inlayHints.bindingModeHints.enable (default: `false`):: ++ +-- +Whether to show inlay type hints for binding modes. +-- [[rust-analyzer.inlayHints.chainingHints.enable]]rust-analyzer.inlayHints.chainingHints.enable (default: `true`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 62b0f3c0baf..3d2f2a0b48a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -784,6 +784,11 @@ "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from." ] }, + "rust-analyzer.inlayHints.bindingModeHints.enable": { + "markdownDescription": "Whether to show inlay type hints for binding modes.", + "default": false, + "type": "boolean" + }, "rust-analyzer.inlayHints.chainingHints.enable": { "markdownDescription": "Whether to show inlay type hints for method chains.", "default": true,