mirror of
https://github.com/rust-lang/rust.git
synced 2025-01-26 22:53:28 +00:00
Add completion for struct literal in which all fields are visible.
Fix ide_completion tests. Move 'complete_record_literal' call to the main completion function. Fix a rendering bug when snippet not available. Checks if an expression is expected before adding completion for struct literal. Move 'completion struct literal with private field' test to 'expressions.rs' test file. Update 'expect' tests with new check in 'complete record literal'.
This commit is contained in:
parent
8a8431133e
commit
9beefef8f9
@ -29,6 +29,7 @@ use crate::{
|
||||
macro_::render_macro,
|
||||
pattern::{render_struct_pat, render_variant_pat},
|
||||
render_field, render_resolution, render_tuple_field,
|
||||
struct_literal::render_struct_literal,
|
||||
type_alias::{render_type_alias, render_type_alias_with_eq},
|
||||
RenderContext,
|
||||
},
|
||||
@ -168,6 +169,16 @@ impl Completions {
|
||||
self.add(item);
|
||||
}
|
||||
|
||||
pub(crate) fn add_struct_literal(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
strukt: hir::Struct,
|
||||
local_name: Option<hir::Name>,
|
||||
) {
|
||||
let item = render_struct_literal(RenderContext::new(ctx), strukt, local_name);
|
||||
self.add_opt(item);
|
||||
}
|
||||
|
||||
pub(crate) fn add_tuple_field(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
|
@ -45,10 +45,81 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(crate) fn complete_record_literal(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
) -> Option<()> {
|
||||
if !ctx.expects_expression() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let hir::Adt::Struct(strukt) = ctx.expected_type.as_ref()?.as_adt()? {
|
||||
acc.add_struct_literal(ctx, strukt, None);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::check_edit;
|
||||
|
||||
#[test]
|
||||
fn literal_struct_completion_edit() {
|
||||
check_edit(
|
||||
"FooDesc {…}",
|
||||
r#"
|
||||
struct FooDesc { pub bar: bool }
|
||||
|
||||
fn create_foo(foo_desc: &FooDesc) -> () { () }
|
||||
|
||||
fn baz() {
|
||||
let foo = create_foo(&$0);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct FooDesc { pub bar: bool }
|
||||
|
||||
fn create_foo(foo_desc: &FooDesc) -> () { () }
|
||||
|
||||
fn baz() {
|
||||
let foo = create_foo(&FooDesc { bar: ${1:()} }$0);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_struct_complexion_module() {
|
||||
check_edit(
|
||||
"FooDesc {…}",
|
||||
r#"
|
||||
mod _69latrick {
|
||||
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
|
||||
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
|
||||
}
|
||||
|
||||
fn baz() {
|
||||
use _69latrick::*;
|
||||
|
||||
let foo = create_foo(&$0);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
mod _69latrick {
|
||||
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
|
||||
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
|
||||
}
|
||||
|
||||
fn baz() {
|
||||
use _69latrick::*;
|
||||
|
||||
let foo = create_foo(&FooDesc { six: ${1:()}, neuf: ${2:()}, bar: ${3:()} }$0);
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_completion_edit() {
|
||||
check_edit(
|
||||
|
@ -156,6 +156,7 @@ pub fn completions(
|
||||
completions::unqualified_path::complete_unqualified_path(&mut acc, &ctx);
|
||||
completions::dot::complete_dot(&mut acc, &ctx);
|
||||
completions::record::complete_record(&mut acc, &ctx);
|
||||
completions::record::complete_record_literal(&mut acc, &ctx);
|
||||
completions::pattern::complete_pattern(&mut acc, &ctx);
|
||||
completions::postfix::complete_postfix(&mut acc, &ctx);
|
||||
completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
|
||||
|
@ -7,6 +7,7 @@ pub(crate) mod enum_variant;
|
||||
pub(crate) mod const_;
|
||||
pub(crate) mod pattern;
|
||||
pub(crate) mod type_alias;
|
||||
pub(crate) mod struct_literal;
|
||||
|
||||
mod builder_ext;
|
||||
|
||||
|
121
crates/ide_completion/src/render/struct_literal.rs
Normal file
121
crates/ide_completion/src/render/struct_literal.rs
Normal file
@ -0,0 +1,121 @@
|
||||
//! Renderer for `struct` literal.
|
||||
|
||||
use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
|
||||
use ide_db::helpers::SnippetCap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
|
||||
|
||||
pub(crate) fn render_struct_literal(
|
||||
ctx: RenderContext<'_>,
|
||||
strukt: hir::Struct,
|
||||
local_name: Option<Name>,
|
||||
) -> Option<CompletionItem> {
|
||||
let _p = profile::span("render_struct_literal");
|
||||
|
||||
let fields = strukt.fields(ctx.db());
|
||||
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
|
||||
|
||||
if fields_omitted {
|
||||
// If some fields are private you can't make `struct` literal.
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
|
||||
let literal = render_literal(&ctx, &name, strukt.kind(ctx.db()), &visible_fields)?;
|
||||
|
||||
Some(build_completion(ctx, name, literal, strukt))
|
||||
}
|
||||
|
||||
fn build_completion(
|
||||
ctx: RenderContext<'_>,
|
||||
name: String,
|
||||
literal: String,
|
||||
def: impl HasAttrs + Copy,
|
||||
) -> CompletionItem {
|
||||
let mut item = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name + " {…}");
|
||||
item.kind(CompletionItemKind::Snippet)
|
||||
.set_documentation(ctx.docs(def))
|
||||
.set_deprecated(ctx.is_deprecated(def))
|
||||
.detail(&literal);
|
||||
if let Some(snippet_cap) = ctx.snippet_cap() {
|
||||
item.insert_snippet(snippet_cap, literal);
|
||||
} else {
|
||||
item.insert_text(literal);
|
||||
};
|
||||
item.build()
|
||||
}
|
||||
|
||||
fn render_literal(
|
||||
ctx: &RenderContext<'_>,
|
||||
name: &str,
|
||||
kind: StructKind,
|
||||
fields: &[hir::Field],
|
||||
) -> Option<String> {
|
||||
let mut literal = match kind {
|
||||
StructKind::Tuple if ctx.snippet_cap().is_some() => render_tuple_as_literal(fields, name),
|
||||
StructKind::Record => render_record_as_literal(ctx.db(), ctx.snippet_cap(), fields, name),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if ctx.completion.is_param {
|
||||
literal.push(':');
|
||||
literal.push(' ');
|
||||
literal.push_str(name);
|
||||
}
|
||||
if ctx.snippet_cap().is_some() {
|
||||
literal.push_str("$0");
|
||||
}
|
||||
Some(literal)
|
||||
}
|
||||
|
||||
fn render_record_as_literal(
|
||||
db: &dyn HirDatabase,
|
||||
snippet_cap: Option<SnippetCap>,
|
||||
fields: &[hir::Field],
|
||||
name: &str,
|
||||
) -> String {
|
||||
let fields = fields.iter();
|
||||
if snippet_cap.is_some() {
|
||||
format!(
|
||||
"{name} {{ {} }}",
|
||||
fields
|
||||
.enumerate()
|
||||
.map(|(idx, field)| format!("{}: ${{{}:()}}", field.name(db), idx + 1))
|
||||
.format(", "),
|
||||
name = name
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{name} {{ {} }}",
|
||||
fields.map(|field| format!("{}: ()", field.name(db))).format(", "),
|
||||
name = name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_tuple_as_literal(fields: &[hir::Field], name: &str) -> String {
|
||||
format!(
|
||||
"{name}({})",
|
||||
fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
|
||||
name = name
|
||||
)
|
||||
}
|
||||
|
||||
fn visible_fields(
|
||||
ctx: &RenderContext<'_>,
|
||||
fields: &[hir::Field],
|
||||
item: impl HasAttrs,
|
||||
) -> Option<(Vec<hir::Field>, bool)> {
|
||||
let module = ctx.completion.scope.module()?;
|
||||
let n_fields = fields.len();
|
||||
let fields = fields
|
||||
.iter()
|
||||
.filter(|field| field.is_visible_from(ctx.db(), module))
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let fields_omitted =
|
||||
n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
|
||||
Some((fields, fields_omitted))
|
||||
}
|
@ -13,6 +13,60 @@ fn check_empty(ra_fixture: &str, expect: Expect) {
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_literal_struct_with_a_private_field() {
|
||||
// `FooDesc.bar` is private, the completion should not be triggered.
|
||||
check(
|
||||
r#"
|
||||
mod _69latrick {
|
||||
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, bar: bool }
|
||||
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
|
||||
}
|
||||
|
||||
fn baz() {
|
||||
use _69latrick::*;
|
||||
|
||||
let foo = create_foo(&$0);
|
||||
}
|
||||
"#,
|
||||
// This should not contain `FooDesc {…}`.
|
||||
expect![[r##"
|
||||
kw unsafe
|
||||
kw match
|
||||
kw while
|
||||
kw while let
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw true
|
||||
kw false
|
||||
kw mut
|
||||
kw return
|
||||
kw self
|
||||
kw super
|
||||
kw crate
|
||||
st FooDesc
|
||||
fn create_foo(…) fn(&FooDesc)
|
||||
bt u32
|
||||
tt Trait
|
||||
en Enum
|
||||
st Record
|
||||
st Tuple
|
||||
md module
|
||||
fn baz() fn()
|
||||
st Unit
|
||||
md _69latrick
|
||||
ma makro!(…) #[macro_export] macro_rules! makro
|
||||
fn function() fn()
|
||||
sc STATIC
|
||||
un Union
|
||||
ev TupleV(…) (u32)
|
||||
ct CONST
|
||||
"##]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_various_bindings() {
|
||||
check_empty(
|
||||
|
Loading…
Reference in New Issue
Block a user