⬆️ rust-analyzer

This commit is contained in:
Laurențiu Nicola 2022-08-02 09:05:16 +03:00
parent a1f1b95d00
commit 9d2cb42a41
45 changed files with 766 additions and 242 deletions

View file

@ -400,7 +400,7 @@ impl Completions {
) {
if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx {
cov_mark::hit!(enum_variant_pattern_path);
self.add_variant_pat(ctx, pat_ctx, variant, local_name);
self.add_variant_pat(ctx, pat_ctx, Some(path_ctx), variant, local_name);
return;
}
@ -484,12 +484,14 @@ impl Completions {
&mut self,
ctx: &CompletionContext<'_>,
pattern_ctx: &PatternContext,
path_ctx: Option<&PathCompletionCtx>,
variant: hir::Variant,
local_name: Option<hir::Name>,
) {
self.add_opt(render_variant_pat(
RenderContext::new(ctx),
pattern_ctx,
path_ctx,
variant,
local_name.clone(),
None,
@ -504,7 +506,14 @@ impl Completions {
path: hir::ModPath,
) {
let path = Some(&path);
self.add_opt(render_variant_pat(RenderContext::new(ctx), pattern_ctx, variant, None, path));
self.add_opt(render_variant_pat(
RenderContext::new(ctx),
pattern_ctx,
None,
variant,
None,
path,
));
}
pub(crate) fn add_struct_pat(

View file

@ -115,7 +115,7 @@ pub(crate) fn complete_attribute_path(
});
acc.add_nameref_keywords_with_colon(ctx);
}
Qualified::Infer | Qualified::With { .. } => {}
Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
}
let attributes = annotated_item_kind.and_then(|kind| {

View file

@ -97,7 +97,7 @@ pub(crate) fn complete_derive_path(
});
acc.add_nameref_keywords_with_colon(ctx);
}
Qualified::Infer | Qualified::With { .. } => {}
Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
}
}

View file

@ -11,7 +11,14 @@ pub(crate) fn complete_expr_path(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
&ExprCtx {
expr_ctx: &ExprCtx,
) {
let _p = profile::span("complete_expr_path");
if !ctx.qualifier_ctx.none() {
return;
}
let &ExprCtx {
in_block_expr,
in_loop_body,
after_if_expr,
@ -23,12 +30,7 @@ pub(crate) fn complete_expr_path(
ref impl_,
in_match_guard,
..
}: &ExprCtx,
) {
let _p = profile::span("complete_expr_path");
if !ctx.qualifier_ctx.none() {
return;
}
} = expr_ctx;
let wants_mut_token =
ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false);
@ -46,11 +48,32 @@ pub(crate) fn complete_expr_path(
};
match qualified {
Qualified::Infer => ctx
Qualified::TypeAnchor { ty: None, trait_: None } => ctx
.traits_in_scope()
.iter()
.flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
.for_each(|item| add_assoc_item(acc, item)),
Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
}
Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
cov_mark::hit!(completes_variant_through_alias);
acc.add_enum_variants(ctx, path_ctx, e);
}
ctx.iterate_path_candidates(&ty, |item| {
add_assoc_item(acc, item);
});
// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
if let hir::AssocItem::TypeAlias(ty) = item {
acc.add_type_alias(ctx, ty)
}
None::<()>
});
}
Qualified::With { resolution: None, .. } => {}
Qualified::With { resolution: Some(resolution), .. } => {
// Add associated types on type parameters and `Self`.
@ -179,10 +202,21 @@ pub(crate) fn complete_expr_path(
}
}
}
ctx.process_all_names(&mut |name, def| {
if scope_def_applicable(def) {
acc.add_path_resolution(ctx, path_ctx, name, def);
ctx.process_all_names(&mut |name, def| match def {
ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
let assocs = t.items_with_supertraits(ctx.db);
match &*assocs {
// traits with no assoc items are unusable as expressions since
// there is no associated item path that can be constructed with them
[] => (),
// FIXME: Render the assoc item with the trait qualified
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def),
// FIXME: Append `::` to the thing here, since a trait on its own won't work
[..] => acc.add_path_resolution(ctx, path_ctx, name, def),
}
}
_ if scope_def_applicable(def) => acc.add_path_resolution(ctx, path_ctx, name, def),
_ => (),
});
if is_func_update.is_none() {

View file

@ -66,7 +66,7 @@ pub(crate) fn complete_item_list(
});
acc.add_nameref_keywords_with_colon(ctx);
}
Qualified::Infer | Qualified::No | Qualified::With { .. } => {}
Qualified::TypeAnchor { .. } | Qualified::No | Qualified::With { .. } => {}
}
}

