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
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
@@ -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 }),
},
)
4 changes: 2 additions & 2 deletions crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
@@ -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>>,
}
@@ -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
}
9 changes: 2 additions & 7 deletions crates/hir-ty/src/infer/pat.rs
Original file line number Diff line number Diff line change
@@ -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),
3 changes: 2 additions & 1 deletion crates/hir-ty/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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::{
6 changes: 6 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 {
24 changes: 21 additions & 3 deletions crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
@@ -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<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)
}
@@ -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)
}
41 changes: 37 additions & 4 deletions crates/hir/src/source_analyzer.rs
Original file line number Diff line number Diff line change
@@ -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<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,
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,
@@ -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,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(())
@@ -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>,
@@ -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
}
}
"#,
);
}
1 change: 1 addition & 0 deletions crates/ide/src/static_index.rs
Original file line number Diff line number Diff line change
@@ -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,
Loading