Repository: racer-rust/racer (language: rust)

Issue:
Add support for unions
Rust 1.19 added support for `union` types in rust-lang/rust#42068 ([blog post](https://blog.rust-lang.org/2017/07/20/Rust-1.19.html)). Racer should add support for them as well.


Discussion / hints:

Fix patch:
diff --git a/src/racer/ast.rs b/src/racer/ast.rs
index 7a5c0aff..d40b1bc4 100644
--- a/src/racer/ast.rs
+++ b/src/racer/ast.rs
@@ -409,7 +409,6 @@ fn resolve_ast_path(
 ) -> Option<Match> {
     let scope = Scope::new(filepath.to_owned(), pos);
     let path = RacerPath::from_ast(path, &scope);
-    debug!("resolve_ast_path {:?} {:?}", path, scope);
     nameres::resolve_path_with_primitive(
         &path,
         filepath,
@@ -534,7 +533,9 @@ impl<'c, 's, 'ast> visit::Visitor<'ast> for ExprTypeVisitor<'c, 's> {
                                 )
                             }
                             // if we find tuple struct / enum variant, try to resolve its generics name
-                            MatchType::Struct(ref mut gen) | MatchType::Enum(ref mut gen) => {
+                            MatchType::Struct(ref mut gen)
+                            | MatchType::Enum(ref mut gen)
+                            | MatchType::Union(ref mut gen) => {
                                 if gen.is_empty() {
                                     return Some(Ty::Match(m));
                                 }
diff --git a/src/racer/core.rs b/src/racer/core.rs
index 091a8635..162cc3ac 100644
--- a/src/racer/core.rs
+++ b/src/racer/core.rs
@@ -41,6 +41,7 @@ pub enum MatchType {
     For(BytePos),
     StructField,
     Enum(Box<GenericsArgs>),
+    Union(Box<GenericsArgs>),
     /// EnumVariant needs to have Enum type to complete methods
     EnumVariant(Option<Box<Match>>),
     UseAlias(Box<Match>),
@@ -81,6 +82,7 @@ impl fmt::Display for MatchType {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             MatchType::Struct(_) => write!(f, "Struct"),
+            MatchType::Union(_) => write!(f, "Union"),
             MatchType::Method(_) => write!(f, "Method"),
             MatchType::IfLet(_) => write!(f, "IfLet"),
             MatchType::Let(_) => write!(f, "Let"),
diff --git a/src/racer/matchers.rs b/src/racer/matchers.rs
index 12f5c961..b803e779 100644
--- a/src/racer/matchers.rs
+++ b/src/racer/matchers.rs
@@ -444,6 +444,34 @@ pub fn match_struct(
     })
 }
 
+pub fn match_union(
+    msrc: Src<'_>,
+    context: &MatchCxt<'_, '_>,
+    session: &Session<'_>,
+) -> Option<Match> {
+    let blob = &msrc[context.range.to_range()];
+    let (start, s) = context.get_key_ident(blob, "union", &[])?;
+
+    debug!("found a union |{}|", s);
+    let generics =
+        find_generics_end(&blob[start.0..]).map_or_else(Default::default, |generics_end| {
+            let header = format!("union {}();", &blob[start.0..=(start + generics_end).0]);
+            ast::parse_generics(header, context.filepath)
+        });
+    let start = context.range.start + start;
+    let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
+    Some(Match {
+        matchstr: s,
+        filepath: context.filepath.to_path_buf(),
+        point: start,
+        coords: None,
+        local: context.is_local,
+        mtype: MatchType::Union(Box::new(generics)),
+        contextstr: get_context(blob, "{"),
+        docs: find_doc(&doc_src, start),
+    })
+}
+
 pub fn match_type(
     msrc: Src<'_>,
     context: &MatchCxt<'_, '_>,
diff --git a/src/racer/nameres.rs b/src/racer/nameres.rs
index 52e72c2e..409b70a4 100644
--- a/src/racer/nameres.rs
+++ b/src/racer/nameres.rs
@@ -33,7 +33,7 @@ pub(crate) fn search_struct_fields(
     session: &Session<'_>,
 ) -> Vec<Match> {
     match structmatch.mtype {
-        MatchType::Struct(_) | MatchType::EnumVariant(_) => {}
+        MatchType::Struct(_) | MatchType::EnumVariant(_) | MatchType::Union(_) => {}
         _ => return Vec::new(),
     }
     let src = session.load_source_file(&structmatch.filepath);
@@ -1254,6 +1254,7 @@ fn run_matchers_on_blob(
     run_matcher!(Namespace::Mod, matchers::match_mod);
     run_matcher!(Namespace::Enum, matchers::match_enum);
     run_matcher!(Namespace::Struct, matchers::match_struct);
+    run_matcher!(Namespace::Union, matchers::match_union);
     run_matcher!(Namespace::Trait, matchers::match_trait);
     run_matcher!(Namespace::TypeDef, matchers::match_type);
     run_matcher!(Namespace::Func, matchers::match_fn);
@@ -1421,7 +1422,6 @@ pub fn resolve_path_with_primitive(
             break;
         }
     }
-
     out
 }
 
@@ -1813,7 +1813,7 @@ pub fn resolve_path(
         )
         .into_iter()
         .nth(0);
-        debug!(
+        println!(
             "[resolve_path] context: {:?}, last_seg: {:?}",
             context, last_seg
         );
@@ -1906,7 +1906,7 @@ fn resolve_following_path(
                 import_info,
             )
         }
-        MatchType::Enum(_) | MatchType::Struct(_) => get_impled_items(
+        MatchType::Enum(_) | MatchType::Struct(_) | MatchType::Union(_) => get_impled_items(
             following_seg,
             search_type,
             &followed_match,
@@ -2196,10 +2196,10 @@ pub fn search_for_fields_and_methods(
     let m = context;
     let mut out = Vec::new();
     match m.mtype {
-        MatchType::Struct(_) => {
+        MatchType::Struct(_) | MatchType::Union(_) => {
             debug!(
-                "got a struct, looking for fields and impl methods!! {}",
-                m.matchstr
+                "got a struct or union, looking for fields and impl methods!! {}",
+                m.matchstr,
             );
             if !only_methods {
                 for m in search_struct_fields(searchstr, &m, search_type, session) {
diff --git a/src/racer/typeinf.rs b/src/racer/typeinf.rs
index bbcdc5c4..5b674566 100644
--- a/src/racer/typeinf.rs
+++ b/src/racer/typeinf.rs
@@ -444,6 +444,7 @@ pub fn get_type_of_match(m: Match, msrc: Src<'_>, session: &Session<'_>) -> Opti
         core::MatchType::FnArg(_) => get_type_of_fnarg(m, session),
         core::MatchType::MatchArm => get_type_from_match_arm(&m, msrc, session),
         core::MatchType::Struct(_)
+        | core::MatchType::Union(_)
         | core::MatchType::Enum(_)
         | core::MatchType::Function
         | core::MatchType::Method(_)


Test patch:
diff --git a/tests/union.rs b/tests/union.rs
new file mode 100644
index 00000000..85cae9c8
--- /dev/null
+++ b/tests/union.rs
@@ -0,0 +1,63 @@
+use racer_testutils::*;
+
+#[test]
+fn completes_union() {
+    let src = r#"
+    #[repr(C)]
+    union MyUnion {
+        f1: u32,
+        f2: f32,
+    }
+    let u: MyU~
+"#;
+    let got = get_only_completion(src, None);
+    assert_eq!(got.matchstr, "MyUnion");
+}
+
+#[test]
+fn completes_maybe_uninit() {
+    let src = r#"
+    let u: std::mem::Mayb~
+"#;
+    let got = get_only_completion(src, None);
+    assert_eq!(got.matchstr, "MaybeUninit");
+}
+
+#[test]
+fn completes_union_member() {
+    let src = r#"
+    #[repr(C)]
+    union MyUnion {
+        uint_member: u32,
+        float_member: f32,
+    }
+    impl MyUnion {
+        fn new() -> Self { Self { uint_member: 10 } }
+    }
+    let uni = unsafe { MyUnion::new().uint~ };
+"#;
+    let got = get_only_completion(src, None);
+    assert_eq!(got.matchstr, "uint_member");
+}
+
+#[test]
+fn completes_union_method() {
+    let src = r#"
+    #[repr(C)]
+    union MyUnion {
+        uint_member: u32,
+        float_member: f32,
+    }
+    impl MyUnion {
+        fn new() -> Self { Self { uint_member: 10 } }
+        fn double(self) -> Self {
+            Self {
+                uint_member: unsafe { self.uint_member * 2 }
+            }
+        }
+    }
+    let uni = unsafe { MyUnion::new().dou~ };
+"#;
+    let got = get_only_completion(src, None);
+    assert_eq!(got.matchstr, "double");
+}