Make CompletionContext expected_type smarter

This commit is contained in:
Lukas Tobias Wirth 2021-05-03 21:34:34 +02:00
parent 544a93ee08
commit 121bd5c533
2 changed files with 113 additions and 117 deletions

View File

@ -736,28 +736,6 @@ fn f() {}
); );
} }
#[test]
fn completes_function() {
check(
r#"
fn foo(
a: i32,
b: i32
) {
}
fn main() {
fo$0
}
"#,
expect![[r#"
fn main() fn()
fn foo() fn(i32, i32)
"#]],
);
}
#[test] #[test]
fn completes_self_enum() { fn completes_self_enum() {
check( check(

View File

@ -301,103 +301,108 @@ fn fill_impl_def(&mut self) {
.find_map(ast::Impl::cast); .find_map(ast::Impl::cast);
} }
fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
let mut node = match self.token.parent() {
Some(it) => it,
None => return (None, None),
};
loop {
break match_ast! {
match node {
ast::LetStmt(it) => {
cov_mark::hit!(expected_type_let_with_leading_char);
cov_mark::hit!(expected_type_let_without_leading_char);
let ty = it.pat()
.and_then(|pat| self.sema.type_of_pat(&pat));
let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
ident.name().map(NameOrNameRef::Name)
} else {
None
};
(ty, name)
},
ast::ArgList(_it) => {
cov_mark::hit!(expected_type_fn_param_with_leading_char);
cov_mark::hit!(expected_type_fn_param_without_leading_char);
ActiveParameter::at_token(
&self.sema,
self.token.clone(),
).map(|ap| {
let name = ap.ident().map(NameOrNameRef::Name);
(Some(ap.ty), name)
})
.unwrap_or((None, None))
},
ast::RecordExprFieldList(_it) => {
cov_mark::hit!(expected_type_struct_field_without_leading_char);
self.token.prev_sibling_or_token()
.and_then(|se| se.into_node())
.and_then(|node| ast::RecordExprField::cast(node))
.and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf)))
.map(|(f, rf)|(
Some(f.0.ty(self.db)),
rf.field_name().map(NameOrNameRef::NameRef),
))
.unwrap_or((None, None))
},
ast::RecordExprField(it) => {
cov_mark::hit!(expected_type_struct_field_with_leading_char);
self.sema
.resolve_record_field(&it)
.map(|f|(
Some(f.0.ty(self.db)),
it.field_name().map(NameOrNameRef::NameRef),
))
.unwrap_or((None, None))
},
ast::MatchExpr(it) => {
cov_mark::hit!(expected_type_match_arm_without_leading_char);
let ty = it.expr()
.and_then(|e| self.sema.type_of_expr(&e));
(ty, None)
},
ast::IfExpr(it) => {
cov_mark::hit!(expected_type_if_let_without_leading_char);
let ty = it.condition()
.and_then(|cond| cond.expr())
.and_then(|e| self.sema.type_of_expr(&e));
(ty, None)
},
ast::IdentPat(it) => {
cov_mark::hit!(expected_type_if_let_with_leading_char);
cov_mark::hit!(expected_type_match_arm_with_leading_char);
let ty = self.sema.type_of_pat(&ast::Pat::from(it));
(ty, None)
},
ast::Fn(it) => {
cov_mark::hit!(expected_type_fn_ret_with_leading_char);
cov_mark::hit!(expected_type_fn_ret_without_leading_char);
let def = self.sema.to_def(&it);
(def.map(|def| def.ret_type(self.db)), None)
},
ast::Stmt(it) => (None, None),
_ => {
match node.parent() {
Some(n) => {
node = n;
continue;
},
None => (None, None),
}
},
}
};
}
}
fn fill( fn fill(
&mut self, &mut self,
original_file: &SyntaxNode, original_file: &SyntaxNode,
file_with_fake_ident: SyntaxNode, file_with_fake_ident: SyntaxNode,
offset: TextSize, offset: TextSize,
) { ) {
let (expected_type, expected_name) = { let (expected_type, expected_name) = self.expected_type_and_name();
let mut node = match self.token.parent() {
Some(it) => it,
None => return,
};
loop {
break match_ast! {
match node {
ast::LetStmt(it) => {
cov_mark::hit!(expected_type_let_with_leading_char);
cov_mark::hit!(expected_type_let_without_leading_char);
let ty = it.pat()
.and_then(|pat| self.sema.type_of_pat(&pat));
let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
ident.name().map(NameOrNameRef::Name)
} else {
None
};
(ty, name)
},
ast::ArgList(_it) => {
cov_mark::hit!(expected_type_fn_param_with_leading_char);
cov_mark::hit!(expected_type_fn_param_without_leading_char);
ActiveParameter::at_token(
&self.sema,
self.token.clone(),
).map(|ap| {
let name = ap.ident().map(NameOrNameRef::Name);
(Some(ap.ty), name)
})
.unwrap_or((None, None))
},
ast::RecordExprFieldList(_it) => {
cov_mark::hit!(expected_type_struct_field_without_leading_char);
self.token.prev_sibling_or_token()
.and_then(|se| se.into_node())
.and_then(|node| ast::RecordExprField::cast(node))
.and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf)))
.map(|(f, rf)|(
Some(f.0.ty(self.db)),
rf.field_name().map(NameOrNameRef::NameRef),
))
.unwrap_or((None, None))
},
ast::RecordExprField(it) => {
cov_mark::hit!(expected_type_struct_field_with_leading_char);
self.sema
.resolve_record_field(&it)
.map(|f|(
Some(f.0.ty(self.db)),
it.field_name().map(NameOrNameRef::NameRef),
))
.unwrap_or((None, None))
},
ast::MatchExpr(it) => {
cov_mark::hit!(expected_type_match_arm_without_leading_char);
let ty = it.expr()
.and_then(|e| self.sema.type_of_expr(&e));
(ty, None)
},
ast::IdentPat(it) => {
cov_mark::hit!(expected_type_if_let_with_leading_char);
cov_mark::hit!(expected_type_match_arm_with_leading_char);
let ty = self.sema.type_of_pat(&ast::Pat::from(it));
(ty, None)
},
ast::Fn(_it) => {
cov_mark::hit!(expected_type_fn_ret_with_leading_char);
cov_mark::hit!(expected_type_fn_ret_without_leading_char);
let ty = self.token.ancestors()
.find_map(|ancestor| ast::Expr::cast(ancestor))
.and_then(|expr| self.sema.type_of_expr(&expr));
(ty, None)
},
_ => {
match node.parent() {
Some(n) => {
node = n;
continue;
},
None => (None, None),
}
},
}
};
}
};
self.expected_type = expected_type; self.expected_type = expected_type;
self.expected_name = expected_name; self.expected_name = expected_name;
self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
@ -802,6 +807,7 @@ fn foo() {
#[test] #[test]
fn expected_type_if_let_without_leading_char() { fn expected_type_if_let_without_leading_char() {
cov_mark::check!(expected_type_if_let_without_leading_char);
check_expected_type_and_name( check_expected_type_and_name(
r#" r#"
enum Foo { Bar, Baz, Quux } enum Foo { Bar, Baz, Quux }
@ -811,8 +817,8 @@ fn foo() {
if let $0 = f { } if let $0 = f { }
} }
"#, "#,
expect![[r#"ty: (), name: ?"#]], expect![[r#"ty: Foo, name: ?"#]],
) // FIXME should be `ty: u32, name: ?` )
} }
#[test] #[test]
@ -840,8 +846,8 @@ fn foo() -> u32 {
$0 $0
} }
"#, "#,
expect![[r#"ty: (), name: ?"#]], expect![[r#"ty: u32, name: ?"#]],
) // FIXME this should be `ty: u32, name: ?` )
} }
#[test] #[test]
@ -852,6 +858,18 @@ fn expected_type_fn_ret_with_leading_char() {
fn foo() -> u32 { fn foo() -> u32 {
c$0 c$0
} }
"#,
expect![[r#"ty: u32, name: ?"#]],
)
}
#[test]
fn expected_type_fn_ret_fn_ref_fully_typed() {
check_expected_type_and_name(
r#"
fn foo() -> u32 {
foo$0
}
"#, "#,
expect![[r#"ty: u32, name: ?"#]], expect![[r#"ty: u32, name: ?"#]],
) )