Skip to content

feat: Add binding mode inlay hints #12253

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 3 commits into from
May 16, 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
2 changes: 1 addition & 1 deletion crates/hir-ty/src/diagnostics/match_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
},
)
Expand Down
4 changes: 2 additions & 2 deletions crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PatId, Vec<Adjustment>>,
pub pat_adjustments: FxHashMap<PatId, Vec<Ty>>,
pub pat_binding_modes: FxHashMap<PatId, BindingMode>,
pub expr_adjustments: FxHashMap<ExprId, Vec<Adjustment>>,
}
Expand Down Expand Up @@ -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
}
Expand Down
9 changes: 2 additions & 7 deletions crates/hir-ty/src/infer/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion crates/hir-ty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
6 changes: 6 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 21 additions & 3 deletions crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<BindingMode> {
self.imp.binding_mode_of_pat(pat)
}

pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
self.imp.resolve_method_call(call).map(Function::from)
}
Expand Down Expand Up @@ -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<BindingMode> {
self.analyze(pat.syntax())?.binding_mode_of_pat(self.db, pat)
}

fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> {
self.analyze(call.syntax())?.resolve_method_call(self.db, call).map(|(id, _)| id)
}
Expand Down
41 changes: 37 additions & 4 deletions crates/hir/src/source_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand All @@ -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<BindingMode> {
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<SmallVec<[Type; 1]>> {
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,
Expand Down
114 changes: 107 additions & 7 deletions crates/ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -43,10 +44,11 @@ pub enum ReborrowHints {

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InlayKind {
BindingModeHint,
ChainingHint,
ClosureReturnTypeHint,
GenericParamListHint,
ImplicitReborrow,
ImplicitReborrowHint,
LifetimeHint,
ParameterHint,
TypeHint,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -383,15 +388,17 @@ fn reborrow_hints(
return None;
}

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 *",
_ => return None,
};
acc.push(InlayHint {
range: expr.syntax().text_range(),
kind: InlayKind::ImplicitReborrow,
kind: InlayKind::ImplicitReborrowHint,
label: SmolStr::new_inline(label),
});
Some(())
Expand Down Expand Up @@ -497,6 +504,51 @@ fn param_name_hints(
Some(())
}

fn binding_mode_hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>,
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<InlayHint>,
sema: &Semantics<RootDatabase>,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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,
};
Expand All @@ -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
};
Expand Down Expand Up @@ -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
}
}
"#,
);
}
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<'_> {
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,
Expand Down
Loading