View file

@ -74,7 +74,7 @@ pub(crate) fn complete_pattern(
hir::ModuleDef::Variant(variant)
if refutable || single_variant_enum(variant.parent_enum(ctx.db)) =>
{
acc.add_variant_pat(ctx, pattern_ctx, variant, Some(name.clone()));
acc.add_variant_pat(ctx, pattern_ctx, None, variant, Some(name.clone()));
true
}
hir::ModuleDef::Adt(hir::Adt::Enum(e)) => refutable || single_variant_enum(e),
@ -180,6 +180,6 @@ pub(crate) fn complete_pattern_path(
acc.add_nameref_keywords_with_colon(ctx);
}
Qualified::Infer | Qualified::With { .. } => {}
Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
}
}

View file

@ -49,11 +49,27 @@ pub(crate) fn complete_type_path(
};
match qualified {
Qualified::Infer => ctx
Qualified::TypeAnchor { ty: None, trait_: None } => ctx
.traits_in_scope()
.iter()
.flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
.for_each(|item| add_assoc_item(acc, item)),
Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
}
Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
ctx.iterate_path_candidates(&ty, |item| {
add_assoc_item(acc, item);
});
// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
if let hir::AssocItem::TypeAlias(ty) = item {
acc.add_type_alias(ctx, ty)
}
None::<()>
});
}
Qualified::With { resolution: None, .. } => {}
Qualified::With { resolution: Some(resolution), .. } => {
// Add associated types on type parameters and `Self`.

View file

@ -115,6 +115,6 @@ pub(crate) fn complete_use_path(
});
acc.add_nameref_keywords_with_colon(ctx);
}
Qualified::Infer | Qualified::With { resolution: None, .. } => {}
Qualified::TypeAnchor { .. } | Qualified::With { resolution: None, .. } => {}
}
}

View file

