Repository: dtolnay/cxx
Pull request #503 (2020-11-25 05:31:24)

Implement Rust [T; N] <-> std::array<T, N>


Patch:
diff --git a/README.md b/README.md
index e4ac67e6..49bd43ce 100644
--- a/README.md
+++ b/README.md
@@ -327,6 +327,7 @@ returns of functions.
 <tr><td><a href="https://docs.rs/cxx/1.0/cxx/struct.CxxString.html">CxxString</a></td><td>std::string</td><td><sup><i>cannot be passed by value</i></sup></td></tr>
 <tr><td>Box&lt;T&gt;</td><td>rust::Box&lt;T&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
 <tr><td><a href="https://docs.rs/cxx/1.0/cxx/struct.UniquePtr.html">UniquePtr&lt;T&gt;</a></td><td>std::unique_ptr&lt;T&gt;</td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
+<tr><td>[T; N]</td><td>std::array&lt;T, N&gt;</td><td><sup><i>cannot hold opaque Rust or C++ type</i></sup></td></tr>
 <tr><td>Vec&lt;T&gt;</td><td>rust::Vec&lt;T&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
 <tr><td><a href="https://docs.rs/cxx/1.0/cxx/struct.CxxVector.html">CxxVector&lt;T&gt;</a></td><td>std::vector&lt;T&gt;</td><td><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr>
 <tr><td>fn(T, U) -&gt; V</td><td>rust::Fn&lt;V(T, U)&gt;</td><td><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr>
diff --git a/book/src/bindings.md b/book/src/bindings.md
index df6a8d7c..c2a525c2 100644
--- a/book/src/bindings.md
+++ b/book/src/bindings.md
@@ -16,6 +16,7 @@ returns of extern functions.
 <tr><td style="padding:3px 6px"><b><a href="binding/cxxstring.md">CxxString</a></b></td><td style="padding:3px 6px">std::string</td><td style="padding:3px 6px"><sup><i>cannot be passed by value</i></sup></td></tr>
 <tr><td style="padding:3px 6px">Box&lt;T&gt;</td><td style="padding:3px 6px"><b><a href="binding/box.md">rust::Box&lt;T&gt;</a></b></td><td style="padding:3px 6px"><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
 <tr><td style="padding:3px 6px"><b><a href="binding/uniqueptr.md">UniquePtr&lt;T&gt;</a></b></td><td style="padding:3px 6px">std::unique_ptr&lt;T&gt;</td><td style="padding:3px 6px"><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
+<tr><td style="padding:3px 6px">[T; N]</td><td style="padding:3px 6px">std::array&lt;T, N&gt;</td><td style="padding:3px 6px"><sup><i>cannot hold opaque Rust or C++ type</i></sup></td></tr>
 <tr><td style="padding:3px 6px">Vec&lt;T&gt;</td><td style="padding:3px 6px"><b><a href="binding/vec.md">rust::Vec&lt;T&gt;</a></b></td><td style="padding:3px 6px"><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
 <tr><td style="padding:3px 6px"><b><a href="binding/cxxvector.md">CxxVector&lt;T&gt;</a></b></td><td style="padding:3px 6px">std::vector&lt;T&gt;</td><td style="padding:3px 6px"><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr>
 <tr><td style="padding:3px 6px">fn(T, U) -&gt; V</td><td style="padding:3px 6px"><b><a href="binding/fn.md">rust::Fn&lt;V(T, U)&gt;</a></b></td><td style="padding:3px 6px"><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr>
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 8b512daf..227df19b 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -172,6 +172,7 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
                 out.include.cstdint = true;
                 out.builtin.rust_slice = true;
             }
+            Type::Array(_) => out.include.array = true,
             Type::Ref(_) | Type::Void(_) => {}
         }
     }
@@ -919,6 +920,11 @@ fn write_type(out: &mut OutFile, ty: &Type) {
             }
             write!(out, ")>");
         }
+        Type::Array(a) => {
+            write!(out, "::std::array<");
+            write_type(out, &a.inner);
+            write!(out, ", {}>", &a.len);
+        }
         Type::Void(_) => unreachable!(),
     }
 }
@@ -957,7 +963,8 @@ fn write_space_after_type(out: &mut OutFile, ty: &Type) {
         | Type::CxxVector(_)
         | Type::RustVec(_)
         | Type::SliceRefU8(_)
-        | Type::Fn(_) => write!(out, " "),
+        | Type::Fn(_)
+        | Type::Array(_) => write!(out, " "),
         Type::Ref(_) => {}
         Type::Void(_) | Type::Slice(_) => unreachable!(),
     }
