Split `Type` in half: separate `RsType` and `CcType`.

This is, I think, adequately motivated by the sheer impossibility of the original design working. Consider the following compound data type:

`int*` in C++ may be `Option<&i32>` in Rust.

This cannot be expressed in the old `Type` struct because it assumed the generic layout was the same: it assumed that if the C++ type was e.g. `*<int>`, then the rust type would be shaped like `a<b>` for some type names `a` and `b`. But in this case, `*<int>` becomes `Option<&<i32>>`. And anyway, `&` takes a lifetime parameter, so this is actually `Option<&<'_, i32>>`. Just not shaped the same at all!

There are many more cases beside. Here's an incomplete list of possible type maps which are incompatible with the old layout:

* `const char*` becomes `CStr`
* function pointers AND functions -- that is, `Identity<void()>` and `Identity<void()>*` -- both can become non-pointer functions in Rust: `extern "C" fn()->()`.
* `std::span<char, std::dynamic_extent>` (2 params) becomes `&mut [u8]` (1 param)

We should change this to do the right thing now, early on, rather than later when it will be even more annoying. :)

This is, in cool news, our first major divergence from [Clif's AST proto](http://google3/third_party/clif/protos/ast.proto;l=156-171;rcl=397117954).

PiperOrigin-RevId: 399622062
diff --git a/rs_bindings_from_cc/ast_visitor.cc b/rs_bindings_from_cc/ast_visitor.cc
index 71b45e5..0659bd8 100644
--- a/rs_bindings_from_cc/ast_visitor.cc
+++ b/rs_bindings_from_cc/ast_visitor.cc
@@ -123,40 +123,40 @@
   return true;
 }
 
