Repository: dtolnay/cxx (rust)

Issue:
Multiple FFI blocks referencing types from one another
I have two files requiring FFI, `json.rs` and `basevalue.rs`. I'd like to keep the FFI blocks local to those files, instead of having a central `ffi.rs`, but right now this is not possible.

My FFI block in `basevalue.rs`.
```rust
#[cxx::bridge(namespace = base)]
pub mod ffi {
    extern "C" {
        include!("base/values_rust.h");
        type RawOptionalBaseValue;
        fn RustOptionalBaseValueSetString(v: &mut RawOptionalBaseValue, value: &str);
        fn RustOptionalBaseValueSetBool(v: &mut RawOptionalBaseValue, val: bool);
        // etc.
    }
}
```

My FFI block in `json.rs`:
```rust
#[cxx::bridge(namespace = base)]
pub mod ffi {
    extern "Rust" {
        pub fn decode_json(
            bv: UniquePtr<RawOptionalBaseValue>,
            json: &str
        ) -> bool;
    }
}       
```

Note that the `json.rs` block references a type from the `basevalue.rs` block.

This gives
```
error[cxxbridge]: unsupported type
```

which is unsurprising, and I imagine is really hard to solve.

(The exact code above is significantly simplified so obviously don't try to build it; if this issue isn't as "known" as I expect, let me know and I'll make a minimal test case).

Pull request description:
Enable sharing a consistent Rust type across multiple FFI blocks
Fixes #99.

This PR introduces a trait `cxx::ExternType` documented as follows.

---

> A type for which the layout is determined by its C++ definition.
>
> This trait serves the following two related purposes.
>
> ### Safely unifying occurrences of the same extern type
>
> `ExternType` makes it possible for CXX to safely share a consistent Rust type across multiple #\[cxx::bridge\] invocations that refer to a common extern C++ type.
>
> In the following snippet, two #\[cxx::bridge\] invocations in different files (possibly different crates) both contain function signatures involving the same C++ type `example::Demo`. If both were written just containing `type Demo;`, then both macro expansions would produce their own separate Rust type called `Demo` and thus the compiler wouldn't allow us to take the `Demo` returned by `file1::ffi::create_demo` and pass it as the `Demo` argument accepted by `file2::ffi::take_ref_demo`. Instead, one of the two `Demo`s has been defined as an extern type alias of the other, making them the same type in Rust. The CXX code generator will use an automatically generated `ExternType` impl emitted in file1 to statically verify that in file2 `crate::file1::ffi::Demo` really does refer to the C++ type `example::Demo` as expected in file2.
>
> ```rust
> // file1.rs
> #[cxx::bridge(namespace = example)]
> pub mod ffi {
>     extern "C" {
>         type Demo;
>
>         fn create_demo() -> UniquePtr<Demo>;
>     }
> }
>
> // file2.rs
> #[cxx::bridge(namespace = example)]
> pub mod ffi {
>     extern "C" {
>         type Demo = crate::file1::ffi::Demo;
>
>         fn take_ref_demo(demo: &Demo);
>     }
> }
> ```
>
> ### Integrating with bindgen-generated types
>
> Handwritten `ExternType` impls make it possible to plug in a data structure emitted by bindgen as the definition of an opaque C++ type emitted by CXX.
>
> By writing the unsafe `ExternType` impl, the programmer asserts that the C++ namespace and type name given in the type id refers to a C++ type that is equivalent to Rust type that is the `Self` type of the impl.
>
> ```rust
> mod folly_sys;  // the bindgen-generated bindings
>
> use cxx::{type_id, ExternType};
>
> unsafe impl ExternType for folly_sys::StringPiece {
>     type Id = type_id!("folly::StringPiece");
> }
>
> #[cxx::bridge(namespace = folly)]
> pub mod ffi {
>     extern "C" {
>         include!("rust_cxx_bindings.h");
>
>         type StringPiece = crate::folly_sys::StringPiece;
>
>         fn print_string_piece(s: &StringPiece);
>     }
> }
>
> // Now if we construct a StringPiece or obtain one through one
> // of the bindgen-generated signatures, we are able to pass it
> // along to ffi::print_string_piece.
> ```


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 1455e557..dd7c18fa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,7 +22,7 @@ cc = "1.0.49"
 cxx-build = { version = "=0.3.0", path = "gen/build" }
 cxx-test-suite = { version = "0", path = "tests/ffi" }
 rustversion = "1.0"
-trybuild = { version = "1.0.21", features = ["diff"] }
+trybuild = { version = "1.0.27", features = ["diff"] }
 
 [workspace]
 members = ["demo-rs", "gen/build", "gen/cmd", "macro", "tests/ffi"]
diff --git a/gen/build/Cargo.toml b/gen/build/Cargo.toml
index 1cbcced5..16aeba3d 100644
--- a/gen/build/Cargo.toml
+++ b/gen/build/Cargo.toml
@@ -13,7 +13,7 @@ cc = "1.0.49"
 codespan-reporting = "0.9"
 proc-macro2 = { version = "1.0.12", features = ["span-locations"] }
 quote = "1.0"
-syn = { version = "1.0", features = ["full"] }
+syn = { version = "1.0.19", features = ["full"] }
 
 [package.metadata.docs.rs]
 targets = ["x86_64-unknown-linux-gnu"]
diff --git a/gen/cmd/Cargo.toml b/gen/cmd/Cargo.toml
index 57f2e39c..92adb89f 100644
--- a/gen/cmd/Cargo.toml
+++ b/gen/cmd/Cargo.toml
@@ -17,7 +17,7 @@ codespan-reporting = "0.9"
 proc-macro2 = { version = "1.0.12", features = ["span-locations"] }
 quote = "1.0"
 structopt = "0.3"
-syn = { version = "1.0", features = ["full"] }
+syn = { version = "1.0.19", features = ["full"] }
 
 [package.metadata.docs.rs]
 targets = ["x86_64-unknown-linux-gnu"]
diff --git a/gen/src/write.rs b/gen/src/write.rs
index f28a58e0..8f54bd7c 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -971,10 +971,6 @@ fn to_mangled(namespace: &Namespace, ty: &Type) -> String {
 }
 
 fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
-    fn allow_unique_ptr(ident: &Ident) -> bool {
-        Atom::from(ident).is_none()
-    }
-
     out.begin_block("extern \"C\"");
     for ty in types {
         if let Type::RustBox(ty) = ty {
@@ -991,14 +987,14 @@ fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
             }
         } else if let Type::UniquePtr(ptr) = ty {
             if let Type::Ident(inner) = &ptr.inner {
-                if allow_unique_ptr(inner) {
+                if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
                     out.next_section();
                     write_unique_ptr(out, inner, types);
                 }
             }
         } else if let Type::CxxVector(ptr) = ty {
             if let Type::Ident(inner) = &ptr.inner {
-                if Atom::from(inner).is_none() {
+                if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
                     out.next_section();
                     write_cxx_vector(out, ty, inner, types);
                 }
diff --git a/macro/Cargo.toml b/macro/Cargo.toml
index 4c2d4237..8ade852f 100644
--- a/macro/Cargo.toml
+++ b/macro/Cargo.toml
@@ -13,8 +13,8 @@ proc-macro = true
 
 [dependencies]
 proc-macro2 = "1.0"
-quote = "1.0"
-syn = { version = "1.0", features = ["full"] }
+quote = "1.0.4"
+syn = { version = "1.0.19", features = ["full"] }
 
 [dev-dependencies]
 cxx = { version = "0.3", path = ".." }
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index fcb845c5..fc129c6d 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -3,7 +3,7 @@ use crate::syntax::namespace::Namespace;
 use crate::syntax::report::Errors;
 use crate::syntax::symbol::Symbol;
 use crate::syntax::{
-    self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, Types,
+    self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, TypeAlias, Types,
 };
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote, quote_spanned, ToTokens};
@@ -46,7 +46,7 @@ fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> T
             Api::Enum(enm) => expanded.extend(expand_enum(enm)),
             Api::CxxType(ety) => {
                 if !types.enums.contains_key(&ety.ident) {
-                    expanded.extend(expand_cxx_type(ety));
+                    expanded.extend(expand_cxx_type(namespace, ety));
                 }
             }
             Api::CxxFunction(efn) => {
@@ -55,6 +55,10 @@ fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> T
             Api::RustFunction(efn) => {
                 hidden.extend(expand_rust_function_shim(namespace, efn, types))
             }
+            Api::TypeAlias(alias) => {
+                expanded.extend(expand_type_alias(alias));
+                hidden.extend(expand_type_alias_verify(namespace, alias));
+            }
         }
     }
 
@@ -73,13 +77,13 @@ fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> T
             }
         } else if let Type::UniquePtr(ptr) = ty {
             if let Type::Ident(ident) = &ptr.inner {
-                if Atom::from(ident).is_none() {
+                if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
                     expanded.extend(expand_unique_ptr(namespace, ident, types));
                 }
             }
         } else if let Type::CxxVector(ptr) = ty {
             if let Type::Ident(ident) = &ptr.inner {
-                if Atom::from(ident).is_none() {
+                if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
                     // Generate impl for CxxVector<T> if T is a struct or opaque
                     // C++ type. Impl for primitives is already provided by cxx
                     // crate.
@@ -161,15 +165,21 @@ fn expand_enum(enm: &Enum) -> TokenStream {
     }
 }
 
-fn expand_cxx_type(ety: &ExternType) -> TokenStream {
+fn expand_cxx_type(namespace: &Namespace, ety: &ExternType) -> TokenStream {
     let ident = &ety.ident;
     let doc = &ety.doc;
+    let type_id = type_id(namespace, ident);
+
     quote! {
         #doc
         #[repr(C)]
         pub struct #ident {
             _private: ::cxx::private::Opaque,
         }
+
+        unsafe impl ::cxx::ExternType for #ident {
+            type Id = #type_id;
+        }
     }
 }
 
@@ -554,6 +564,40 @@ fn expand_rust_function_shim_impl(
     }
 }
 