diff --git a/src/lib.rs b/src/lib.rs
index a0577327..9ee2c4f0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -336,6 +336,7 @@
 //! <tr><td><a href="struct.CxxString.html">CxxString</a></td><td>std::string</td><td><sup><i>cannot be passed by value</i></sup></td></tr>
 //! <tr><td>Box&lt;T&gt;</td><td>rust::Box&lt;T&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
 //! <tr><td><a href="struct.UniquePtr.html">UniquePtr&lt;T&gt;</a></td><td>std::unique_ptr&lt;T&gt;</td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
+//! <tr><td>[T; N]</td><td>std::array&lt;T, N&gt;</td><td><sup><i>cannot hold opaque Rust or C++ type</i></sup></td></tr>
 //! <tr><td>Vec&lt;T&gt;</td><td>rust::Vec&lt;T&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
 //! <tr><td><a href="struct.CxxVector.html">CxxVector&lt;T&gt;</a></td><td>std::vector&lt;T&gt;</td><td><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr>
 //! <tr><td>fn(T, U) -&gt; V</td><td>rust::Fn&lt;V(T, U)&gt;</td><td><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr>
diff --git a/syntax/check.rs b/syntax/check.rs
index 1ec6178c..f077bd35 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -2,8 +2,8 @@ use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::report::Errors;
 use crate::syntax::types::TrivialReason;
 use crate::syntax::{
-    error, ident, Api, Enum, ExternFn, ExternType, Impl, Lang, Receiver, Ref, Signature, Slice,
-    Struct, Ty1, Type, Types,
+    error, ident, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Receiver, Ref, Signature,
+    Slice, Struct, Ty1, Type, Types,
 };
 use proc_macro2::{Delimiter, Group, Ident, TokenStream};
 use quote::{quote, ToTokens};
@@ -35,6 +35,7 @@ fn do_typecheck(cx: &mut Check) {
             Type::CxxVector(ptr) => check_type_cxx_vector(cx, ptr),
             Type::Ref(ty) => check_type_ref(cx, ty),
             Type::Slice(ty) => check_type_slice(cx, ty),
+            Type::Array(array) => check_type_array(cx, array),
             Type::Fn(ty) => check_type_fn(cx, ty),
             Type::Str(_) | Type::Void(_) | Type::SliceRefU8(_) => {}
         }
@@ -184,6 +185,17 @@ fn check_type_slice(cx: &mut Check, ty: &Slice) {
     );
 }
 
+fn check_type_array(cx: &mut Check, ty: &Array) {
+    let supported = match &ty.inner {
+        Type::Fn(_) => false,
+        element => !is_unsized(cx, element),
+    };
+
+    if !supported {
+        cx.error(ty, "unsupported array element type");
+    }
+}
+
 fn check_type_fn(cx: &mut Check, ty: &Signature) {
     if ty.throws {
         cx.error(ty, "function pointer returning Result is not supported yet");
@@ -414,6 +426,7 @@ fn is_unsized(cx: &mut Check, ty: &Type) -> bool {
             let ident = &ident.rust;
             ident == CxxString || is_opaque_cxx(cx, ident) || cx.types.rust.contains(ident)
         }
+        Type::Array(array) => is_unsized(cx, &array.inner),
         Type::CxxVector(_) | Type::Slice(_) | Type::Void(_) => true,
         Type::RustBox(_)
         | Type::RustVec(_)
@@ -498,5 +511,6 @@ fn describe(cx: &mut Check, ty: &Type) -> String {
         },
         Type::Fn(_) => "function pointer".to_owned(),
         Type::Void(_) => "()".to_owned(),
+        Type::Array(_) => "array".to_owned(),
     }
 }
diff --git a/syntax/impls.rs b/syntax/impls.rs
index a9c48c33..f5330936 100644
--- a/syntax/impls.rs
+++ b/syntax/impls.rs
@@ -1,4 +1,4 @@
-use crate::syntax::{ExternFn, Impl, Include, Receiver, Ref, Signature, Slice, Ty1, Type};
+use crate::syntax::{Array, ExternFn, Impl, Include, Receiver, Ref, Signature, Slice, Ty1, Type};
 use std::borrow::Borrow;
 use std::hash::{Hash, Hasher};
 use std::mem;
@@ -50,6 +50,7 @@ impl Hash for Type {
             Type::Fn(t) => t.hash(state),
             Type::Slice(t) => t.hash(state),
             Type::SliceRefU8(t) => t.hash(state),
+            Type::Array(t) => t.hash(state),
             Type::Void(_) => {}
         }
     }