-absl::StatusOr<Type> AstVisitor::ConvertType(
+absl::StatusOr<MappedType> AstVisitor::ConvertType(
     clang::QualType qual_type, const clang::ASTContext& ctx) const {
-  std::optional<Type> type = std::nullopt;
+  std::optional<MappedType> type = std::nullopt;
   std::string type_string = qual_type.getAsString();
 
   if (const clang::PointerType* pointer_type =
           qual_type->getAs<clang::PointerType>()) {
     auto pointee_type = ConvertType(pointer_type->getPointeeType(), ctx);
     if (pointee_type.ok()) {
-      type = Type::PointerTo(*pointee_type);
+      type = MappedType::PointerTo(*pointee_type);
     }
   } else if (const clang::BuiltinType* builtin_type =
                  qual_type->getAs<clang::BuiltinType>()) {
     switch (builtin_type->getKind()) {
       case clang::BuiltinType::Bool:
-        type = {"bool", "bool"};
+        type = MappedType::Simple("bool", "bool");
         break;
       case clang::BuiltinType::Float:
-        type = {"float", "float"};
+        type = MappedType::Simple("float", "float");
         break;
       case clang::BuiltinType::Double:
-        type = {"double", "double"};
+        type = MappedType::Simple("double", "double");
         break;
       case clang::BuiltinType::Void:
-        type = Type::Void();
+        type = MappedType::Void();
         break;
       default:
         if (builtin_type->isIntegerType()) {
           auto size = ctx.getTypeSize(builtin_type);
           if (size == 8 || size == 16 || size == 32 || size == 64) {
-            type = Type{
+            type = MappedType::Simple(
                 absl::Substitute(
                     "$0$1", builtin_type->isSignedInteger() ? 'i' : 'u', size),
-                type_string};
+                type_string);
           }
         }
     }
@@ -170,7 +170,7 @@
   }
 
   // Add cv-qualification.
-  type->cc_const = qual_type.isConstQualified();
+  type->cc_type.is_const = qual_type.isConstQualified();
   // Not doing volatile for now -- note that volatile pointers do not exist in
   // Rust, though volatile reads/writes still do.
 
diff --git a/rs_bindings_from_cc/ast_visitor.h b/rs_bindings_from_cc/ast_visitor.h
index e08d504..b7a1fba 100644
--- a/rs_bindings_from_cc/ast_visitor.h
+++ b/rs_bindings_from_cc/ast_visitor.h
@@ -44,8 +44,8 @@
  private:
   std::string GetMangledName(const clang::NamedDecl* named_decl) const;
   Identifier GetTranslatedName(const clang::NamedDecl* named_decl) const;
-  absl::StatusOr<Type> ConvertType(clang::QualType qual_type,
-                                   const clang::ASTContext& ctx) const;
+  absl::StatusOr<MappedType> ConvertType(clang::QualType qual_type,
+                                         const clang::ASTContext& ctx) const;
 
   absl::Span<const absl::string_view> public_header_names_;
   IR& ir_;
diff --git a/rs_bindings_from_cc/ast_visitor_test.cc b/rs_bindings_from_cc/ast_visitor_test.cc
index 3d4210e..fda3f9c 100644
--- a/rs_bindings_from_cc/ast_visitor_test.cc
+++ b/rs_bindings_from_cc/ast_visitor_test.cc
@@ -65,40 +65,70 @@
   return testing::Field(&FuncParam::type, AllOf(matchers...));
 }
 
-// Matches a Type that has the given Rust name.
-MATCHER_P(RsNameIs, rs_name, "") {
-  if (arg.rs_name == rs_name) return true;
+// Matches a RsType or CcType that has the given name.
+MATCHER_P(NameIs, name, "") {
+  if (arg.name == name) return true;
 
-  *result_listener << "actual rs_name: '" << arg.rs_name << "'";
+  *result_listener << "actual name: '" << arg.name << "'";
   return false;
 }
 
-// Matches a Type that has the given C++ name.
-MATCHER_P(CcNameIs, cc_name, "") {
-  if (arg.cc_name == cc_name) return true;
-
-  *result_listener << "actual cc_name: '" << arg.cc_name << "'";
-  return false;
-}
-
-// Matches a type that has type parameters matching `matchers`.
+// Matches a MappedType with a CcType that matches all given matchers.
 template <typename... Args>
-auto TypeParamsAre(const Args&... matchers) {
-  return testing::Field(&Type::type_params, ElementsAre(matchers...));
+auto CcTypeIs(const Args&... matchers) {
+  return testing::Field(&MappedType::cc_type, AllOf(matchers...));
 }
 
-// Matches a type that is void.
+// Matches a MappedType with a RsType that matches all given matchers.
+template <typename... Args>
+auto RsTypeIs(const Args&... matchers) {
+  return testing::Field(&MappedType::rs_type, AllOf(matchers...));
+}
+
+// Matches an RsType that has Rust type parameters matching `matchers`.
+template <typename... Args>
+auto RsTypeParamsAre(const Args&... matchers) {
+  return testing::Field(&RsType::type_params, ElementsAre(matchers...));
+}
+
+// Matches a CcType that has Rust type parameters matching `matchers`.
+template <typename... Args>
+auto CcTypeParamsAre(const Args&... matchers) {
+  return testing::Field(&CcType::type_params, ElementsAre(matchers...));
+}
+
+auto IsCcInt() { return AllOf(NameIs("int"), CcTypeParamsAre()); }
+
+auto IsRsInt() { return AllOf(NameIs("i32"), RsTypeParamsAre()); }
+
+// Matches a CcType that is a pointer to a type matching `matcher`.
+template <typename Matcher>
+auto CcPointsTo(const Matcher& matcher) {
+  return AllOf(NameIs("*"), CcTypeParamsAre(matcher));
+}
+
+// Matches an RsType that is a pointer to a type matching `matcher`.
+template <typename Matcher>
+auto RsPointsTo(const Matcher& matcher) {
+  return AllOf(NameIs("*mut"), RsTypeParamsAre(matcher));
+}
+
+// Matches a MappedType that is void.
 MATCHER(IsVoid, "") { return arg.IsVoid(); }
 
-// Matches an integer type.
-auto IsInt() {
-  return AllOf(RsNameIs("i32"), CcNameIs("int"), TypeParamsAre());
+// Matches a MappedType that is an integer.
+auto IsInt() { return AllOf(CcTypeIs(IsCcInt()), RsTypeIs(IsRsInt())); }
+
+// Matches a MappedType that is a pointer to integer.
+auto IsIntPtr() {
+  return AllOf(CcTypeIs(CcPointsTo(IsCcInt())),
+               RsTypeIs(RsPointsTo(IsRsInt())));
 }
 
-// Matches a type that is a pointer to a type matching `matcher`.
-template <typename Matcher>
-auto PointsTo(const Matcher& matcher) {
-  return AllOf(RsNameIs("*mut"), CcNameIs("*"), TypeParamsAre(matcher));
+// Matches a MappedType for cc and rs types with no type parameters.
+auto IsSimpleType(absl::string_view rs_name, absl::string_view cc_name) {
+  return AllOf(CcTypeIs(NameIs(cc_name), CcTypeParamsAre()),
+               RsTypeIs(NameIs(rs_name), RsTypeParamsAre()));
 }
 
 // Matches a Record that has fields matching `matchers`.
@@ -242,8 +272,8 @@
   IR ir = ImportCode({"int* Foo(int* a);"}, {});
 
   EXPECT_THAT(ir.functions,
-              ElementsAre(AllOf(ReturnType(PointsTo(IsInt())),
-                                ParamsAre(ParamType(PointsTo(IsInt()))))));
+              ElementsAre(AllOf(ReturnType(IsIntPtr()),
+                                ParamsAre(ParamType(IsIntPtr())))));
 }
 
 TEST(AstVisitorTest, Struct) {
@@ -340,44 +370,44 @@
   EXPECT_THAT(
       ir.records,
       ElementsAre(FieldsAre(
-          FieldType(RsNameIs("bool"), CcNameIs("bool")),
+          FieldType(IsSimpleType("bool", "bool")),
 
-          FieldType(RsNameIs("i8"), CcNameIs("char")),
-          FieldType(RsNameIs("u8"), CcNameIs("unsigned char")),
-          FieldType(RsNameIs("i8"), CcNameIs("signed char")),
-          FieldType(RsNameIs("u16"), CcNameIs("char16_t")),
+          FieldType(IsSimpleType("i8", "char")),
+          FieldType(IsSimpleType("u8", "unsigned char")),
+          FieldType(IsSimpleType("i8", "signed char")),
+          FieldType(IsSimpleType("u16", "char16_t")),
           // We cannot map C++ char32_t or wchar_t to Rust char,
           // because Rust requires that chars are valid UTF scalar values.
-          FieldType(RsNameIs("u32"), CcNameIs("char32_t")),
-          FieldType(RsNameIs("i32"), CcNameIs("wchar_t")),
+          FieldType(IsSimpleType("u32", "char32_t")),
+          FieldType(IsSimpleType("i32", "wchar_t")),
 
-          FieldType(RsNameIs("i16"), CcNameIs("short")),
-          FieldType(RsNameIs("i32"), CcNameIs("int")),
-          FieldType(RsNameIs("i64"), CcNameIs("long")),
-          FieldType(RsNameIs("i64"), CcNameIs("long long")),
+          FieldType(IsSimpleType("i16", "short")),
+          FieldType(IsSimpleType("i32", "int")),
+          FieldType(IsSimpleType("i64", "long")),
+          FieldType(IsSimpleType("i64", "long long")),
 
-          FieldType(RsNameIs("u16"), CcNameIs("unsigned short")),
-          FieldType(RsNameIs("u32"), CcNameIs("unsigned int")),
-          FieldType(RsNameIs("u64"), CcNameIs("unsigned long")),
-          FieldType(RsNameIs("u64"), CcNameIs("unsigned long long")),
+          FieldType(IsSimpleType("u16", "unsigned short")),
+          FieldType(IsSimpleType("u32", "unsigned int")),
+          FieldType(IsSimpleType("u64", "unsigned long")),
+          FieldType(IsSimpleType("u64", "unsigned long long")),
 
-          FieldType(RsNameIs("i16"), CcNameIs("short")),
-          FieldType(RsNameIs("i32"), CcNameIs("int")),
-          FieldType(RsNameIs("i64"), CcNameIs("long")),
-          FieldType(RsNameIs("i64"), CcNameIs("long long")),
+          FieldType(IsSimpleType("i16", "short")),
+          FieldType(IsSimpleType("i32", "int")),
+          FieldType(IsSimpleType("i64", "long")),
+          FieldType(IsSimpleType("i64", "long long")),
 
-          FieldType(RsNameIs("i8"), CcNameIs("int8_t")),
-          FieldType(RsNameIs("i16"), CcNameIs("int16_t")),
-          FieldType(RsNameIs("i32"), CcNameIs("int32_t")),
-          FieldType(RsNameIs("i64"), CcNameIs("int64_t")),
+          FieldType(IsSimpleType("i8", "int8_t")),
+          FieldType(IsSimpleType("i16", "int16_t")),
+          FieldType(IsSimpleType("i32", "int32_t")),
+          FieldType(IsSimpleType("i64", "int64_t")),
 
-          FieldType(RsNameIs("u8"), CcNameIs("uint8_t")),
-          FieldType(RsNameIs("u16"), CcNameIs("uint16_t")),
-          FieldType(RsNameIs("u32"), CcNameIs("uint32_t")),
-          FieldType(RsNameIs("u64"), CcNameIs("uint64_t")),
+          FieldType(IsSimpleType("u8", "uint8_t")),
+          FieldType(IsSimpleType("u16", "uint16_t")),
+          FieldType(IsSimpleType("u32", "uint32_t")),
+          FieldType(IsSimpleType("u64", "uint64_t")),
 
-          FieldType(RsNameIs("float"), CcNameIs("float")),
-          FieldType(RsNameIs("double"), CcNameIs("double")))));
+          FieldType(IsSimpleType("float", "float")),
+          FieldType(IsSimpleType("double", "double")))));
 }
 
 }  // namespace
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index f1c4a97..92c5f4f 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -17,22 +17,44 @@
   return result;
 }
 
