Add ForeignName wrapper around non-Rust names
diff --git a/syntax/attrs.rs b/syntax/attrs.rs
index d3e2bb0..4808f2e 100644
--- a/syntax/attrs.rs
+++ b/syntax/attrs.rs
@@ -1,7 +1,7 @@
 use crate::syntax::namespace::Namespace;
 use crate::syntax::report::Errors;
 use crate::syntax::Atom::{self, *};
-use crate::syntax::{Derive, Doc};
+use crate::syntax::{Derive, Doc, ForeignName};
 use proc_macro2::{Ident, TokenStream};
 use quote::ToTokens;
 use syn::parse::{ParseStream, Parser as _};
@@ -31,7 +31,7 @@
     pub derives: Option<&'a mut Vec<Derive>>,
     pub repr: Option<&'a mut Option<Atom>>,
     pub namespace: Option<&'a mut Namespace>,
-    pub cxx_name: Option<&'a mut Option<Ident>>,
+    pub cxx_name: Option<&'a mut Option<ForeignName>>,
     pub rust_name: Option<&'a mut Option<Ident>>,
 
     // Suppress clippy needless_update lint ("struct update has no effect, all
@@ -96,7 +96,7 @@
                 }
             }
         } else if attr.path.is_ident("cxx_name") {
-            match parse_function_alias_attribute.parse2(attr.tokens.clone()) {
+            match parse_cxx_name_attribute.parse2(attr.tokens.clone()) {
                 Ok(attr) => {
                     if let Some(cxx_name) = &mut parser.cxx_name {
                         **cxx_name = Some(attr);
@@ -109,7 +109,7 @@
                 }
             }
         } else if attr.path.is_ident("rust_name") {
-            match parse_function_alias_attribute.parse2(attr.tokens.clone()) {
+            match parse_rust_name_attribute.parse2(attr.tokens.clone()) {
                 Ok(attr) => {
                     if let Some(rust_name) = &mut parser.rust_name {
                         **rust_name = Some(attr);
@@ -192,7 +192,18 @@
     Ok(namespace)
 }
 
-fn parse_function_alias_attribute(input: ParseStream) -> Result<Ident> {
+fn parse_cxx_name_attribute(input: ParseStream) -> Result<ForeignName> {
+    input.parse::<Token![=]>()?;
+    if input.peek(LitStr) {
+        let lit: LitStr = input.parse()?;
+        ForeignName::parse(&lit.value(), lit.span())
+    } else {
+        let ident: Ident = input.parse()?;
+        ForeignName::parse(&ident.to_string(), ident.span())
+    }
+}
+
+fn parse_rust_name_attribute(input: ParseStream) -> Result<Ident> {
     input.parse::<Token![=]>()?;
     if input.peek(LitStr) {
         let lit: LitStr = input.parse()?;
diff --git a/syntax/ident.rs b/syntax/ident.rs
index 78750dc..bb2281e 100644
--- a/syntax/ident.rs
+++ b/syntax/ident.rs
@@ -1,27 +1,24 @@
 use crate::syntax::check::Check;
 use crate::syntax::{error, Api, Pair};
-use proc_macro2::Ident;
 
 fn check(cx: &mut Check, name: &Pair) {
     for segment in &name.namespace {
-        check_cxx_ident(cx, segment);
+        check_cxx_ident(cx, &segment.to_string());
     }
-    check_cxx_ident(cx, &name.cxx);
-    check_rust_ident(cx, &name.rust);
+    check_cxx_ident(cx, &name.cxx.to_string());
+    check_rust_ident(cx, &name.rust.to_string());
 
-    fn check_cxx_ident(cx: &mut Check, ident: &Ident) {
-        let s = ident.to_string();
-        if s.starts_with("cxxbridge") {
+    fn check_cxx_ident(cx: &mut Check, ident: &str) {
+        if ident.starts_with("cxxbridge") {
             cx.error(ident, error::CXXBRIDGE_RESERVED.msg);
         }
-        if s.contains("__") {
+        if ident.contains("__") {
             cx.error(ident, error::DOUBLE_UNDERSCORE.msg);
         }
     }
 
-    fn check_rust_ident(cx: &mut Check, ident: &Ident) {
-        let s = ident.to_string();
-        if s.starts_with("cxxbridge") {
+    fn check_rust_ident(cx: &mut Check, ident: &str) {
+        if ident.starts_with("cxxbridge") {
             cx.error(ident, error::CXXBRIDGE_RESERVED.msg);
         }
     }
diff --git a/syntax/mod.rs b/syntax/mod.rs
index 9b4a101..57ed4fc 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -41,6 +41,7 @@
 pub use self::atom::Atom;
 pub use self::derive::{Derive, Trait};
 pub use self::doc::Doc;
+pub use self::names::ForeignName;
 pub use self::parse::parse_items;
 pub use self::types::Types;
 
@@ -252,7 +253,7 @@
 #[derive(Clone)]
 pub struct Pair {
     pub namespace: Namespace,
-    pub cxx: Ident,
+    pub cxx: ForeignName,
     pub rust: Ident,
 }
 
diff --git a/syntax/names.rs b/syntax/names.rs
index c4cbcff..b2ae013 100644
--- a/syntax/names.rs
+++ b/syntax/names.rs
@@ -1,25 +1,37 @@
+use crate::syntax::symbol::Segment;
 use crate::syntax::{Lifetimes, NamedType, Pair, Symbol};
 use proc_macro2::{Ident, Span};
+use std::fmt::{self, Display};
 use std::iter;
+use syn::parse::Result;
 use syn::punctuated::Punctuated;
 
+#[derive(Clone)]
+pub struct ForeignName {
+    text: String,
+    span: Span,
+}
+
 impl Pair {
     pub fn to_symbol(&self) -> Symbol {
-        Symbol::from_idents(self.iter_all_segments())
+        let segments = self
+            .namespace
+            .iter()
+            .map(|ident| ident as &dyn Segment)
+            .chain(iter::once(&self.cxx as &dyn Segment));
+        Symbol::from_idents(segments)
     }
 
     pub fn to_fully_qualified(&self) -> String {
         let mut fully_qualified = String::new();
-        for segment in self.iter_all_segments() {
+        for segment in &self.namespace {
             fully_qualified += "::";
             fully_qualified += &segment.to_string();
         }
+        fully_qualified += "::";
+        fully_qualified += &self.cxx.to_string();
         fully_qualified
     }
-
-    fn iter_all_segments(&self) -> impl Iterator<Item = &Ident> {
-        self.namespace.iter().chain(iter::once(&self.cxx))
-    }
 }
 
 impl NamedType {
@@ -36,3 +48,19 @@
         self.rust.span()
     }
 }
+
+impl ForeignName {
+    pub fn parse(text: &str, span: Span) -> Result<Self> {
+        // TODO: support C++ names containing whitespace (`unsigned int`) or
+        // non-alphanumeric characters (`operator++`).
+        let ident: Ident = syn::parse_str(text)?;
+        let text = ident.to_string();
+        Ok(ForeignName { text, span })
+    }
+}
+
+impl Display for ForeignName {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(&self.text)
+    }
+}
diff --git a/syntax/parse.rs b/syntax/parse.rs
index 3d2c638..795a518 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -4,9 +4,9 @@
 use crate::syntax::report::Errors;
 use crate::syntax::Atom::*;
 use crate::syntax::{
-    attrs, error, Api, Array, Derive, Doc, Enum, ExternFn, ExternType, Impl, Include, IncludeKind,
-    Lang, Lifetimes, NamedType, Namespace, Pair, Receiver, Ref, Signature, SliceRef, Struct, Ty1,
-    Type, TypeAlias, Var, Variant,
+    attrs, error, Api, Array, Derive, Doc, Enum, ExternFn, ExternType, ForeignName, Impl, Include,
+    IncludeKind, Lang, Lifetimes, NamedType, Namespace, Pair, Receiver, Ref, Signature, SliceRef,
+    Struct, Ty1, Type, TypeAlias, Var, Variant,
 };
 use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
 use quote::{format_ident, quote, quote_spanned};
@@ -1258,11 +1258,16 @@
     })
 }
 
-fn pair(namespace: Namespace, default: &Ident, cxx: Option<Ident>, rust: Option<Ident>) -> Pair {
-    let default = || default.clone();
+fn pair(
+    namespace: Namespace,
+    default: &Ident,
+    cxx: Option<ForeignName>,
+    rust: Option<Ident>,
+) -> Pair {
     Pair {
         namespace,
-        cxx: cxx.unwrap_or_else(default),
-        rust: rust.unwrap_or_else(default),
+        cxx: cxx
+            .unwrap_or_else(|| ForeignName::parse(&default.to_string(), default.span()).unwrap()),
+        rust: rust.unwrap_or_else(|| default.clone()),
     }
 }
diff --git a/syntax/symbol.rs b/syntax/symbol.rs
index 9a0a4a1..a13a4f3 100644
--- a/syntax/symbol.rs
+++ b/syntax/symbol.rs
@@ -1,5 +1,5 @@
 use crate::syntax::namespace::Namespace;
-use crate::syntax::Pair;
+use crate::syntax::{ForeignName, Pair};
 use proc_macro2::{Ident, TokenStream};
 use quote::ToTokens;
 use std::fmt::{self, Display, Write};
@@ -30,7 +30,7 @@
         assert!(self.0.len() > len_before);
     }
 
-    pub fn from_idents<'a, T: Iterator<Item = &'a Ident>>(it: T) -> Self {
+    pub fn from_idents<'a>(it: impl Iterator<Item = &'a dyn Segment>) -> Self {
         let mut symbol = Symbol(String::new());
         for segment in it {
             segment.write(&mut symbol);
@@ -55,16 +55,19 @@
         symbol.push(&self);
     }
 }
+
 impl Segment for usize {
     fn write(&self, symbol: &mut Symbol) {
         symbol.push(&self);
     }
 }
+
 impl Segment for Ident {
     fn write(&self, symbol: &mut Symbol) {
         symbol.push(&self);
     }
 }
+
 impl Segment for Symbol {
     fn write(&self, symbol: &mut Symbol) {
         symbol.push(&self);
@@ -86,6 +89,14 @@
     }
 }
 
+impl Segment for ForeignName {
+    fn write(&self, symbol: &mut Symbol) {
+        // TODO: support C++ names containing whitespace (`unsigned int`) or
+        // non-alphanumeric characters (`operator++`).
+        self.to_string().write(symbol);
+    }
+}
+
 impl<T> Segment for &'_ T
 where
     T: ?Sized + Segment + Display,