@@ -173,6 +174,42 @@ impl Hash for Slice {
     }
 }
 
+impl Eq for Array {}
+
+impl PartialEq for Array {
+    fn eq(&self, other: &Array) -> bool {
+        let Array {
+            bracket: _,
+            inner,
+            semi_token: _,
+            len,
+            len_token: _,
+        } = self;
+        let Array {
+            bracket: _,
+            inner: inner2,
+            semi_token: _,
+            len: len2,
+            len_token: _,
+        } = other;
+        inner == inner2 && len == len2
+    }
+}
+
+impl Hash for Array {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Array {
+            bracket: _,
+            inner,
+            semi_token: _,
+            len,
+            len_token: _,
+        } = self;
+        inner.hash(state);
+        len.hash(state);
+    }
+}
+
 impl Eq for Signature {}
 
 impl PartialEq for Signature {
diff --git a/syntax/improper.rs b/syntax/improper.rs
index 6fd31629..e0168844 100644
--- a/syntax/improper.rs
+++ b/syntax/improper.rs
@@ -31,6 +31,7 @@ impl<'a> Types<'a> {
             | Type::SliceRefU8(_) => Definite(true),
             Type::UniquePtr(_) | Type::CxxVector(_) => Definite(false),
             Type::Ref(ty) => self.determine_improper_ctype(&ty.inner),
+            Type::Array(ty) => self.determine_improper_ctype(&ty.inner),
         }
     }
 }
diff --git a/syntax/mod.rs b/syntax/mod.rs
index 475cec18..e61e384c 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -30,7 +30,7 @@ use self::symbol::Symbol;
 use proc_macro2::{Ident, Span};
 use syn::punctuated::Punctuated;
 use syn::token::{Brace, Bracket, Paren};
-use syn::{Expr, Generics, Lifetime, Token, Type as RustType};
+use syn::{Expr, Generics, Lifetime, LitInt, Token, Type as RustType};
 
 pub use self::atom::Atom;
 pub use self::derive::Derive;
@@ -166,6 +166,7 @@ pub enum Type {
     Void(Span),
     Slice(Box<Slice>),
     SliceRefU8(Box<Ref>),
+    Array(Box<Array>),
 }
 
 pub struct Ty1 {
@@ -190,6 +191,14 @@ pub struct Slice {
     pub inner: Type,
 }
 
+pub struct Array {
+    pub bracket: Bracket,
+    pub inner: Type,
+    pub semi_token: Token![;],
+    pub len: usize,
+    pub len_token: LitInt,
+}
+
 #[derive(Copy, Clone, PartialEq)]
 pub enum Lang {
     Cxx,
diff --git a/syntax/parse.rs b/syntax/parse.rs
index 8ce767cc..2c8d9823 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -3,7 +3,7 @@ use crate::syntax::file::{Item, ItemForeignMod};
 use crate::syntax::report::Errors;
 use crate::syntax::Atom::*;
 use crate::syntax::{
-    attrs, error, Api, Doc, Enum, ExternFn, ExternType, Impl, Include, IncludeKind, Lang,
+    attrs, error, Api, Array, Doc, Enum, ExternFn, ExternType, Impl, Include, IncludeKind, Lang,
     Namespace, Pair, Receiver, Ref, ResolvableName, Signature, Slice, Struct, Ty1, Type, TypeAlias,
     Var, Variant,
 };
@@ -12,10 +12,10 @@ use quote::{format_ident, quote, quote_spanned};
 use syn::parse::{ParseStream, Parser};
 use syn::punctuated::Punctuated;
 use syn::{
-    Abi, Attribute, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
-    GenericArgument, GenericParam, Generics, Ident, ItemEnum, ItemImpl, ItemStruct, LitStr, Pat,
-    PathArguments, Result, ReturnType, Token, Type as RustType, TypeBareFn, TypePath,
-    TypeReference, TypeSlice,
+    Abi, Attribute, Error, Expr, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
+    GenericArgument, GenericParam, Generics, Ident, ItemEnum, ItemImpl, ItemStruct, Lit, LitStr,
+    Pat, PathArguments, Result, ReturnType, Token, Type as RustType, TypeArray, TypeBareFn,
+    TypePath, TypeReference, TypeSlice,
 };
 
 pub mod kw {
@@ -639,6 +639,7 @@ fn parse_type(ty: &RustType, namespace: &Namespace) -> Result<Type> {
         RustType::Reference(ty) => parse_type_reference(ty, namespace),
         RustType::Path(ty) => parse_type_path(ty, namespace),
         RustType::Slice(ty) => parse_type_slice(ty, namespace),
+        RustType::Array(ty) => parse_type_array(ty, namespace),
         RustType::BareFn(ty) => parse_type_fn(ty, namespace),
         RustType::Tuple(ty) if ty.elems.is_empty() => Ok(Type::Void(ty.paren_token.span)),
         _ => Err(Error::new_spanned(ty, "unsupported type")),
@@ -747,6 +748,36 @@ fn parse_type_slice(ty: &TypeSlice, namespace: &Namespace) -> Result<Type> {
     })))
 }
 