@ -29,7 +29,7 @@ pub(crate) fn complete_vis_path(
acc.add_super_keyword(ctx, *super_chain_len);
}
Qualified::Absolute | Qualified::Infer | Qualified::With { .. } => {}
Qualified::Absolute | Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
Qualified::No => {
if !has_in_token {
cov_mark::hit!(kw_completion_in);

View file

@ -193,7 +193,10 @@ pub(super) enum Qualified {
super_chain_len: Option<usize>,
},
/// <_>::
Infer,
TypeAnchor {
ty: Option<hir::Type>,
trait_: Option<hir::Trait>,
},
/// Whether the path is an absolute path
Absolute,
}

View file

@ -162,11 +162,52 @@ impl<'a> CompletionContext<'a> {
}
/// Calculate the expected type and name of the cursor position.
fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
fn expected_type_and_name(
&self,
name_like: &ast::NameLike,
) -> (Option<Type>, Option<NameOrNameRef>) {
let mut node = match self.token.parent() {
Some(it) => it,
None => return (None, None),
};
let strip_refs = |mut ty: Type| match name_like {
ast::NameLike::NameRef(n) => {
let p = match n.syntax().parent() {
Some(it) => it,
None => return ty,
};
let top_syn = match_ast! {
match p {
ast::FieldExpr(e) => e
.syntax()
.ancestors()
.map_while(ast::FieldExpr::cast)
.last()
.map(|it| it.syntax().clone()),
ast::PathSegment(e) => e
.syntax()
.ancestors()
.skip(1)
.take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
.find_map(ast::PathExpr::cast)
.map(|it| it.syntax().clone()),
_ => None
}
};
let top_syn = match top_syn {
Some(it) => it,
None => return ty,
};
for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) {
cov_mark::hit!(expected_type_fn_param_ref);
ty = ty.strip_reference();
}
ty
}
_ => ty,
};
loop {
break match_ast! {
match node {
@ -199,13 +240,9 @@ impl<'a> CompletionContext<'a> {
self.token.clone(),
).map(|ap| {
let name = ap.ident().map(NameOrNameRef::Name);
let ty = if has_ref(&self.token) {
cov_mark::hit!(expected_type_fn_param_ref);
ap.ty.remove_ref()
} else {
Some(ap.ty)
};
(ty, name)
let ty = strip_refs(ap.ty);
(Some(ty), name)
})
.unwrap_or((None, None))
},
@ -330,8 +367,6 @@ impl<'a> CompletionContext<'a> {
return None;
}
(self.expected_type, self.expected_name) = self.expected_type_and_name();
// Overwrite the path kind for derives
if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
if let Some(ast::NameLike::NameRef(name_ref)) =
@ -389,6 +424,7 @@ impl<'a> CompletionContext<'a> {
return Some(analysis);
}
};
(self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like);
let analysis = match name_like {
ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime(
Self::classify_lifetime(&self.sema, original_file, lifetime)?,
@ -556,7 +592,7 @@ impl<'a> CompletionContext<'a> {
has_call_parens: false,
has_macro_bang: false,
qualified: Qualified::No,
parent: path.parent_path(),
parent: None,
path: path.clone(),
kind: PathKind::Item { kind: ItemListKind::SourceFile },
has_type_args: false,
@ -791,92 +827,125 @@ impl<'a> CompletionContext<'a> {
PathKind::Type { location: location.unwrap_or(TypeLocation::Other) }
};
let mut kind_macro_call = |it: ast::MacroCall| {
path_ctx.has_macro_bang = it.excl_token().is_some();
let parent = it.syntax().parent()?;
// Any path in an item list will be treated as a macro call by the parser
let kind = match_ast! {
match parent {
ast::MacroExpr(expr) => make_path_kind_expr(expr.into()),
ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())},
ast::MacroType(ty) => make_path_kind_type(ty.into()),
ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module },
ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() {
Some(it) => match_ast! {
match it {
ast::Trait(_) => ItemListKind::Trait,
ast::Impl(it) => if it.trait_().is_some() {
ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
} else {
ItemListKind::Impl
},
_ => return None
}
},
None => return None,
} },
ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
_ => return None,
}
};
Some(kind)
};
let make_path_kind_attr = |meta: ast::Meta| {
let attr = meta.parent_attr()?;
let kind = attr.kind();
let attached = attr.syntax().parent()?;
let is_trailing_outer_attr = kind != AttrKind::Inner
&& non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next)
.is_none();
let annotated_item_kind =
if is_trailing_outer_attr { None } else { Some(attached.kind()) };
Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } })
};
// Infer the path kind
let parent = path.syntax().parent()?;
let kind = match_ast! {
match parent {
ast::PathType(it) => make_path_kind_type(it.into()),
ast::PathExpr(it) => {
if let Some(p) = it.syntax().parent() {
if ast::ExprStmt::can_cast(p.kind()) {
if let Some(kind) = inbetween_body_and_decl_check(p) {
return Some(make_res(NameRefKind::Keyword(kind)));
}
match parent {
ast::PathType(it) => make_path_kind_type(it.into()),
ast::PathExpr(it) => {
if let Some(p) = it.syntax().parent() {
if ast::ExprStmt::can_cast(p.kind()) {
if let Some(kind) = inbetween_body_and_decl_check(p) {
return Some(make_res(NameRefKind::Keyword(kind)));
}
}
}
path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
make_path_kind_expr(it.into())
},
ast::TupleStructPat(it) => {
path_ctx.has_call_parens = true;
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
},
ast::RecordPat(it) => {
path_ctx.has_call_parens = true;
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
},
ast::PathPat(it) => {
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
},
ast::MacroCall(it) => {
// A macro call in this position is usually a result of parsing recovery, so check that
if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
return Some(make_res(NameRefKind::Keyword(kind)));
make_path_kind_expr(it.into())
},
ast::TupleStructPat(it) => {
path_ctx.has_call_parens = true;
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
},
ast::RecordPat(it) => {
path_ctx.has_call_parens = true;
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
},
ast::PathPat(it) => {
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
},
ast::MacroCall(it) => {
// A macro call in this position is usually a result of parsing recovery, so check that
if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
return Some(make_res(NameRefKind::Keyword(kind)));
}
kind_macro_call(it)?
},
ast::Meta(meta) => make_path_kind_attr(meta)?,
ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
ast::UseTree(_) => PathKind::Use,
// completing inside a qualifier
ast::Path(parent) => {
path_ctx.parent = Some(parent.clone());
let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?;
match_ast! {
match parent {
ast::PathType(it) => make_path_kind_type(it.into()),
ast::PathExpr(it) => {
path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
make_path_kind_expr(it.into())
},
ast::TupleStructPat(it) => {
path_ctx.has_call_parens = true;
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
},
ast::RecordPat(it) => {
path_ctx.has_call_parens = true;
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
},
ast::PathPat(it) => {
PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
},
ast::MacroCall(it) => {
kind_macro_call(it)?
},
ast::Meta(meta) => make_path_kind_attr(meta)?,
ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
ast::UseTree(_) => PathKind::Use,
ast::RecordExpr(it) => make_path_kind_expr(it.into()),
_ => return None,
}
path_ctx.has_macro_bang = it.excl_token().is_some();
let parent = it.syntax().parent()?;
// Any path in an item list will be treated as a macro call by the parser
match_ast! {
match parent {
ast::MacroExpr(expr) => make_path_kind_expr(expr.into()),
ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())},
ast::MacroType(ty) => make_path_kind_type(ty.into()),
ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module },
ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() {
Some(it) => match_ast! {
match it {
ast::Trait(_) => ItemListKind::Trait,
ast::Impl(it) => if it.trait_().is_some() {
ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
} else {
ItemListKind::Impl
},
_ => return None
}
},
None => return None,
} },
ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
_ => return None,
}
}
},
ast::Meta(meta) => {
let attr = meta.parent_attr()?;
let kind = attr.kind();
let attached = attr.syntax().parent()?;
let is_trailing_outer_attr = kind != AttrKind::Inner
&& non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none();
let annotated_item_kind = if is_trailing_outer_attr {
None
} else {
Some(attached.kind())
};
PathKind::Attr {
attr_ctx: AttrCtx {
kind,
annotated_item_kind,
}
}
},
ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
ast::UseTree(_) => PathKind::Use,
_ => return None,
}
},
ast::RecordExpr(it) => make_path_kind_expr(it.into()),
_ => return None,
}
};
@ -884,49 +953,53 @@ impl<'a> CompletionContext<'a> {
path_ctx.has_type_args = segment.generic_arg_list().is_some();
// calculate the qualifier context
if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
path_ctx.use_tree_parent = use_tree_parent;
if !use_tree_parent && segment.coloncolon_token().is_some() {
path_ctx.qualified = Qualified::Absolute;
} else {
let path = path
let qualifier = qualifier
.segment()
.and_then(|it| find_node_in_file(original_file, &it))
.map(|it| it.parent_path());
if let Some(path) = path {
// `<_>::$0`
let is_infer_qualifier = path.qualifier().is_none()
&& matches!(
path.segment().and_then(|it| it.kind()),
Some(ast::PathSegmentKind::Type {
type_ref: Some(ast::Type::InferType(_)),
trait_ref: None,
})
);
if let Some(qualifier) = qualifier {
let type_anchor = match qualifier.segment().and_then(|it| it.kind()) {
Some(ast::PathSegmentKind::Type {
type_ref: Some(type_ref),
trait_ref,
}) if qualifier.qualifier().is_none() => Some((type_ref, trait_ref)),
_ => None,
};
path_ctx.qualified = if is_infer_qualifier {
Qualified::Infer
path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor {
let ty = match ty {
ast::Type::InferType(_) => None,
ty => sema.resolve_type(&ty),
};
let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?));
Qualified::TypeAnchor { ty, trait_ }
} else {
let res = sema.resolve_path(&path);
let res = sema.resolve_path(&qualifier);
// For understanding how and why super_chain_len is calculated the way it
// is check the documentation at it's definition
let mut segment_count = 0;
let super_count = iter::successors(Some(path.clone()), |p| p.qualifier())
.take_while(|p| {
p.segment()
.and_then(|s| {
segment_count += 1;
s.super_token()
})
.is_some()
})
.count();
let super_count =
iter::successors(Some(qualifier.clone()), |p| p.qualifier())
.take_while(|p| {
p.segment()
.and_then(|s| {
segment_count += 1;
s.super_token()
})
.is_some()
})
.count();
let super_chain_len =
if segment_count > super_count { None } else { Some(super_count) };
Qualified::With { path, resolution: res, super_chain_len }
Qualified::With { path: qualifier, resolution: res, super_chain_len }
}
};
}
@ -1141,19 +1214,6 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
Some((use_tree.path()?, true))
}
fn has_ref(token: &SyntaxToken) -> bool {
let mut token = token.clone();
for skip in [SyntaxKind::IDENT, SyntaxKind::WHITESPACE, T![mut]] {
if token.kind() == skip {
token = match token.prev_token() {
Some(it) => it,
None => return false,
}
}
}
token.kind() == T![&]
}
pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
// oh my ...
(|| {

View file

@ -391,3 +391,23 @@ fn foo($0: Foo) {}
expect![[r#"ty: ?, name: ?"#]],
);
}
#[test]
fn expected_type_ref_prefix_on_field() {
check_expected_type_and_name(
r#"
fn foo(_: &mut i32) {}
struct S {
field: i32,
}
fn main() {
let s = S {
field: 100,
};
foo(&mut s.f$0);
}
"#,
expect!["ty: i32, name: ?"],
);
}

View file

@ -1271,8 +1271,8 @@ fn main() {
st S []
st &mut S [type]
st S []
fn main() []
fn foo() []
fn main() []
"#]],
);
check_relevance(
@ -1288,8 +1288,8 @@ fn main() {
lc s [type+name+local]
st S [type]
st S []
fn main() []
fn foo() []
fn main() []
"#]],
);
check_relevance(
@ -1305,8 +1305,8 @@ fn main() {
lc ssss [type+local]
st S [type]
st S []
fn main() []
fn foo() []
fn main() []
"#]],
);
}
@ -1342,12 +1342,11 @@ fn main() {
lc &t [type+local]
st S []
st &S [type]
st T []
st S []
fn main() []
st T []
fn foo() []
fn main() []
md core []
tt Sized []
"#]],
)
}
@ -1389,12 +1388,11 @@ fn main() {
lc &mut t [type+local]
st S []
st &mut S [type]
st T []
st S []
fn main() []
st T []
fn foo() []
fn main() []
md core []
tt Sized []
"#]],
)
}
@ -1485,14 +1483,13 @@ fn main() {
expect![[r#"
st S []
st &S [type]
st T []
st S []
fn main() []
st T []
fn bar() []
fn &bar() [type]
fn foo() []
fn main() []
md core []
tt Sized []
"#]],
)
}
@ -1636,8 +1633,8 @@ fn foo() {
ev Foo::B [type_could_unify]
fn foo() []
en Foo []
fn baz() []
fn bar() []
fn baz() []
"#]],
);
}
@ -1727,9 +1724,9 @@ fn f() {
}
"#,
expect![[r#"
md std []
st Buffer []
fn f() []
md std []
tt BufRead (use std::io::BufRead) [requires_import]
st BufReader (use std::io::BufReader) [requires_import]
st BufWriter (use std::io::BufWriter) [requires_import]

View file

@ -85,7 +85,9 @@ fn render(
item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
}
FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
item.ref_match(ref_match, receiver.syntax().text_range().start());
if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
item.ref_match(ref_match, original_expr.syntax().text_range().start());
}
}
_ => (),
}