+fn expand_type_alias(alias: &TypeAlias) -> TokenStream {
+    let ident = &alias.ident;
+    let ty = &alias.ty;
+    quote! {
+        pub type #ident = #ty;
+    }
+}
+
+fn expand_type_alias_verify(namespace: &Namespace, alias: &TypeAlias) -> TokenStream {
+    let ident = &alias.ident;
+    let type_id = type_id(namespace, ident);
+    let begin_span = alias.type_token.span;
+    let end_span = alias.semi_token.span;
+    let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_type::<);
+    let end = quote_spanned!(end_span=> >);
+
+    quote! {
+        const _: fn() = #begin #ident, #type_id #end;
+    }
+}
+
+fn type_id(namespace: &Namespace, ident: &Ident) -> TokenStream {
+    let mut path = String::new();
+    for name in namespace {
+        path += &name.to_string();
+        path += "::";
+    }
+    path += &ident.to_string();
+
+    quote! {
+        ::cxx::type_id!(#path)
+    }
+}
+
 fn expand_rust_box(namespace: &Namespace, ident: &Ident) -> TokenStream {
     let link_prefix = format!("cxxbridge03$box${}{}$", namespace, ident);
     let link_uninit = format!("{}uninit", link_prefix);
diff --git a/macro/src/lib.rs b/macro/src/lib.rs
index b56f58e4..bc8d19bb 100644
--- a/macro/src/lib.rs
+++ b/macro/src/lib.rs
@@ -11,10 +11,11 @@ extern crate proc_macro;
 
 mod expand;
 mod syntax;
+mod type_id;
 
 use crate::syntax::namespace::Namespace;
 use proc_macro::TokenStream;
-use syn::{parse_macro_input, ItemMod};
+use syn::{parse_macro_input, ItemMod, LitStr};
 
 /// `#[cxx::bridge] mod ffi { ... }`
 ///
@@ -44,3 +45,9 @@ pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream {
         .unwrap_or_else(|err| err.to_compile_error())
         .into()
 }
+
+#[proc_macro]
+pub fn type_id(input: TokenStream) -> TokenStream {
+    let arg = parse_macro_input!(input as LitStr);
+    type_id::expand(arg).into()
+}
diff --git a/macro/src/type_id.rs b/macro/src/type_id.rs
new file mode 100644
index 00000000..445da2ba
--- /dev/null
+++ b/macro/src/type_id.rs
@@ -0,0 +1,29 @@
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+use syn::LitStr;
+
+// "folly::File" => `(f, o, l, l, y, (), F, i, l, e)`
+pub fn expand(arg: LitStr) -> TokenStream {
+    let mut ids = Vec::new();
+
+    for word in arg.value().split("::") {
+        if !ids.is_empty() {
+            ids.push(quote!(()));
+        }
+        for ch in word.chars() {
+            ids.push(match ch {
+                'A'..='Z' | 'a'..='z' => {
+                    let t = format_ident!("{}", ch);
+                    quote!(::cxx::#t)
+                }
+                '0'..='9' | '_' => {
+                    let t = format_ident!("_{}", ch);
+                    quote!(::cxx::#t)
+                }
+                _ => quote!([(); #ch as _]),
+            });
+        }
+    }
+
+    quote! { (#(#ids,)*) }
+}
diff --git a/src/extern_type.rs b/src/extern_type.rs
new file mode 100644
index 00000000..6701ef59
--- /dev/null
+++ b/src/extern_type.rs
@@ -0,0 +1,110 @@
+/// A type for which the layout is determined by its C++ definition.
+///
+/// This trait serves the following two related purposes.
+///
+/// <br>
+///
+/// ## Safely unifying occurrences of the same extern type
+///
+/// `ExternType` makes it possible for CXX to safely share a consistent Rust
+/// type across multiple #\[cxx::bridge\] invocations that refer to a common
+/// extern C++ type.
+///
+/// In the following snippet, two #\[cxx::bridge\] invocations in different
+/// files (possibly different crates) both contain function signatures involving
+/// the same C++ type `example::Demo`. If both were written just containing
+/// `type Demo;`, then both macro expansions would produce their own separate
+/// Rust type called `Demo` and thus the compiler wouldn't allow us to take the
+/// `Demo` returned by `file1::ffi::create_demo` and pass it as the `Demo`
+/// argument accepted by `file2::ffi::take_ref_demo`. Instead, one of the two
+/// `Demo`s has been defined as an extern type alias of the other, making them
+/// the same type in Rust. The CXX code generator will use an automatically
+/// generated `ExternType` impl emitted in file1 to statically verify that in
+/// file2 `crate::file1::ffi::Demo` really does refer to the C++ type
+/// `example::Demo` as expected in file2.
+///
+/// ```no_run
+/// // file1.rs
+/// # mod file1 {
+/// #[cxx::bridge(namespace = example)]
+/// pub mod ffi {
+///     extern "C" {
+///         type Demo;
+///
+///         fn create_demo() -> UniquePtr<Demo>;
+///     }
+/// }
+/// # }
+///
+/// // file2.rs
+/// #[cxx::bridge(namespace = example)]
+/// pub mod ffi {
+///     extern "C" {
+///         type Demo = crate::file1::ffi::Demo;
+///
+///         fn take_ref_demo(demo: &Demo);
+///     }
+/// }
+/// #
+/// # fn main() {}
+/// ```
+///
+/// <br><br>
+///
+/// ## Integrating with bindgen-generated types
+///
+/// Handwritten `ExternType` impls make it possible to plug in a data structure
+/// emitted by bindgen as the definition of an opaque C++ type emitted by CXX.
+///
+/// By writing the unsafe `ExternType` impl, the programmer asserts that the C++
+/// namespace and type name given in the type id refers to a C++ type that is
+/// equivalent to Rust type that is the `Self` type of the impl.
+///
+/// ```no_run
+/// # const _: &str = stringify! {
+/// mod folly_sys;  // the bindgen-generated bindings
+/// # };
+/// # mod folly_sys {
+/// #     #[repr(transparent)]
+/// #     pub struct StringPiece([usize; 2]);
+/// # }
+///
+/// use cxx::{type_id, ExternType};
+///
+/// unsafe impl ExternType for folly_sys::StringPiece {
+///     type Id = type_id!("folly::StringPiece");
+/// }
+///
+/// #[cxx::bridge(namespace = folly)]
+/// pub mod ffi {
+///     extern "C" {
+///         include!("rust_cxx_bindings.h");
+///
+///         type StringPiece = crate::folly_sys::StringPiece;
+///
+///         fn print_string_piece(s: &StringPiece);
+///     }
+/// }
+///
+/// // Now if we construct a StringPiece or obtain one through one
+/// // of the bindgen-generated signatures, we are able to pass it
+/// // along to ffi::print_string_piece.
+/// #
+/// # fn main() {}
+/// ```
+pub unsafe trait ExternType {
+    /// A type-level representation of the type's C++ namespace and type name.
+    ///
+    /// This will always be defined using `type_id!` in the following form:
+    ///
+    /// ```
+    /// # struct TypeName;
+    /// # unsafe impl cxx::ExternType for TypeName {
+    /// type Id = cxx::type_id!("name::space::of::TypeName");
+    /// # }
+    /// ```
+    type Id;
+}
+
+#[doc(hidden)]
+pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
diff --git a/src/lib.rs b/src/lib.rs
index cc0dcf22..fc0e6828 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -345,6 +345,7 @@
 
 #![doc(html_root_url = "https://docs.rs/cxx/0.3.0")]
 #![deny(improper_ctypes)]
+#![allow(non_camel_case_types)]
 #![allow(
     clippy::cognitive_complexity,
     clippy::declare_interior_mutable_const,
@@ -369,6 +370,7 @@ mod macros;
 mod cxx_string;
 mod cxx_vector;
 mod exception;
+mod extern_type;
 mod function;
 mod opaque;
 mod result;
@@ -385,13 +387,18 @@ mod symbols;
 pub use crate::cxx_string::CxxString;
 pub use crate::cxx_vector::CxxVector;
 pub use crate::exception::Exception;
+pub use crate::extern_type::ExternType;
 pub use crate::unique_ptr::UniquePtr;
-pub use cxxbridge_macro::bridge;
+pub use cxxbridge_macro::{bridge};
+
+/// For use in impls of the `ExternType` trait. See [`ExternType`].
+pub use cxxbridge_macro::type_id;
 
 // Not public API.
 #[doc(hidden)]
 pub mod private {
     pub use crate::cxx_vector::VectorElement;
+    pub use crate::extern_type::verify_extern_type;
     pub use crate::function::FatFunction;
     pub use crate::opaque::Opaque;
     pub use crate::result::{r#try, Result};
@@ -402,3 +409,19 @@ pub mod private {
     pub use crate::unique_ptr::UniquePtrTarget;
     pub use crate::unwind::catch_unwind;
 }
+
+macro_rules! chars {
+    ($($ch:ident)*) => {
+        $(
+            #[doc(hidden)]
+            pub enum $ch {}
+        )*
+    };
+}
+
+chars! {
+    _0 _1 _2 _3 _4 _5 _6 _7 _8 _9
+    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+    a b c d e f g h i j k l m n o p q r s t u v w x y z
+    __ // underscore
+}
diff --git a/syntax/ident.rs b/syntax/ident.rs
index cec424c0..7545e92c 100644
--- a/syntax/ident.rs
+++ b/syntax/ident.rs
@@ -42,6 +42,9 @@ pub(crate) fn check_all(cx: &mut Check, namespace: &Namespace, apis: &[Api]) {
                     check(cx, &arg.ident);
                 }
             }
+            Api::TypeAlias(alias) => {
+                check(cx, &alias.ident);
+            }
         }
     }
 }
diff --git a/syntax/mod.rs b/syntax/mod.rs
index fd8db73d..3eb6c6e6 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -20,7 +20,7 @@ use self::parse::kw;
 use proc_macro2::{Ident, Span};
 use syn::punctuated::Punctuated;
 use syn::token::{Brace, Bracket, Paren};
-use syn::{Lifetime, LitStr, Token};
+use syn::{Lifetime, LitStr, Token, Type as RustType};
 
 pub use self::atom::Atom;
 pub use self::doc::Doc;
@@ -35,6 +35,7 @@ pub enum Api {
     CxxFunction(ExternFn),
     RustType(ExternType),
     RustFunction(ExternFn),
+    TypeAlias(TypeAlias),
 }
 
 pub struct ExternType {
@@ -68,6 +69,14 @@ pub struct ExternFn {
     pub semi_token: Token![;],
 }
 
+pub struct TypeAlias {
+    pub type_token: Token![type],
+    pub ident: Ident,
+    pub eq_token: Token![=],
+    pub ty: RustType,
+    pub semi_token: Token![;],
+}
+
 pub struct Signature {
     pub fn_token: Token![fn],
     pub receiver: Option<Receiver>,
diff --git a/syntax/parse.rs b/syntax/parse.rs
index b794ba92..0640e7f8 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -2,17 +2,19 @@ use crate::syntax::report::Errors;
 use crate::syntax::Atom::*;
 use crate::syntax::{
     attrs, error, Api, Doc, Enum, ExternFn, ExternType, Lang, Receiver, Ref, Signature, Slice,
-    Struct, Ty1, Type, Var, Variant,
+    Struct, Ty1, Type, TypeAlias, Var, Variant,
 };
+use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
 use std::collections::HashSet;
 use std::u32;
+use syn::parse::{ParseStream, Parser};
 use syn::punctuated::Punctuated;
 use syn::{
-    Abi, Error, Expr, ExprLit, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
-    GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, Lit, Pat, PathArguments,
-    Result, ReturnType, Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice,
-    Variant as RustVariant,
+    Abi, Attribute, Error, Expr, ExprLit, Fields, FnArg, ForeignItem, ForeignItemFn,
+    ForeignItemType, GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, Lit, Pat,
+    PathArguments, Result, ReturnType, Token, Type as RustType, TypeBareFn, TypePath,
+    TypeReference, TypeSlice, Variant as RustVariant,
 };
 
 pub mod kw {
@@ -182,16 +184,21 @@ fn parse_foreign_mod(cx: &mut Errors, foreign_mod: ItemForeignMod, out: &mut Vec
                     Err(err) => cx.push(err),
                 }
             }
+            ForeignItem::Verbatim(tokens) => match parse_extern_verbatim(tokens, lang) {
+                Ok(api) => items.push(api),
+                Err(err) => cx.push(err),
+            },
             _ => cx.error(foreign, "unsupported foreign item"),
         }
     }
 
     let mut types = items.iter().filter_map(|item| match item {
-        Api::CxxType(ty) | Api::RustType(ty) => Some(ty),
+        Api::CxxType(ty) | Api::RustType(ty) => Some(&ty.ident),
+        Api::TypeAlias(alias) => Some(&alias.ident),
         _ => None,
     });
     if let (Some(single_type), None) = (types.next(), types.next()) {
-        let single_type = single_type.ident.clone();
+        let single_type = single_type.clone();
         for item in &mut items {
             if let Api::CxxFunction(efn) | Api::RustFunction(efn) = item {
                 if let Some(receiver) = &mut efn.receiver {
@@ -336,6 +343,44 @@ fn parse_extern_fn(foreign_fn: &ForeignItemFn, lang: Lang) -> Result<Api> {
     }))
 }
 
+fn parse_extern_verbatim(tokens: &TokenStream, lang: Lang) -> Result<Api> {
+    // type Alias = crate::path::to::Type;
+    fn parse(input: ParseStream) -> Result<TypeAlias> {
+        let attrs = input.call(Attribute::parse_outer)?;
+        let type_token: Token![type] = match input.parse()? {
+            Some(type_token) => type_token,
+            None => {
+                let span = input.cursor().token_stream();
+                return Err(Error::new_spanned(span, "unsupported foreign item"));
+            }
+        };
+        let ident: Ident = input.parse()?;
+        let eq_token: Token![=] = input.parse()?;
+        let ty: RustType = input.parse()?;
+        let semi_token: Token![;] = input.parse()?;
+        attrs::parse_doc(&attrs)?;
+
+        Ok(TypeAlias {
+            type_token,
+            ident,
+            eq_token,
+            ty,
+            semi_token,
+        })
+    }
+
+    let type_alias = parse.parse2(tokens.clone())?;
+    match lang {
+        Lang::Cxx => Ok(Api::TypeAlias(type_alias)),
+        Lang::Rust => {
+            let (type_token, semi_token) = (type_alias.type_token, type_alias.semi_token);
+            let span = quote!(#type_token #semi_token);
+            let msg = "type alias in extern \"Rust\" block is not supported";
+            Err(Error::new_spanned(span, msg))
+        }
+    }
+}
+
 fn parse_type(ty: &RustType) -> Result<Type> {
     match ty {
         RustType::Reference(ty) => parse_type_reference(ty),
diff --git a/syntax/tokens.rs b/syntax/tokens.rs
index 934c85bf..6dd60738 100644
--- a/syntax/tokens.rs
+++ b/syntax/tokens.rs
@@ -1,6 +1,7 @@
 use crate::syntax::atom::Atom::*;
 use crate::syntax::{
-    Derive, Enum, ExternFn, ExternType, Receiver, Ref, Signature, Slice, Struct, Ty1, Type, Var,
+    Derive, Enum, ExternFn, ExternType, Receiver, Ref, Signature, Slice, Struct, Ty1, Type,
+    TypeAlias, Var,
 };
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{quote_spanned, ToTokens};
@@ -86,6 +87,14 @@ impl ToTokens for ExternType {
     }
 }
 
+impl ToTokens for TypeAlias {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // Notional token range for error reporting purposes.
+        self.type_token.to_tokens(tokens);
+        self.ident.to_tokens(tokens);
+    }
+}
+
 impl ToTokens for Struct {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         // Notional token range for error reporting purposes.
diff --git a/syntax/types.rs b/syntax/types.rs
index 58005139..ba9bc789 100644
--- a/syntax/types.rs
+++ b/syntax/types.rs
@@ -1,7 +1,7 @@
 use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::report::Errors;
 use crate::syntax::set::OrderedSet as Set;
-use crate::syntax::{Api, Derive, Enum, Struct, Type};
+use crate::syntax::{Api, Derive, Enum, Struct, Type, TypeAlias};
 use proc_macro2::Ident;
 use quote::ToTokens;
 use std::collections::{BTreeMap as Map, HashSet as UnorderedSet};
@@ -12,6 +12,7 @@ pub struct Types<'a> {
     pub enums: Map<Ident, &'a Enum>,
     pub cxx: Set<'a, Ident>,
     pub rust: Set<'a, Ident>,
+    pub aliases: Map<Ident, &'a TypeAlias>,
 }
 
 impl<'a> Types<'a> {
@@ -21,6 +22,7 @@ impl<'a> Types<'a> {
         let mut enums = Map::new();
         let mut cxx = Set::new();
         let mut rust = Set::new();
+        let mut aliases = Map::new();
 
         fn visit<'a>(all: &mut Set<'a, Type>, ty: &'a Type) {
             all.insert(ty);
@@ -96,6 +98,14 @@ impl<'a> Types<'a> {
                         visit(&mut all, ret);
                     }
                 }
+                Api::TypeAlias(alias) => {
+                    let ident = &alias.ident;
+                    if !type_names.insert(ident) {
+                        duplicate_name(cx, alias, ident);
+                    }
+                    cxx.insert(ident);
+                    aliases.insert(ident.clone(), alias);
+                }
             }
         }
 
@@ -105,6 +115,7 @@ impl<'a> Types<'a> {
             enums,
             cxx,
             rust,
+            aliases,
         }
     }
 
diff --git a/third-party/BUCK b/third-party/BUCK
index 2de784b2..e16bad29 100644
--- a/third-party/BUCK
+++ b/third-party/BUCK
@@ -131,7 +131,7 @@ rust_library(
 
 rust_library(
     name = "syn",
-    srcs = glob(["vendor/syn-1.0.18/src/**"]),
+    srcs = glob(["vendor/syn-1.0.19/src/**"]),
     visibility = ["PUBLIC"],
     features = [
         "clone-impls",
diff --git a/third-party/BUILD b/third-party/BUILD
index 4d8fd9f9..45b1163c 100644
--- a/third-party/BUILD
+++ b/third-party/BUILD
@@ -136,7 +136,7 @@ rust_library(
 
 rust_library(
     name = "syn",
-    srcs = glob(["vendor/syn-1.0.18/src/**"]),
+    srcs = glob(["vendor/syn-1.0.19/src/**"]),
     crate_features = [
         "clone-impls",
         "derive",
diff --git a/third-party/Cargo.lock b/third-party/Cargo.lock
index 3397192f..b1227f97 100644
--- a/third-party/Cargo.lock
+++ b/third-party/Cargo.lock
@@ -307,9 +307,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.18"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213"
+checksum = "e8e5aa70697bb26ee62214ae3288465ecec0000f05182f039b477001f08f5ae7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -356,9 +356,9 @@ dependencies = [
 
 [[package]]
 name = "trybuild"
-version = "1.0.26"
+version = "1.0.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e5696e4fd793743fbcc29943fe965ea3993b6c3d2a6a3a35c6680d926fd3a49"
+checksum = "744665442556a91933cee5e75b0371376eb03498c4d0bfbcebd2a9882b4fb5ef"
 dependencies = [
  "dissimilar",
  "glob",


Test patch:
diff --git a/tests/BUCK b/tests/BUCK
index 659223ca..41781c84 100644
--- a/tests/BUCK
+++ b/tests/BUCK
@@ -6,7 +6,10 @@ rust_test(
 
 rust_library(
     name = "ffi",
-    srcs = ["ffi/lib.rs"],
+    srcs = [
+        "ffi/lib.rs",
+        "ffi/module.rs",
+    ],
     crate = "cxx_test_suite",
     deps = [
         ":impl",
@@ -18,25 +21,33 @@ cxx_library(
     name = "impl",
     srcs = [
         "ffi/tests.cc",
-        ":gen-source",
+        ":gen-lib-source",
+        ":gen-module-source",
     ],
     headers = {
-        "ffi/lib.rs.h": ":gen-header",
+        "ffi/lib.rs.h": ":gen-lib-header",
         "ffi/tests.h": "ffi/tests.h",
     },
     deps = ["//:core"],
 )
 
 genrule(
-    name = "gen-header",
+    name = "gen-lib-header",
     srcs = ["ffi/lib.rs"],
     cmd = "$(exe //:codegen) --header ${SRCS} > ${OUT}",
-    out = "generated.h",
+    out = "lib.rs.h",
 )
 
 genrule(
-    name = "gen-source",
+    name = "gen-lib-source",
     srcs = ["ffi/lib.rs"],
     cmd = "$(exe //:codegen) ${SRCS} > ${OUT}",
-    out = "generated.cc",
+    out = "lib.rs.cc",
+)
+
+genrule(
+    name = "gen-module-source",
+    srcs = ["ffi/module.rs"],
+    cmd = "$(exe //:codegen) ${SRCS} > ${OUT}",
+    out = "module.rs.cc",
 )
diff --git a/tests/BUILD b/tests/BUILD
index 65c5b415..e1f16371 100644
--- a/tests/BUILD
+++ b/tests/BUILD
@@ -8,7 +8,10 @@ rust_test(
 
 rust_library(
     name = "cxx_test_suite",
-    srcs = ["ffi/lib.rs"],
+    srcs = [
+        "ffi/lib.rs",
+        "ffi/module.rs",
+    ],
     deps = [
         ":impl",
         "//:cxx",
@@ -19,17 +22,18 @@ cc_library(
     name = "impl",
     srcs = [
         "ffi/tests.cc",
-        ":gen-source",
+        ":gen-lib-source",
+        ":gen-module-source",
     ],
     hdrs = ["ffi/tests.h"],
     deps = [
-        ":include",
+        ":lib-include",
         "//:core",
     ],
 )
 
 genrule(
-    name = "gen-header",
+    name = "gen-lib-header",
     srcs = ["ffi/lib.rs"],
     outs = ["lib.rs.h"],
     cmd = "$(location //:codegen) --header $< > $@",
@@ -37,15 +41,23 @@ genrule(
 )
 
 genrule(
-    name = "gen-source",
+    name = "gen-lib-source",
     srcs = ["ffi/lib.rs"],
-    outs = ["generated.cc"],
+    outs = ["lib.rs.cc"],
     cmd = "$(location //:codegen) $< > $@",
     tools = ["//:codegen"],
 )
 
 cc_library(
-    name = "include",
-    hdrs = [":gen-header"],
+    name = "lib-include",
+    hdrs = [":gen-lib-header"],
     include_prefix = "tests/ffi",
 )
+
+genrule(
+    name = "gen-module-source",
+    srcs = ["ffi/module.rs"],
+    outs = ["module.rs.cc"],
+    cmd = "$(location //:codegen) $< > $@",
+    tools = ["//:codegen"],
+)
diff --git a/tests/ffi/build.rs b/tests/ffi/build.rs
index b970362a..2c96d732 100644
--- a/tests/ffi/build.rs
+++ b/tests/ffi/build.rs
@@ -3,7 +3,8 @@ fn main() {
         return;
     }
 
-    cxx_build::bridge("lib.rs")
+    let sources = vec!["lib.rs", "module.rs"];
+    cxx_build::bridges(sources)
         .file("tests.cc")
         .flag("-std=c++11")
         .compile("cxx-test-suite");
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index e298e881..94572299 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -4,6 +4,8 @@
     clippy::trivially_copy_pass_by_ref
 )]
 
+pub mod module;
+
 use cxx::{CxxString, UniquePtr};
 use std::fmt::{self, Display};
 
@@ -47,7 +49,6 @@ pub mod ffi {
         fn c_take_primitive(n: usize);
         fn c_take_shared(shared: Shared);
         fn c_take_box(r: Box<R>);
-        fn c_take_unique_ptr(c: UniquePtr<C>);
         fn c_take_ref_r(r: &R);
         fn c_take_ref_c(c: &C);
         fn c_take_str(s: &str);
diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs
new file mode 100644
index 00000000..77bae066
--- /dev/null
+++ b/tests/ffi/module.rs
@@ -0,0 +1,13 @@
+// Rustfmt mangles the extern type alias.
+// https://github.com/rust-lang/rustfmt/issues/4159
+#[rustfmt::skip]
+#[cxx::bridge(namespace = tests)]
+pub mod ffi {
+    extern "C" {
+        include!("tests/ffi/tests.h");
+
+        type C = crate::ffi::C;
+
+        fn c_take_unique_ptr(c: UniquePtr<C>);
+    }
+}
diff --git a/tests/test.rs b/tests/test.rs
index b8593c58..ddf8f70a 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -89,7 +89,7 @@ fn test_c_take() {
     check!(ffi::c_take_shared(ffi::Shared { z: 2020 }));
     check!(ffi::c_take_box(Box::new(2020)));
     check!(ffi::c_take_ref_c(&unique_ptr));
-    check!(ffi::c_take_unique_ptr(unique_ptr));
+    check!(cxx_test_suite::module::ffi::c_take_unique_ptr(unique_ptr));
     check!(ffi::c_take_str("2020"));
     check!(ffi::c_take_sliceu8(b"2020"));
     check!(ffi::c_take_rust_string("2020".to_owned()));
diff --git a/tests/ui/type_alias_rust.rs b/tests/ui/type_alias_rust.rs
new file mode 100644
index 00000000..67df4892
--- /dev/null
+++ b/tests/ui/type_alias_rust.rs
@@ -0,0 +1,9 @@
+#[cxx::bridge]
+mod ffi {
+    extern "Rust" {
+        /// Incorrect.
+        type Alias = crate::Type;
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/type_alias_rust.stderr b/tests/ui/type_alias_rust.stderr
new file mode 100644
index 00000000..1b08f67c
--- /dev/null
+++ b/tests/ui/type_alias_rust.stderr
@@ -0,0 +1,5 @@
+error: type alias in extern "Rust" block is not supported
+ --> $DIR/type_alias_rust.rs:5:9
+  |
+5 |         type Alias = crate::Type;
+  |         ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/wrong_type_id.rs b/tests/ui/wrong_type_id.rs
new file mode 100644
index 00000000..81a9b3f8
--- /dev/null
+++ b/tests/ui/wrong_type_id.rs
@@ -0,0 +1,15 @@
+#[cxx::bridge(namespace = folly)]
+mod here {
+    extern "C" {
+        type StringPiece;
+    }
+}
+
+#[cxx::bridge(namespace = folly)]
+mod there {
+    extern "C" {
+        type ByteRange = crate::here::StringPiece;
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/wrong_type_id.stderr b/tests/ui/wrong_type_id.stderr
new file mode 100644
index 00000000..5b4f6c66
--- /dev/null
+++ b/tests/ui/wrong_type_id.stderr
@@ -0,0 +1,13 @@
+error[E0271]: type mismatch resolving `<here::StringPiece as cxx::extern_type::ExternType>::Id == (cxx::f, cxx::o, cxx::l, cxx::l, cxx::y, (), cxx::B, cxx::y, cxx::t, cxx::e, cxx::R, cxx::a, cxx::n, cxx::g, cxx::e)`
+   --> $DIR/wrong_type_id.rs:11:9
+    |
+11  |         type ByteRange = crate::here::StringPiece;
+    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected a tuple with 15 elements, found one with 17 elements
+    |
+   ::: $WORKSPACE/src/extern_type.rs:110:41
+    |
+110 | pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
+    |                                         ------- required by this bound in `cxx::extern_type::verify_extern_type`
+    |
+    = note: expected tuple `(cxx::f, cxx::o, cxx::l, cxx::l, cxx::y, (), cxx::B, cxx::y, cxx::t, cxx::e, cxx::R, cxx::a, cxx::n, cxx::g, cxx::e)`
+               found tuple `(cxx::f, cxx::o, cxx::l, cxx::l, cxx::y, (), cxx::S, cxx::t, cxx::r, cxx::i, cxx::n, cxx::g, cxx::P, cxx::i, cxx::e, cxx::c, cxx::e)`