-nlohmann::json Type::ToJson() const {
+nlohmann::json RsType::ToJson() const {
   nlohmann::json result;
 
   std::vector<nlohmann::json> json_params;
   json_params.reserve(type_params.size());
-  for (const Type& param : type_params) {
+  for (const RsType& param : type_params) {
     json_params.push_back(param.ToJson());
   }
-  result["rs_name"] = rs_name;
-  result["cc_name"] = cc_name;
-  result["cc_const"] = cc_const;
+  result["name"] = name;
   result["type_params"] = std::move(json_params);
 
   return result;
 }
 
+nlohmann::json CcType::ToJson() const {
+  nlohmann::json result;
+
+  std::vector<nlohmann::json> json_params;
+  json_params.reserve(type_params.size());
+  for (const CcType& param : type_params) {
+    json_params.push_back(param.ToJson());
+  }
+  result["name"] = name;
+  result["is_const"] = is_const;
+  result["type_params"] = std::move(json_params);
+
+  return result;
+}
+
+nlohmann::json MappedType::ToJson() const {
+  nlohmann::json result;
+
+  result["rs_type"] = rs_type.ToJson();
+  result["cc_type"] = cc_type.ToJson();
+
+  return result;
+}
+
 nlohmann::json Identifier::ToJson() const {
   nlohmann::json result;
   result["identifier"] = identifier_;
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index a87be71..a670deb 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -43,33 +43,12 @@
   std::string name_;
 };
 
-// A type involved in the bindings. It has the knowledge about how the type is
-// spelled in Rust and in C++ code.
-//
-// Examples:
-//     C++'s `int32_t` will be `Type{"i32", "int"}`.
-//     C++'s `struct Foo` will be `Type{"Foo", "Foo"}`.
-//     C++'s `int*` will be `Type{"*mut", "*", {Type{"i32", "int"}}}
-struct Type {
-  static Type Void() { return Type{"()", "void"}; }
-  bool IsVoid() const { return rs_name == "()"; }
-
-  static Type PointerTo(Type pointee_type) {
-    absl::string_view rs_name =
-        pointee_type.cc_const ? internal::kRustPtrConst : internal::kRustPtrMut;
-    auto pointer_type = Type{.rs_name = std::string(rs_name),
-                             .cc_name = std::string(internal::kCcPtr)};
-    pointer_type.type_params.push_back(std::move(pointee_type));
-    return pointer_type;
-  }
-
+// A C++ type involved in the bindings. It has the knowledge of how the type
+// is spelled in C++.
+struct CcType {
   nlohmann::json ToJson() const;
-
-  // The rust name of the type. For example, i32 or ().
-  std::string rs_name;
-
-  // The C++ name for the type. For example, int or void.
-  std::string cc_name;
+  // The name of the type. For example, int or void.
+  std::string name;
 
   // The C++ const-qualification for the type.
   //
@@ -77,12 +56,62 @@
   // references and functions. if `T` is either a function type like `void()`,
   // or a reference type like `int&`, then `T`, `const T`, and `volatile T` are
   // all the same type in C++.
-  bool cc_const = false;
+  bool is_const = false;
 
   // Type parameters for a generic type. Examples:
+  //   int has no type parameters.
   //   int* has a single type parameter, int.
   //   tuple<int, float> has two type parameters, int and float.
-  std::vector<Type> type_params = {};
+  std::vector<CcType> type_params = {};
+};
+
+// A Rust type involved in the bindings. It has the knowledge of how the type
+// is spelled in Rust.
+struct RsType {
+  nlohmann::json ToJson() const;
+
+  // The name of the type. For example, i32 or ().
+  std::string name;
+
+  // Type parameters for a generic type. Examples:
+  //   i32 has no type parameters.
+  //   *mut i32 has a single type parameter, i32.
+  //   (i32, f32) has two type parameters, i32 and f32.
+  std::vector<RsType> type_params = {};
+};
+
+// A type involved in the bindings. The rs_type and cc_type will be treated
+// as interchangeable during bindings, and so should share the same layout.
+//
+// For example: a C++ pointer may be a usize in Rust, rather than a pointer, but
+// should almost certainly not be a u8, because u8 and pointers are sized and
+// aligned differently.
+struct MappedType {
+  static MappedType Void() { return Simple("()", "void"); }
+
+  /// Returns the MappedType for a non-templated/generic, non-cv-qualified type.
+  /// For example, Void() is Simple("()", "void").
+  static MappedType Simple(std::string rs_name, std::string cc_name) {
+    return MappedType{RsType{rs_name}, CcType{cc_name}};
+  }
+
+  static MappedType PointerTo(MappedType pointee_type) {
+    absl::string_view rs_name = pointee_type.cc_type.is_const
+                                    ? internal::kRustPtrConst
+                                    : internal::kRustPtrMut;
+    auto pointer_type =
+        Simple(std::string(rs_name), std::string(internal::kCcPtr));
+    pointer_type.rs_type.type_params.push_back(std::move(pointee_type.rs_type));
+    pointer_type.cc_type.type_params.push_back(std::move(pointee_type.cc_type));
+    return pointer_type;
+  }
+
+  bool IsVoid() const { return rs_type.name == "()"; }
+
+  nlohmann::json ToJson() const;
+
+  RsType rs_type;
+  CcType cc_type;
 };
 
 // An identifier involved in bindings.
@@ -116,7 +145,7 @@
 struct FuncParam {
   nlohmann::json ToJson() const;
 
-  Type type;
+  MappedType type;
   Identifier identifier;
 };
 
@@ -126,7 +155,7 @@
 
   Identifier identifier;
   std::string mangled_name;
-  Type return_type;
+  MappedType return_type;
   std::vector<FuncParam> params;
   bool is_inline;
 };
@@ -143,7 +172,7 @@
   nlohmann::json ToJson() const;
 
   Identifier identifier;
-  Type type;
+  MappedType type;
   AccessSpecifier access;
   // Field offset in bits.
   uint64_t offset;
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 8ee14f0..5e3334f 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -18,11 +18,22 @@
 }
 
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
-pub struct IRType {
-    pub rs_name: String,
-    pub cc_name: String,
-    pub cc_const: bool,
-    pub type_params: Vec<IRType>,
+pub struct RsType {
+    pub name: String,
+    pub type_params: Vec<RsType>,
+}
+
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
+pub struct CcType {
+    pub name: String,
+    pub is_const: bool,
+    pub type_params: Vec<CcType>,
+}
+
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
+pub struct MappedType {
+    pub rs_type: RsType,
+    pub cc_type: CcType,
 }
 
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
@@ -33,7 +44,7 @@
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 pub struct FuncParam {
     #[serde(rename(deserialize = "type"))]
-    pub type_: IRType,
+    pub type_: MappedType,
     pub identifier: Identifier,
 }
 
@@ -41,7 +52,7 @@
 pub struct Func {
     pub identifier: Identifier,
     pub mangled_name: String,
-    pub return_type: IRType,
+    pub return_type: MappedType,
     pub params: Vec<FuncParam>,
     pub is_inline: bool,
 }
@@ -57,7 +68,7 @@
 pub struct Field {
     pub identifier: Identifier,
     #[serde(rename(deserialize = "type"))]
-    pub type_: IRType,
+    pub type_: MappedType,
     pub access: AccessSpecifier,
     pub offset: i64,
 }
@@ -110,10 +121,16 @@
                     "params": [
                         {
                             "identifier": { "identifier": "arg" },
-                            "type": { "rs_name":"i32", "cc_name": "int", "cc_const": false, "type_params": [] }
+                            "type": {
+                                "rs_type": {"name": "i32", "type_params": []},
+                                "cc_type": {"name": "int", "is_const": false, "type_params": []}
+                            }
                         }
                     ],
-                    "return_type": { "rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
+                    "return_type": {
+                        "rs_type": {"name": "i32", "type_params": []},
+                        "cc_type": {"name": "int", "is_const": false, "type_params": []}
+                    },
                     "is_inline": false
                 }
             ]
@@ -124,18 +141,22 @@
             functions: vec![Func {
                 identifier: Identifier { identifier: "hello_world".to_string() },
                 mangled_name: "$$mangled_name$$".to_string(),
-                return_type: IRType {
-                    rs_name: "i32".to_string(),
-                    cc_name: "int".to_string(),
-                    cc_const: false,
-                    type_params: vec![],
+                return_type: MappedType {
+                    rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                    cc_type: CcType {
+                        name: "int".to_string(),
+                        is_const: false,
+                        type_params: vec![],
+                    },
                 },
                 params: vec![FuncParam {
-                    type_: IRType {
-                        rs_name: "i32".to_string(),
-                        cc_name: "int".to_string(),
-                        cc_const: false,
-                        type_params: vec![],
+                    type_: MappedType {
+                        rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                        cc_type: CcType {
+                            name: "int".to_string(),
+                            is_const: false,
+                            type_params: vec![],
+                        },
                     },
                     identifier: Identifier { identifier: "arg".to_string() },
                 }],
@@ -156,19 +177,28 @@
                     "fields": [
                         {
                             "identifier": {"identifier": "public_int" },
-                            "type": {"rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
+                            "type": {
+                                "rs_type": {"name": "i32", "type_params": []},
+                                "cc_type": {"name": "int", "is_const": false, "type_params": []}
+                            },
                             "access": "Public",
                             "offset": 0
                         },
                         {
                             "identifier": {"identifier": "protected_int" },
-                            "type": {"rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
+                            "type": {
+                                "rs_type": {"name": "i32", "type_params": []},
+                                "cc_type": {"name": "int", "is_const": false, "type_params": []}
+                            },
                             "access": "Protected",
                             "offset": 32
                         },
                         {
                             "identifier": {"identifier": "private_int" },
-                            "type": {"rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
+                            "type": {
+                                "rs_type": {"name": "i32", "type_params": []},
+                                "cc_type": {"name": "int", "is_const": false, "type_params": []}
+                            },
                             "access": "Private",
                             "offset": 64
                         }
@@ -186,33 +216,39 @@
                 fields: vec![
                     Field {
                         identifier: Identifier { identifier: "public_int".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                         access: AccessSpecifier::Public,
                         offset: 0,
                     },
                     Field {
                         identifier: Identifier { identifier: "protected_int".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                         access: AccessSpecifier::Protected,
                         offset: 32,
                     },
                     Field {
                         identifier: Identifier { identifier: "private_int".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                         access: AccessSpecifier::Private,
                         offset: 64,
@@ -236,9 +272,14 @@
                     "fields": [
                         {
                             "identifier": {"identifier": "ptr" },
-                            "type": {"rs_name": "*mut", "cc_name": "*", "cc_const": false, "type_params": [
-                                {"rs_name": "SomeStruct", "cc_name": "SomeStruct", "cc_const": false, "type_params": []}
-                            ] },
+                            "type": {
+                                "rs_type": {"name": "*mut", "type_params": [
+                                    {"name": "SomeStruct", "type_params": []}
+                                ]},
+                                "cc_type": { "name": "*", "is_const": false, "type_params": [
+                                    {"name": "SomeStruct", "is_const": false, "type_params": []}
+                                ]}
+                            },
                             "access": "Public",
                             "offset": 0
                         }
@@ -255,16 +296,23 @@
                 identifier: Identifier { identifier: "SomeStruct".to_string() },
                 fields: vec![Field {
                     identifier: Identifier { identifier: "ptr".to_string() },
-                    type_: IRType {
-                        rs_name: "*mut".to_string(),
-                        cc_name: "*".to_string(),
-                        cc_const: false,
-                        type_params: vec![IRType {
-                            rs_name: "SomeStruct".to_string(),
-                            cc_name: "SomeStruct".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
-                        }],
+                    type_: MappedType {
+                        rs_type: RsType {
+                            name: "*mut".to_string(),
+                            type_params: vec![RsType {
+                                name: "SomeStruct".to_string(),
+                                type_params: vec![],
+                            }],
+                        },
+                        cc_type: CcType {
+                            name: "*".to_string(),
+                            is_const: false,
+                            type_params: vec![CcType {
+                                name: "SomeStruct".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            }],
+                        },
                     },
                     access: AccessSpecifier::Public,
                     offset: 0,
diff --git a/rs_bindings_from_cc/ir_test.cc b/rs_bindings_from_cc/ir_test.cc
index aa8a255..025357f 100644
--- a/rs_bindings_from_cc/ir_test.cc
+++ b/rs_bindings_from_cc/ir_test.cc
@@ -15,16 +15,22 @@
 
 TEST(IrTest, TypeToJson) {
   nlohmann::json expected = nlohmann::json::parse(R"j({
-      "rs_name": "CompoundRs",
-      "cc_name": "CompoundCc",
-      "cc_const": false,
-      "type_params": [
-          { "rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": []}
-      ]
+      "rs_type": {
+          "name": "CompoundRs",
+          "type_params": [{"name": "i32", "type_params": []}]
+      },
+      "cc_type": {
+          "name": "CompoundCc",
+          "is_const": false,
+          "type_params": [
+              {"is_const": false, "name": "int", "type_params": []}
+          ]
+      }
   })j");
-  auto type = Type{.rs_name = "CompoundRs",
-                   .cc_name = "CompoundCc",
-                   .type_params = {Type{"i32", "int"}}};
+  auto type = MappedType{.rs_type = RsType{"CompoundRs", {RsType{"i32"}}},
+                         .cc_type = CcType{.name = "CompoundCc",
+                                           .is_const = false,
+                                           .type_params = {CcType{"int"}}}};
   EXPECT_EQ(type.ToJson(), expected);
 }
 
@@ -33,36 +39,69 @@
       R"j({
             "used_headers": [{ "name": "foo/bar.h" }],
             "functions": [{
-              "identifier": { "identifier": "hello_world" },
-              "mangled_name": "#$mangled_name$#",
-              "return_type": { "rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
-              "params": [
-                {
-                  "identifier": {"identifier": "arg" },
-                  "type": { "rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] }
-                }
-              ],
-              "is_inline": false
+                "identifier": { "identifier": "hello_world" },
+                "mangled_name": "#$mangled_name$#",
+                "return_type": {
+                    "rs_type": { "name": "i32", "type_params": [] },
+                    "cc_type": {
+                        "is_const": false,
+                        "name": "int",
+                        "type_params": []
+                    }
+                },
+                "params": [{
+                    "identifier": { "identifier": "arg" },
+                    "type": {
+                        "rs_type": { "name": "i32", "type_params": [] },
+                        "cc_type": {
+                            "is_const": false,
+                            "name": "int",
+                            "type_params": []
+                        }
+                    }
+                }],
+                "is_inline": false
             }],
             "records": [
               {
-                "identifier": {"identifier": "SomeStruct" },
+                "identifier": { "identifier": "SomeStruct" },
                 "fields": [
                   {
-                    "identifier": {"identifier": "public_int" },
-                    "type": {"rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
+                    "identifier": { "identifier": "public_int" },
+                    "type": {
+                        "rs_type": { "name": "i32", "type_params": [] },
+                        "cc_type": {
+                            "is_const": false,
+                            "name": "int",
+                            "type_params": []
+                        }
+                    },
                     "access": "Public",
                     "offset": 0
                   },
                   {
-                    "identifier": {"identifier": "protected_int" },
-                    "type": {"rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
+                    "identifier": { "identifier": "protected_int" },
+                    "type": {
+                        "rs_type": { "name": "i32", "type_params": [] },
+                        "cc_type": {
+                            "is_const": false,
+                            "name": "int",
+                            "type_params": []
+                        }
+                    },
                     "access": "Protected",
                     "offset": 32
                   },
                   {
-                    "identifier": {"identifier": "private_int" },
-                    "type": {"rs_name": "i32", "cc_name": "int", "cc_const": false, "type_params": [] },
+                    "identifier": { "identifier": "private_int" },
+                    "type": {
+                        "rs_type": { "name": "i32", "type_params": [] },
+                        "cc_type": {
+                            "is_const": false,
+                            "name": "int",
+                            "type_params": []
+                        }
+                    },
                     "access": "Private",
                     "offset": 64
                   }
@@ -71,38 +110,34 @@
                 "alignment": 4
               }
             ]
-      })j");
-  IR
-      ir =
-          {
-              .used_headers = {HeaderName("foo/bar.h")},
-              .functions = {Func{
-                  .identifier = Identifier("hello_world"),
-                  .mangled_name = "#$mangled_name$#",
-                  .return_type = Type{"i32", "int"},
-                  .params = {FuncParam{Type{"i32", "int"}, Identifier("arg")}},
-                  .is_inline = false}},
-              .records = {Record{.identifier = Identifier("SomeStruct"),
-                                 .fields =
-                                     {
-                                         Field{.identifier =
-                                                   Identifier("public_int"),
-                                               .type = Type{"i32", "int"},
-                                               .access = kPublic,
-                                               .offset = 0},
-                                         Field{.identifier = Identifier(
-                                                   "protected_int"),
-                                               .type = Type{"i32", "int"},
-                                               .access = kProtected,
-                                               .offset = 32},
-                                         Field{.identifier = Identifier(
-                                                   "private_int"),
-                                               .type = Type{"i32", "int"},
-                                               .access = kPrivate,
-                                               .offset = 64},
-                                     },
-                                 .size = 12,
-                                 .alignment = 4}}};
+        })j");
+  IR ir = {
+      .used_headers = {HeaderName("foo/bar.h")},
+      .functions = {Func{.identifier = Identifier("hello_world"),
+                         .mangled_name = "#$mangled_name$#",
+                         .return_type = MappedType::Simple("i32", "int"),
+                         .params = {FuncParam{MappedType::Simple("i32", "int"),
+                                              Identifier("arg")}},
+                         .is_inline = false}},
+      .records = {Record{.identifier = Identifier("SomeStruct"),
+                         .fields =
+                             {
+                                 Field{.identifier = Identifier("public_int"),
+                                       .type = MappedType::Simple("i32", "int"),
+                                       .access = kPublic,
+                                       .offset = 0},
+                                 Field{
+                                     .identifier = Identifier("protected_int"),
+                                     .type = MappedType::Simple("i32", "int"),
+                                     .access = kProtected,
+                                     .offset = 32},
+                                 Field{.identifier = Identifier("private_int"),
+                                       .type = MappedType::Simple("i32", "int"),
+                                       .access = kPrivate,
+                                       .offset = 64},
+                             },
+                         .size = 12,
+                         .alignment = 4}}};
   EXPECT_EQ(ir.ToJson(), expected);
 }
 
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 20d9d14..b1ea084 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -86,8 +86,11 @@
     let ident = make_ident(&record.identifier.identifier);
     let field_idents =
         record.fields.iter().map(|f| make_ident(&f.identifier.identifier)).collect_vec();
-    let field_types =
-        record.fields.iter().map(|f| format_rs_type(&f.type_)).collect::<Result<Vec<_>>>()?;
+    let field_types = record
+        .fields
+        .iter()
+        .map(|f| format_rs_type(&f.type_.rs_type))
+        .collect::<Result<Vec<_>>>()?;
     let field_accesses = record
         .fields
         .iter()
@@ -120,13 +123,16 @@
         let ident = make_ident(&func.identifier.identifier);
         let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
         // TODO(hlopko): do not emit `-> ()` when return type is void, it's implicit.
-        let return_type_name = format_rs_type(&func.return_type)?;
+        let return_type_name = format_rs_type(&func.return_type.rs_type)?;
 
         let param_idents =
             func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
 
-        let param_types =
-            func.params.iter().map(|p| format_rs_type(&p.type_)).collect::<Result<Vec<_>>>()?;
+        let param_types = func
+            .params
+            .iter()
+            .map(|p| format_rs_type(&p.type_.rs_type))
+            .collect::<Result<Vec<_>>>()?;
 
         api_funcs.push(quote! {
             #[inline(always)]
@@ -185,8 +191,8 @@
     format_ident!("{}", ident)
 }
 
-fn format_rs_type(ty: &ir::IRType) -> Result<TokenStream> {
-    let ptr_fragment = match ty.rs_name.as_str() {
+fn format_rs_type(ty: &ir::RsType) -> Result<TokenStream> {
+    let ptr_fragment = match ty.name.as_str() {
         "*mut" => Some(quote! {*mut}),
         "*const" => Some(quote! {*const}),
         _ => None,
@@ -206,19 +212,19 @@
             if ty.type_params.len() > 0 {
                 return Err(anyhow!("Type not yet supported: {:?}", ty));
             }
-            let ident = make_ident(&ty.rs_name);
+            let ident = make_ident(&ty.name);
             Ok(quote! {#ident})
         }
     }
 }
 
-fn format_cc_type(ty: &ir::IRType) -> Result<TokenStream> {
-    let const_fragment = if ty.cc_const {
+fn format_cc_type(ty: &ir::CcType) -> Result<TokenStream> {
+    let const_fragment = if ty.is_const {
         quote! {const}
     } else {
         quote! {}
     };
-    match ty.cc_name.as_str() {
+    match ty.name.as_str() {
         "*" => {
             if ty.type_params.len() != 1 {
                 return Err(anyhow!(
@@ -255,13 +261,16 @@
 
         let thunk_ident = format_ident!("__rust_thunk__{}", &func.identifier.identifier);
         let ident = make_ident(&func.identifier.identifier);
-        let return_type_name = format_cc_type(&func.return_type)?;
+        let return_type_name = format_cc_type(&func.return_type.cc_type)?;
 
         let param_idents =
             func.params.iter().map(|p| make_ident(&p.identifier.identifier)).collect_vec();
 
-        let param_types =
-            func.params.iter().map(|p| format_cc_type(&p.type_)).collect::<Result<Vec<_>>>()?;
+        let param_types = func
+            .params
+            .iter()
+            .map(|p| format_cc_type(&p.type_.cc_type))
+            .collect::<Result<Vec<_>>>()?;
 
         thunks.push(quote! {
             extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) {
@@ -299,29 +308,35 @@
             functions: vec![Func {
                 identifier: Identifier { identifier: "add".to_string() },
                 mangled_name: "_Z3Addii".to_string(),
-                return_type: IRType {
-                    rs_name: "i32".to_string(),
-                    cc_name: "int".to_string(),
-                    cc_const: false,
-                    type_params: vec![],
+                return_type: MappedType {
+                    rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                    cc_type: CcType {
+                        name: "int".to_string(),
+                        is_const: false,
+                        type_params: vec![],
+                    },
                 },
                 params: vec![
                     FuncParam {
                         identifier: Identifier { identifier: "a".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                     },
                     FuncParam {
                         identifier: Identifier { identifier: "b".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                     },
                 ],
@@ -360,29 +375,35 @@
             functions: vec![Func {
                 identifier: Identifier { identifier: "add".to_string() },
                 mangled_name: "_Z3Addii".to_string(),
-                return_type: IRType {
-                    rs_name: "i32".to_string(),
-                    cc_name: "int".to_string(),
-                    cc_const: false,
-                    type_params: vec![],
+                return_type: MappedType {
+                    rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                    cc_type: CcType {
+                        name: "int".to_string(),
+                        is_const: false,
+                        type_params: vec![],
+                    },
                 },
                 params: vec![
                     FuncParam {
                         identifier: Identifier { identifier: "a".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                     },
                     FuncParam {
                         identifier: Identifier { identifier: "b".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                     },
                 ],
@@ -429,33 +450,39 @@
                 fields: vec![
                     Field {
                         identifier: Identifier { identifier: "public_int".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                         access: AccessSpecifier::Public,
                         offset: 0,
                     },
                     Field {
                         identifier: Identifier { identifier: "protected_int".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                         access: AccessSpecifier::Protected,
                         offset: 32,
                     },
                     Field {
                         identifier: Identifier { identifier: "private_int".to_string() },
-                        type_: IRType {
-                            rs_name: "i32".to_string(),
-                            cc_name: "int".to_string(),
-                            cc_const: false,
-                            type_params: vec![],
+                        type_: MappedType {
+                            rs_type: RsType { name: "i32".to_string(), type_params: vec![] },
+                            cc_type: CcType {
+                                name: "int".to_string(),
+                                is_const: false,
+                                type_params: vec![],
+                            },
                         },
                         access: AccessSpecifier::Private,
                         offset: 64,
@@ -495,34 +522,47 @@
             functions: vec![Func {
                 identifier: Identifier { identifier: "Deref".to_string() },
                 mangled_name: "_Z5DerefPKPi".to_string(),
-                return_type: IRType {
-                    rs_name: "*mut".to_string(),
-                    cc_name: "*".to_string(),
-                    cc_const: false,
-                    type_params: vec![IRType {
-                        rs_name: "i32".to_string(),
-                        cc_name: "int".to_string(),
-                        cc_const: false,
-                        type_params: vec![],
-                    }],
+                return_type: MappedType {
+                    rs_type: RsType {
+                        name: "*mut".to_string(),
+                        type_params: vec![RsType { name: "i32".to_string(), type_params: vec![] }],
+                    },
+                    cc_type: CcType {
+                        name: "*".to_string(),
+                        is_const: false,
+                        type_params: vec![CcType {
+                            name: "int".to_string(),
+                            is_const: false,
+                            type_params: vec![],
+                        }],
+                    },
                 },
                 params: vec![FuncParam {
                     identifier: Identifier { identifier: "p".to_string() },
-                    type_: IRType {
-                        rs_name: "*const".to_string(),
-                        cc_name: "*".to_string(),
-                        cc_const: false,
-                        type_params: vec![IRType {
-                            rs_name: "*mut".to_string(),
-                            cc_name: "*".to_string(),
-                            cc_const: true,
-                            type_params: vec![IRType {
-                                rs_name: "i32".to_string(),
-                                cc_name: "int".to_string(),
-                                cc_const: false,
-                                type_params: vec![],
+                    type_: MappedType {
+                        rs_type: RsType {
+                            name: "*const".to_string(),
+                            type_params: vec![RsType {
+                                name: "*mut".to_string(),
+                                type_params: vec![RsType {
+                                    name: "i32".to_string(),
+                                    type_params: vec![],
+                                }],
                             }],
-                        }],
+                        },
+                        cc_type: CcType {
+                            name: "*".to_string(),
+                            is_const: false,
+                            type_params: vec![CcType {
+                                name: "*".to_string(),
+                                is_const: true,
+                                type_params: vec![CcType {
+                                    name: "int".to_string(),
+                                    is_const: false,
+                                    type_params: vec![],
+                                }],
+                            }],
+                        },
                     },
                 }],
                 is_inline: true,
diff --git a/rs_bindings_from_cc/src_code_gen_test.cc b/rs_bindings_from_cc/src_code_gen_test.cc
index cd782f2..ff4a8a2 100644
--- a/rs_bindings_from_cc/src_code_gen_test.cc
+++ b/rs_bindings_from_cc/src_code_gen_test.cc
@@ -17,14 +17,15 @@
 using ::testing::StrEq;
 
 TEST(SrcGenTest, FFIIntegration) {
-  IR ir = {.used_headers = {HeaderName("foo/bar.h")},
-           .functions = {Func{
-               .identifier = Identifier("hello_world"),
-               .mangled_name = "$$mangled_name$$",
-               .return_type = Type{"i32", "int"},
-               .params = {FuncParam{Type{"i32", "int"}, Identifier("arg")}},
-               .is_inline = true}},
-           .records = {}};
+  IR ir = {
+      .used_headers = {HeaderName("foo/bar.h")},
+      .functions = {Func{.identifier = Identifier("hello_world"),
+                         .mangled_name = "$$mangled_name$$",
+                         .return_type = MappedType::Simple("i32", "int"),
+                         .params = {FuncParam{MappedType::Simple("i32", "int"),
+                                              Identifier("arg")}},
+                         .is_inline = true}},
+      .records = {}};
   Bindings bindings = GenerateBindings(ir);
   EXPECT_THAT(
       bindings.rs_api,