View file

@ -6,7 +6,7 @@ use itertools::Itertools;
use syntax::SmolStr;
use crate::{
context::{ParamContext, ParamKind, PatternContext},
context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext},
render::{
variant::{format_literal_label, visible_fields},
RenderContext,
@ -42,6 +42,7 @@ pub(crate) fn render_struct_pat(
pub(crate) fn render_variant_pat(
ctx: RenderContext<'_>,
pattern_ctx: &PatternContext,
path_ctx: Option<&PathCompletionCtx>,
variant: hir::Variant,
local_name: Option<Name>,
path: Option<&hir::ModPath>,
@ -58,9 +59,23 @@ pub(crate) fn render_variant_pat(
(name.to_smol_str(), name.escaped().to_smol_str())
}
};
let kind = variant.kind(ctx.db());
let label = format_literal_label(name.as_str(), kind);
let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?;
let (label, pat) = match path_ctx {
Some(PathCompletionCtx { has_call_parens: true, .. }) => (name, escaped_name.to_string()),
_ => {
let kind = variant.kind(ctx.db());
let label = format_literal_label(name.as_str(), kind);
let pat = render_pat(
&ctx,
pattern_ctx,
&escaped_name,
kind,
&visible_fields,
fields_omitted,
)?;
(label, pat)
}
};
Some(build_completion(ctx, label, pat, variant))
}

View file

@ -23,8 +23,6 @@ mod type_pos;
mod use_tree;
mod visibility;
use std::mem;
use hir::{db::DefDatabase, PrefixKind, Semantics};
use ide_db::{
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
@ -107,12 +105,9 @@ fn completion_list_with_config(
) -> String {
// filter out all but one builtintype completion for smaller test outputs
let items = get_all_items(config, ra_fixture, trigger_character);
let mut bt_seen = false;
let items = items
.into_iter()
.filter(|it| {
it.kind() != CompletionItemKind::BuiltinType || !mem::replace(&mut bt_seen, true)
})
.filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32")
.filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
.filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
.sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned)))