+fn parse_type_array(ty: &TypeArray, namespace: &Namespace) -> Result<Type> {
+    let inner = parse_type(&ty.elem, namespace)?;
+
+    let len_expr = if let Expr::Lit(lit) = &ty.len {
+        lit
+    } else {
+        let msg = "unsupported expression, array length must be an integer literal";
+        return Err(Error::new_spanned(&ty.len, msg));
+    };
+
+    let len_token = if let Lit::Int(int) = &len_expr.lit {
+        int.clone()
+    } else {
+        let msg = "array length must be an integer literal";
+        return Err(Error::new_spanned(len_expr, msg));
+    };
+
+    let bracket = ty.bracket_token;
+    let semi_token = ty.semi_token;
+    let len = len_token.base10_parse::<usize>()?;
+
+    Ok(Type::Array(Box::new(Array {
+        bracket,
+        inner,
+        semi_token,
+        len,
+        len_token,
+    })))
+}
+
 fn parse_type_fn(ty: &TypeBareFn, namespace: &Namespace) -> Result<Type> {
     if ty.lifetimes.is_some() {
         return Err(Error::new_spanned(
diff --git a/syntax/tokens.rs b/syntax/tokens.rs
index 40ab7239..30a63d83 100644
--- a/syntax/tokens.rs
+++ b/syntax/tokens.rs
@@ -1,7 +1,7 @@
 use crate::syntax::atom::Atom::*;
 use crate::syntax::{
-    Atom, Derive, Enum, ExternFn, ExternType, Impl, Receiver, Ref, ResolvableName, Signature,
-    Slice, Struct, Ty1, Type, TypeAlias, Var,
+    Array, Atom, Derive, Enum, ExternFn, ExternType, Impl, Receiver, Ref, ResolvableName,
+    Signature, Slice, Struct, Ty1, Type, TypeAlias, Var,
 };
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{quote_spanned, ToTokens};
@@ -22,6 +22,7 @@ impl ToTokens for Type {
             }
             Type::Ref(r) | Type::Str(r) | Type::SliceRefU8(r) => r.to_tokens(tokens),
             Type::Slice(s) => s.to_tokens(tokens),
+            Type::Array(a) => a.to_tokens(tokens),
             Type::Fn(f) => f.to_tokens(tokens),
             Type::Void(span) => tokens.extend(quote_spanned!(*span=> ())),
         }
@@ -68,6 +69,16 @@ impl ToTokens for Ref {
     }
 }
 
+impl ToTokens for Array {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        self.bracket.surround(tokens, |tokens| {
+            self.inner.to_tokens(tokens);
+            self.semi_token.to_tokens(tokens);
+            self.len_token.to_tokens(tokens);
+        });
+    }
+}
+
 impl ToTokens for Slice {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         self.bracket.surround(tokens, |tokens| {
diff --git a/syntax/types.rs b/syntax/types.rs
index f57b61bb..d59bce0d 100644
--- a/syntax/types.rs
+++ b/syntax/types.rs
@@ -49,6 +49,7 @@ impl<'a> Types<'a> {
                 | Type::RustVec(ty) => visit(all, &ty.inner),
                 Type::Ref(r) => visit(all, &r.inner),
                 Type::Slice(s) => visit(all, &s.inner),
+                Type::Array(a) => visit(all, &a.inner),
                 Type::Fn(f) => {
                     if let Some(ret) = &f.ret {
                         visit(all, ret);
@@ -253,7 +254,7 @@ impl<'a> Types<'a> {
                     Atom::from(&ident.rust) == Some(RustString)
                 }
             }
-            Type::RustVec(_) => true,
+            Type::RustVec(_) | Type::Array(_) => true,
             _ => false,
         }
     }


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index aa9d3b44..2d7544ae 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -65,6 +65,10 @@ pub mod ffi {
         e: COwnedEnum,
     }
 
+    pub struct Array {
+        a: [i32; 4],
+    }
+
     unsafe extern "C++" {
         include!("tests/ffi/tests.h");
 
@@ -155,6 +159,7 @@ pub mod ffi {
         fn set_succeed(self: Pin<&mut C>, n: usize) -> Result<usize>;
         fn get_fail(self: Pin<&mut C>) -> Result<usize>;
         fn c_method_on_shared(self: &Shared) -> usize;
+        fn c_set_array(self: &mut Array, value: i32);
 
         #[rust_name = "i32_overloaded_method"]
         fn cOverloadedMethod(&self, x: i32) -> String;
@@ -229,6 +234,7 @@ pub mod ffi {
         fn get(self: &R) -> usize;
         fn set(self: &mut R, n: usize) -> usize;
         fn r_method_on_shared(self: &Shared) -> String;
+        fn r_get_array_sum(self: &Array) -> i32;
 
         #[cxx_name = "rAliasedFunction"]
         fn r_aliased_function(x: i32) -> String;
@@ -328,6 +334,12 @@ impl ffi::Shared {
     }
 }
 
+impl ffi::Array {
+    pub fn r_get_array_sum(&self) -> i32 {
+        self.a.iter().sum()
+    }
+}
+
 #[derive(Debug)]
 struct Error;
 
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 238b1457..46d8b160 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -32,6 +32,10 @@ size_t C::get_fail() { throw std::runtime_error("unimplemented"); }
 
 size_t Shared::c_method_on_shared() const noexcept { return 2021; }
 
+void Array::c_set_array(int32_t val) noexcept {
+  this->a = {val, val, val, val};
+}
+
 const std::vector<uint8_t> &C::get_v() const { return this->v; }
 
 std::vector<uint8_t> &C::get_v() { return this->v; }
diff --git a/tests/test.rs b/tests/test.rs
index 24fa97fa..3bd41d17 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -198,6 +198,11 @@ fn test_c_method_calls() {
     assert_eq!(2022, unique_ptr.pin_mut().set_succeed(2022).unwrap(),);
     assert!(unique_ptr.pin_mut().get_fail().is_err());
     assert_eq!(2021, ffi::Shared { z: 0 }.c_method_on_shared());
+
+    let val = 42;
+    let mut array = ffi::Array { a: [0, 0, 0, 0] };
+    array.c_set_array(val);
+    assert_eq!(array.a.len() as i32 * val, array.r_get_array_sum());
 }
 
 #[test]
diff --git a/tests/ui/array_len_expr.rs b/tests/ui/array_len_expr.rs
new file mode 100644
index 00000000..fdd9783b
--- /dev/null
+++ b/tests/ui/array_len_expr.rs
@@ -0,0 +1,9 @@
+#[cxx::bridge]
+mod ffi {
+    unsafe extern "C++" {
+        fn arraystr() -> [String; "13"];
+        fn arraysub() -> [String; 15 - 1];
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/array_len_expr.stderr b/tests/ui/array_len_expr.stderr
new file mode 100644
index 00000000..cb943288
--- /dev/null
+++ b/tests/ui/array_len_expr.stderr
@@ -0,0 +1,11 @@
+error: array length must be an integer literal
+ --> $DIR/array_len_expr.rs:4:35
+  |
+4 |         fn arraystr() -> [String; "13"];
+  |                                   ^^^^
+
+error: unsupported expression, array length must be an integer literal
+ --> $DIR/array_len_expr.rs:5:35
+  |
+5 |         fn arraysub() -> [String; 15 - 1];
+  |                                   ^^^^^^
diff --git a/tests/ui/array_len_suffix.rs b/tests/ui/array_len_suffix.rs
new file mode 100644
index 00000000..1e9d514f
--- /dev/null
+++ b/tests/ui/array_len_suffix.rs
@@ -0,0 +1,8 @@
+#[cxx::bridge]
+mod ffi {
+    unsafe extern "C++" {
+        fn array() -> [String; 12u16];
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/array_len_suffix.stderr b/tests/ui/array_len_suffix.stderr
new file mode 100644
index 00000000..b72fc022
--- /dev/null
+++ b/tests/ui/array_len_suffix.stderr
@@ -0,0 +1,10 @@
+error[E0308]: mismatched types
+ --> $DIR/array_len_suffix.rs:4:32
+  |
+4 |         fn array() -> [String; 12u16];
+  |                                ^^^^^ expected `usize`, found `u16`
+  |
+help: change the type of the numeric literal from `u16` to `usize`
+  |
+4 |         fn array() -> [String; 12usize];
+  |                                ^^^^^^^