Skip to content
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

Provide completion in struct patterns #1572

Merged
merged 1 commit into from Jul 21, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions crates/ra_hir/src/source_binder.rs
Expand Up @@ -266,9 +266,14 @@ impl SourceAnalyzer {
self.infer.as_ref()?.field_resolution(expr_id)
}

pub fn resolve_variant(&self, struct_lit: &ast::StructLit) -> Option<crate::VariantDef> {
pub fn resolve_struct_literal(&self, struct_lit: &ast::StructLit) -> Option<crate::VariantDef> {
let expr_id = self.body_source_map.as_ref()?.node_expr(&struct_lit.clone().into())?;
self.infer.as_ref()?.variant_resolution(expr_id)
self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
}

pub fn resolve_struct_pattern(&self, struct_pat: &ast::StructPat) -> Option<crate::VariantDef> {
let pat_id = self.body_source_map.as_ref()?.node_pat(&struct_pat.clone().into())?;
self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
}

pub fn resolve_macro_call(
Expand Down
2 changes: 1 addition & 1 deletion crates/ra_hir/src/ty.rs
Expand Up @@ -472,7 +472,7 @@ impl Ty {

/// Returns the type parameters of this type if it has some (i.e. is an ADT
/// or function); so if `self` is `Option<u32>`, this returns the `u32`.
fn substs(&self) -> Option<Substs> {
pub fn substs(&self) -> Option<Substs> {
match self {
Ty::Apply(ApplicationTy { parameters, .. }) => Some(parameters.clone()),
_ => None,
Expand Down
22 changes: 15 additions & 7 deletions crates/ra_hir/src/ty/infer.rs
Expand Up @@ -113,7 +113,8 @@ pub struct InferenceResult {
method_resolutions: FxHashMap<ExprId, Function>,
/// For each field access expr, records the field it resolves to.
field_resolutions: FxHashMap<ExprId, StructField>,
variant_resolutions: FxHashMap<ExprId, VariantDef>,
/// For each struct literal, records the variant it resolves to.
variant_resolutions: FxHashMap<ExprOrPatId, VariantDef>,
/// For each associated item record what it resolves to
assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
diagnostics: Vec<InferenceDiagnostic>,
Expand All @@ -128,8 +129,11 @@ impl InferenceResult {
pub fn field_resolution(&self, expr: ExprId) -> Option<StructField> {
self.field_resolutions.get(&expr).copied()
}
pub fn variant_resolution(&self, expr: ExprId) -> Option<VariantDef> {
self.variant_resolutions.get(&expr).copied()
pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option<VariantDef> {
self.variant_resolutions.get(&id.into()).copied()
}
pub fn variant_resolution_for_pat(&self, id: PatId) -> Option<VariantDef> {
self.variant_resolutions.get(&id.into()).copied()
}
pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option<ImplItem> {
self.assoc_resolutions.get(&id.into()).copied()
Expand Down Expand Up @@ -218,8 +222,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
self.result.field_resolutions.insert(expr, field);
}

fn write_variant_resolution(&mut self, expr: ExprId, variant: VariantDef) {
self.result.variant_resolutions.insert(expr, variant);
fn write_variant_resolution(&mut self, id: ExprOrPatId, variant: VariantDef) {
self.result.variant_resolutions.insert(id, variant);
}

fn write_assoc_resolution(&mut self, id: ExprOrPatId, item: ImplItem) {
Expand Down Expand Up @@ -678,8 +682,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
subpats: &[FieldPat],
expected: &Ty,
default_bm: BindingMode,
id: PatId,
) -> Ty {
let (ty, def) = self.resolve_variant(path);
if let Some(variant) = def {
self.write_variant_resolution(id.into(), variant);
}

self.unify(&ty, expected);

Expand Down Expand Up @@ -762,7 +770,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm)
}
Pat::Struct { path: ref p, args: ref fields } => {
self.infer_struct_pat(p.as_ref(), fields, expected, default_bm)
self.infer_struct_pat(p.as_ref(), fields, expected, default_bm, pat)
}
Pat::Path(path) => {
// FIXME use correct resolver for the surrounding expression
Expand Down Expand Up @@ -1064,7 +1072,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
Expr::StructLit { path, fields, spread } => {
let (ty, def_id) = self.resolve_variant(path.as_ref());
if let Some(variant) = def_id {
self.write_variant_resolution(tgt_expr, variant);
self.write_variant_resolution(tgt_expr.into(), variant);
}

let substs = ty.substs().unwrap_or_else(Substs::empty);
Expand Down
2 changes: 2 additions & 0 deletions crates/ra_ide_api/src/completion.rs
Expand Up @@ -4,6 +4,7 @@ mod presentation;

mod complete_dot;
mod complete_struct_literal;
mod complete_struct_pattern;
mod complete_pattern;
mod complete_fn_param;
mod complete_keyword;
Expand Down Expand Up @@ -65,6 +66,7 @@ pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Opti
complete_scope::complete_scope(&mut acc, &ctx);
complete_dot::complete_dot(&mut acc, &ctx);
complete_struct_literal::complete_struct_literal(&mut acc, &ctx);
complete_struct_pattern::complete_struct_pattern(&mut acc, &ctx);
complete_pattern::complete_pattern(&mut acc, &ctx);
complete_postfix::complete_postfix(&mut acc, &ctx);
Some(acc)
Expand Down
15 changes: 7 additions & 8 deletions crates/ra_ide_api/src/completion/complete_struct_literal.rs
@@ -1,23 +1,22 @@
use hir::{Substs, Ty};
use hir::Substs;

use crate::completion::{CompletionContext, Completions};

/// Complete fields in fields literals.
pub(super) fn complete_struct_literal(acc: &mut Completions, ctx: &CompletionContext) {
let (ty, variant) = match ctx.struct_lit_syntax.as_ref().and_then(|it| {
Some((ctx.analyzer.type_of(ctx.db, &it.clone().into())?, ctx.analyzer.resolve_variant(it)?))
Some((
ctx.analyzer.type_of(ctx.db, &it.clone().into())?,
ctx.analyzer.resolve_struct_literal(it)?,
))
}) {
Some(it) => it,
_ => return,
};

let ty_substs = match ty {
Ty::Apply(it) => it.parameters,
_ => Substs::empty(),
};
let substs = &ty.substs().unwrap_or_else(Substs::empty);

for field in variant.fields(ctx.db) {
acc.add_field(ctx, field, &ty_substs);
acc.add_field(ctx, field, substs);
}
}

Expand Down
94 changes: 94 additions & 0 deletions crates/ra_ide_api/src/completion/complete_struct_pattern.rs
@@ -0,0 +1,94 @@
use hir::Substs;

use crate::completion::{CompletionContext, Completions};

pub(super) fn complete_struct_pattern(acc: &mut Completions, ctx: &CompletionContext) {
let (ty, variant) = match ctx.struct_lit_pat.as_ref().and_then(|it| {
Some((
ctx.analyzer.type_of_pat(ctx.db, &it.clone().into())?,
ctx.analyzer.resolve_struct_pattern(it)?,
))
}) {
Some(it) => it,
_ => return,
};
let substs = &ty.substs().unwrap_or_else(Substs::empty);

for field in variant.fields(ctx.db) {
acc.add_field(ctx, field, substs);
}
}

#[cfg(test)]
mod tests {
use crate::completion::{do_completion, CompletionItem, CompletionKind};
use insta::assert_debug_snapshot_matches;

fn complete(code: &str) -> Vec<CompletionItem> {
do_completion(code, CompletionKind::Reference)
}

#[test]
fn test_struct_pattern_field() {
let completions = complete(
r"
struct S { foo: u32 }

fn process(f: S) {
match f {
S { f<|>: 92 } => (),
}
}
",
);
assert_debug_snapshot_matches!(completions, @r###"
⋮[
⋮ CompletionItem {
⋮ label: "foo",
⋮ source_range: [117; 118),
⋮ delete: [117; 118),
⋮ insert: "foo",
⋮ kind: Field,
⋮ detail: "u32",
⋮ },
⋮]
"###);
}

#[test]
fn test_struct_pattern_enum_variant() {
let completions = complete(
r"
enum E {
S { foo: u32, bar: () }
}

fn process(e: E) {
match e {
E::S { <|> } => (),
}
}
",
);
assert_debug_snapshot_matches!(completions, @r###"
⋮[
⋮ CompletionItem {
⋮ label: "bar",
⋮ source_range: [161; 161),
⋮ delete: [161; 161),
⋮ insert: "bar",
⋮ kind: Field,
⋮ detail: "()",
⋮ },
⋮ CompletionItem {
⋮ label: "foo",
⋮ source_range: [161; 161),
⋮ delete: [161; 161),
⋮ insert: "foo",
⋮ kind: Field,
⋮ detail: "u32",
⋮ },
⋮]
"###);
}
}
11 changes: 8 additions & 3 deletions crates/ra_ide_api/src/completion/completion_context.rs
Expand Up @@ -21,6 +21,7 @@ pub(crate) struct CompletionContext<'a> {
pub(super) function_syntax: Option<ast::FnDef>,
pub(super) use_item_syntax: Option<ast::UseItem>,
pub(super) struct_lit_syntax: Option<ast::StructLit>,
pub(super) struct_lit_pat: Option<ast::StructPat>,
pub(super) is_param: bool,
/// If a name-binding or reference to a const in a pattern.
/// Irrefutable patterns (like let) are excluded.
Expand Down Expand Up @@ -60,6 +61,7 @@ impl<'a> CompletionContext<'a> {
function_syntax: None,
use_item_syntax: None,
struct_lit_syntax: None,
struct_lit_pat: None,
is_param: false,
is_pat_binding: false,
is_trivial_path: false,
Expand Down Expand Up @@ -106,8 +108,7 @@ impl<'a> CompletionContext<'a> {
// Otherwise, see if this is a declaration. We can use heuristics to
// suggest declaration names, see `CompletionKind::Magic`.
if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
if is_node::<ast::BindPat>(name.syntax()) {
let bind_pat = name.syntax().ancestors().find_map(ast::BindPat::cast).unwrap();
if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
let parent = bind_pat.syntax().parent();
if parent.clone().and_then(ast::MatchArm::cast).is_some()
|| parent.and_then(ast::Condition::cast).is_some()
Expand All @@ -119,6 +120,10 @@ impl<'a> CompletionContext<'a> {
self.is_param = true;
return;
}
if name.syntax().ancestors().find_map(ast::FieldPatList::cast).is_some() {
self.struct_lit_pat =
find_node_at_offset(original_parse.tree().syntax(), self.offset);
}
}
}

Expand Down Expand Up @@ -235,7 +240,7 @@ fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Op
}

fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
match node.ancestors().filter_map(N::cast).next() {
match node.ancestors().find_map(N::cast) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

None => false,
Some(n) => n.syntax().text_range() == node.text_range(),
}
Expand Down