View file

@ -44,7 +44,6 @@ fn baz() {
st Record
st Tuple
st Unit
tt Trait
un Union
ev TupleV() TupleV(u32)
bt u32
@ -137,7 +136,6 @@ impl Unit {
st Record
st Tuple
st Unit
tt Trait
tp TypeParam
un Union
ev TupleV() TupleV(u32)
@ -653,3 +651,22 @@ fn main() {
"]],
);
}
#[test]
fn complete_record_expr_path() {
check(
r#"
struct Zulu;
impl Zulu {
fn test() -> Self { }
}
fn boi(val: Zulu) { }
fn main() {
boi(Zulu:: $0 {});
}
"#,
expect![[r#"
fn test() fn() -> Zulu
"#]],
);
}

View file

@ -443,7 +443,7 @@ fn foo() {
}
"#,
expect![[r#"
bn TupleVariant() TupleVariant($1)$0
bn TupleVariant TupleVariant
"#]],
);
check_empty(
@ -458,7 +458,7 @@ fn foo() {
}
"#,
expect![[r#"
bn RecordVariant {} RecordVariant { field$1 }$0
bn RecordVariant RecordVariant
"#]],
);
}

View file

@ -167,7 +167,6 @@ fn main() {
st Foo
st Foo {} Foo { foo1: u32, foo2: u32 }
tt Default
tt Sized
bt u32
kw crate::
kw self::

View file

@ -674,7 +674,60 @@ fn bar() -> Bar {
expect![[r#"
fn foo() (as Foo) fn() -> Self
"#]],
)
);
}
#[test]
fn type_anchor_type() {
check(
r#"
trait Foo {
fn foo() -> Self;
}
struct Bar;
impl Bar {
fn bar() {}
}
impl Foo for Bar {
fn foo() -> {
Bar
}
}
fn bar() -> Bar {
<Bar>::$0
}
"#,
expect![[r#"
fn bar() fn()
fn foo() (as Foo) fn() -> Self
"#]],
);
}
#[test]
fn type_anchor_type_trait() {
check(
r#"
trait Foo {
fn foo() -> Self;
}
struct Bar;
impl Bar {
fn bar() {}
}
impl Foo for Bar {
fn foo() -> {
Bar
}
}
fn bar() -> Bar {
<Bar as Foo>::$0
}
"#,
expect![[r#"
fn foo() (as Foo) fn() -> Self
"#]],
);
}
#[test]