blob: 55916bd8f4fa889ff729c1531bd2e34a3d0a17f2 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
/// Types and deserialization logic for IR. See docs in
// `rs_bindings_from_cc/ir.h` for more information.
use anyhow::{bail, Context, Result};
use itertools::Itertools;
use serde::Deserialize;
use std::collections::HashMap;
use std::io::Read;
/// Deserialize `IR` from JSON given as a reader.
pub fn deserialize_ir<R: Read>(reader: R) -> Result<IR> {
let flat_ir = serde_json::from_reader(reader)?;
make_ir(flat_ir)
}
/// Create a testing `IR` instance from given items, using mock values for other
/// fields.
pub fn make_ir_from_items(items: impl IntoIterator<Item = Item>) -> Result<IR> {
make_ir_from_parts(
items.into_iter().collect_vec(),
/* used_headers= */ vec![],
/* current_target= */ "//test:testing_target".into(),
)
}
/// Create a testing `IR` instance from given parts. This function does not use
/// any mock values.
pub fn make_ir_from_parts(
items: Vec<Item>,
used_headers: Vec<HeaderName>,
current_target: Label,
) -> Result<IR> {
make_ir(FlatIR { used_headers, current_target, items })
}
fn make_ir(flat_ir: FlatIR) -> Result<IR> {
let mut used_decl_ids = HashMap::new();
for item in &flat_ir.items {
if let Some(Record { id, .. }) = item.as_record() {
if let Some(existing_decl) = used_decl_ids.insert(id, item) {
bail!("Duplicate decl_id found in {:?} and {:?}", existing_decl, item);
}
}
}
let decl_id_to_item_idx = flat_ir
.items
.iter()
.enumerate()
.filter_map(|(idx, item)| item.as_record().map(|record| (record.id, idx)))
.collect::<HashMap<_, _>>();
Ok(IR { flat_ir, decl_id_to_item_idx })
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct HeaderName {
pub name: String,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct RsType {
pub name: Option<String>,
pub type_args: Vec<RsType>,
pub decl_id: Option<DeclId>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct CcType {
pub name: Option<String>,
pub is_const: bool,
pub type_args: Vec<CcType>,
pub decl_id: Option<DeclId>,
}
pub trait TypeWithDeclId {
fn decl_id(&self) -> Option<DeclId>;
}
impl TypeWithDeclId for RsType {
fn decl_id(&self) -> Option<DeclId> {
self.decl_id
}
}
impl TypeWithDeclId for CcType {
fn decl_id(&self) -> Option<DeclId> {
self.decl_id
}
}
#[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)]
pub struct Identifier {
pub identifier: String,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize)]
#[serde(transparent)]
pub struct DeclId(pub usize);
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(transparent)]
pub struct Label(pub String);
impl<T: Into<String>> From<T> for Label {
fn from(label: T) -> Self {
Self(label.into())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum UnqualifiedIdentifier {
Identifier(Identifier),
Constructor,
Destructor,
}
impl UnqualifiedIdentifier {
pub fn identifier_as_str(&self) -> Option<&str> {
match self {
UnqualifiedIdentifier::Identifier(identifier) => Some(identifier.identifier.as_str()),
_ => None,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum ReferenceQualification {
LValue,
RValue,
Unqualified,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct InstanceMethodMetadata {
pub reference: ReferenceQualification,
pub is_const: bool,
pub is_virtual: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct MemberFuncMetadata {
pub for_type: Identifier,
pub instance_method_metadata: Option<InstanceMethodMetadata>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct FuncParam {
#[serde(rename(deserialize = "type"))]
pub type_: MappedType,
pub identifier: Identifier,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Func {
pub name: UnqualifiedIdentifier,
pub record_decl_id: Option<DeclId>,
pub owning_target: Label,
pub mangled_name: String,
pub doc_comment: Option<String>,
pub return_type: MappedType,
pub params: Vec<FuncParam>,
pub is_inline: bool,
pub member_func_metadata: Option<MemberFuncMetadata>,
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize)]
pub enum AccessSpecifier {
Public,
Protected,
Private,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Field {
pub identifier: Identifier,
pub doc_comment: Option<String>,
#[serde(rename(deserialize = "type"))]
pub type_: MappedType,
pub access: AccessSpecifier,
pub offset: usize,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum SpecialMemberDefinition {
Trivial,
NontrivialMembers,
NontrivialSelf,
Deleted,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct SpecialMemberFunc {
pub definition: SpecialMemberDefinition,
pub access: AccessSpecifier,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Record {
pub identifier: Identifier,
pub id: DeclId,
pub owning_target: Label,
pub doc_comment: Option<String>,
pub fields: Vec<Field>,
pub size: usize,
pub alignment: usize,
pub copy_constructor: SpecialMemberFunc,
pub move_constructor: SpecialMemberFunc,
pub destructor: SpecialMemberFunc,
pub is_trivial_abi: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct SourceLoc {
pub filename: String,
pub line: u64,
pub column: u64,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct UnsupportedItem {
pub name: String,
pub message: String,
pub source_loc: SourceLoc,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Comment {
pub text: String,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum Item {
Func(Func),
Record(Record),
UnsupportedItem(UnsupportedItem),
Comment(Comment),
}
impl Item {
fn as_record(&self) -> Option<&Record> {
match self {
Item::Record(record) => Some(record),
_ => None,
}
}
}
impl From<Func> for Item {
fn from(func: Func) -> Item {
Item::Func(func)
}
}
impl From<Record> for Item {
fn from(record: Record) -> Item {
Item::Record(record)
}
}
impl From<UnsupportedItem> for Item {
fn from(unsupported: UnsupportedItem) -> Item {
Item::UnsupportedItem(unsupported)
}
}
impl From<Comment> for Item {
fn from(comment: Comment) -> Item {
Item::Comment(comment)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(rename(deserialize = "IR"))]
struct FlatIR {
#[serde(default)]
used_headers: Vec<HeaderName>,
current_target: Label,
#[serde(default)]
items: Vec<Item>,
}
/// Struct providing the necessary information about the API of a C++ target to
/// enable generation of Rust bindings source code (both `rs_api.rs` and
/// `rs_api_impl.cc` files).
#[derive(PartialEq, Debug)]
pub struct IR {
flat_ir: FlatIR,
// A map from a `decl_id` to an index of an `Item` in the `flat_ir.items` vec.
decl_id_to_item_idx: HashMap<DeclId, usize>,
}
impl IR {
pub fn items(&self) -> impl Iterator<Item = &Item> {
self.flat_ir.items.iter()
}
pub fn items_mut(&mut self) -> impl Iterator<Item = &mut Item> {
self.flat_ir.items.iter_mut()
}
pub fn take_items(self) -> Vec<Item> {
self.flat_ir.items
}
pub fn used_headers(&self) -> impl Iterator<Item = &HeaderName> {
self.flat_ir.used_headers.iter()
}
pub fn functions(&self) -> impl Iterator<Item = &Func> {
self.items().filter_map(|item| match item {
Item::Func(func) => Some(func),
_ => None,
})
}
pub fn records(&self) -> impl Iterator<Item = &Record> {
self.items().filter_map(|item| match item {
Item::Record(func) => Some(func),
_ => None,
})
}
pub fn unsupported_items(&self) -> impl Iterator<Item = &UnsupportedItem> {
self.items().filter_map(|item| match item {
Item::UnsupportedItem(unsupported_item) => Some(unsupported_item),
_ => None,
})
}
pub fn record_for_type<T>(&self, ty: &T) -> Result<&Record>
where
T: TypeWithDeclId + std::fmt::Debug,
{
if let Some(decl_id) = ty.decl_id() {
let idx = *self
.decl_id_to_item_idx
.get(&decl_id)
.with_context(|| format!("Couldn't find decl_id {:?} in the IR.", decl_id))?;
let item = self
.flat_ir
.items
.get(idx)
.with_context(|| format!("Couldn't find an item at idx {}", idx))?;
match item {
Item::Record(ref record) => Ok(record),
_ => bail!("Unexpected item type {:?}", item),
}
} else {
bail!("Type {:?} does not have an associated record.", ty)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_used_headers() {
let input = r#"
{
"used_headers": [{ "name": "foo/bar.h" }],
"current_target": "//foo:bar"
}
"#;
let ir = deserialize_ir(input.as_bytes()).unwrap();
let expected = FlatIR {
used_headers: vec![HeaderName { name: "foo/bar.h".to_string() }],
current_target: "//foo:bar".into(),
items: vec![],
};
assert_eq!(ir.flat_ir, expected);
}
#[test]
fn test_member_access_specifiers() {
let input = r#"
{
"current_target": "//foo:bar",
"items": [
{ "Record" : {
"identifier": {"identifier": "SomeStruct" },
"id": 42,
"owning_target": "//foo:bar",
"fields": [
{
"identifier": {"identifier": "public_int" },
"type": {
"rs_type": {"name": "i32", "type_args": []},
"cc_type": {"name": "int", "is_const": false, "type_args": []}
},
"access": "Public",
"offset": 0
},
{
"identifier": {"identifier": "protected_int" },
"type": {
"rs_type": {"name": "i32", "type_args": []},
"cc_type": {"name": "int", "is_const": false, "type_args": []}
},
"access": "Protected",
"offset": 32
},
{
"identifier": {"identifier": "private_int" },
"type": {
"rs_type": {"name": "i32", "type_args": []},
"cc_type": {"name": "int", "is_const": false, "type_args": []}
},
"access": "Private",
"offset": 64
}
],
"size": 12,
"alignment": 4,
"copy_constructor": {
"definition": "NontrivialSelf",
"access": "Private"
},
"move_constructor": {
"definition": "Deleted",
"access": "Protected"
},
"destructor": {
"definition": "Trivial",
"access": "Public"
},
"is_trivial_abi": true
}}
]
}
"#;
let ir = deserialize_ir(input.as_bytes()).unwrap();
let expected = FlatIR {
used_headers: vec![],
current_target: "//foo:bar".into(),
items: vec![Item::Record(Record {
identifier: Identifier { identifier: "SomeStruct".to_string() },
id: DeclId(42),
owning_target: "//foo:bar".into(),
doc_comment: None,
fields: vec![
Field {
identifier: Identifier { identifier: "public_int".to_string() },
doc_comment: None,
type_: MappedType {
rs_type: RsType {
name: "i32".to_string().into(),
type_args: vec![],
decl_id: None,
},
cc_type: CcType {
name: "int".to_string().into(),
is_const: false,
type_args: vec![],
decl_id: None,
},
},
access: AccessSpecifier::Public,
offset: 0,
},
Field {
identifier: Identifier { identifier: "protected_int".to_string() },
doc_comment: None,
type_: MappedType {
rs_type: RsType {
name: "i32".to_string().into(),
type_args: vec![],
decl_id: None,
},
cc_type: CcType {
name: "int".to_string().into(),
is_const: false,
type_args: vec![],
decl_id: None,
},
},
access: AccessSpecifier::Protected,
offset: 32,
},
Field {
identifier: Identifier { identifier: "private_int".to_string() },
doc_comment: None,
type_: MappedType {
rs_type: RsType {
name: "i32".to_string().into(),
type_args: vec![],
decl_id: None,
},
cc_type: CcType {
name: "int".to_string().into(),
is_const: false,
type_args: vec![],
decl_id: None,
},
},
access: AccessSpecifier::Private,
offset: 64,
},
],
size: 12,
alignment: 4,
copy_constructor: SpecialMemberFunc {
definition: SpecialMemberDefinition::NontrivialSelf,
access: AccessSpecifier::Private,
},
move_constructor: SpecialMemberFunc {
definition: SpecialMemberDefinition::Deleted,
access: AccessSpecifier::Protected,
},
destructor: SpecialMemberFunc {
definition: SpecialMemberDefinition::Trivial,
access: AccessSpecifier::Public,
},
is_trivial_abi: true,
})],
};
assert_eq!(ir.flat_ir, expected);
}
#[test]
fn test_pointer_member_variable() {
let input = r#"
{
"current_target": "//foo:bar",
"items": [
{ "Record": {
"identifier": {"identifier": "SomeStruct" },
"id": 42,
"owning_target": "//foo:bar",
"fields": [
{
"identifier": {"identifier": "ptr" },
"type": {
"rs_type": {"name": "*mut", "type_args": [
{"name": "SomeStruct", "type_args": [], "decl_id": 42}
]},
"cc_type": { "name": "*", "is_const": false, "type_args": [
{
"name": "SomeStruct",
"is_const": false,
"type_args": [],
"decl_id": 42
}
]}
},
"access": "Public",
"offset": 0
}
],
"size": 8,
"alignment": 8,
"copy_constructor": {
"definition": "Trivial",
"access": "Public"
},
"move_constructor": {
"definition": "Trivial",
"access": "Public"
},
"destructor": {
"definition": "Trivial",
"access": "Public"
},
"is_trivial_abi": true
}}
]
}
"#;
let ir = deserialize_ir(input.as_bytes()).unwrap();
let expected = FlatIR {
used_headers: vec![],
current_target: "//foo:bar".into(),
items: vec![Item::Record(Record {
identifier: Identifier { identifier: "SomeStruct".to_string() },
id: DeclId(42),
owning_target: "//foo:bar".into(),
doc_comment: None,
fields: vec![Field {
identifier: Identifier { identifier: "ptr".to_string() },
doc_comment: None,
type_: MappedType {
rs_type: RsType {
name: "*mut".to_string().into(),
decl_id: None,
type_args: vec![RsType {
name: "SomeStruct".to_string().into(),
type_args: vec![],
decl_id: Some(DeclId(42)),
}],
},
cc_type: CcType {
name: "*".to_string().into(),
is_const: false,
decl_id: None,
type_args: vec![CcType {
name: "SomeStruct".to_string().into(),
is_const: false,
type_args: vec![],
decl_id: Some(DeclId(42)),
}],
},
},
access: AccessSpecifier::Public,
offset: 0,
}],
size: 8,
alignment: 8,
move_constructor: SpecialMemberFunc {
definition: SpecialMemberDefinition::Trivial,
access: AccessSpecifier::Public,
},
copy_constructor: SpecialMemberFunc {
definition: SpecialMemberDefinition::Trivial,
access: AccessSpecifier::Public,
},
destructor: SpecialMemberFunc {
definition: SpecialMemberDefinition::Trivial,
access: AccessSpecifier::Public,
},
is_trivial_abi: true,
})],
};
assert_eq!(ir.flat_ir, expected);
}
}