Centralize mangled symbol joining
diff --git a/syntax/symbol.rs b/syntax/symbol.rs
new file mode 100644
index 0000000..a40baaf
--- /dev/null
+++ b/syntax/symbol.rs
@@ -0,0 +1,66 @@
+use crate::syntax::namespace::Namespace;
+use proc_macro2::{Ident, TokenStream};
+use quote::ToTokens;
+use std::fmt::{self, Display, Write};
+
+// A mangled symbol consisting of segments separated by '$'.
+// For example: cxxbridge02$string$new
+pub struct Symbol(String);
+
+impl Display for Symbol {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        Display::fmt(&self.0, formatter)
+    }
+}
+
+impl ToTokens for Symbol {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        ToTokens::to_tokens(&self.0, tokens);
+    }
+}
+
+impl Symbol {
+    fn push(&mut self, segment: &dyn Display) {
+        let len_before = self.0.len();
+        if !self.0.is_empty() {
+            self.0.push('$');
+        }
+        self.0.write_fmt(format_args!("{}", segment)).unwrap();
+        assert!(self.0.len() > len_before);
+    }
+}
+
+pub trait Segment: Display {
+    fn write(&self, symbol: &mut Symbol) {
+        symbol.push(&self);
+    }
+}
+
+impl Segment for str {}
+impl Segment for Ident {}
+
+impl Segment for Namespace {
+    fn write(&self, symbol: &mut Symbol) {
+        for segment in self {
+            symbol.push(segment);
+        }
+    }
+}
+
+impl<T> Segment for &'_ T
+where
+    T: ?Sized + Segment,
+{
+    fn write(&self, symbol: &mut Symbol) {
+        (**self).write(symbol);
+    }
+}
+
+pub fn join(segments: &[&dyn Segment]) -> Symbol {
+    let mut symbol = Symbol(String::new());
+    for segment in segments {
+        segment.write(&mut symbol);
+    }
+    assert!(!symbol.0.is_empty());
+    symbol
+}