rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.47k stars 696 forks source link

Associated types not resolved to concrete type #1924

Open adetaylor opened 3 years ago

adetaylor commented 3 years ago

Input C/C++ Header

struct InnerType {
    typedef int related_type;
};

template <typename ContainedType> class Container {
public:
    typedef typename ContainedType::related_type contents;
    contents contents_;
};

typedef Container<InnerType> Concrete;

struct LaterContainingType {
    Concrete contents;
};

Bindgen Invocation

    let bindings = bindgen::Builder::default()
        .header("wrapper.hpp")
        .clang_args(&["-x", "c++", "-std=c++14"])
        .layout_tests(false)
        .whitelist_type("LaterContainingType")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings");

Rust code

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

fn main() {
    Concrete { contents_: 32 };
}

Actual Results

$ cargo build
   Compiling bindgen-test-case v0.1.0
error[E0308]: mismatched types
 --> src/main.rs:8:27
  |
8 |     Concrete { contents_: 32 };
  |                           ^^ expected array `[u8; 0]`, found integer

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `bindgen-test-case`

Expected Results

I'd expect the type of contents_ to be a c_int.

Other notes

Perhaps this counts as the "traits templates" situation listed under the recorded C++ limitations but, as it doesn't involve any nasty SFINAE magic I am hoping this may be possible.

Thanks for bindgen, it's amazing! Apologies if this is a duplicate.

adetaylor commented 3 years ago

I am looking into solving this.

Here is my plan. I would appreciate any guidance: since this is my first potential bindgen contribution, I feel this is quite ambitious and I could easily spend lots of time down blind alleys.

TL;DR: we generate a new trait for each associated type.

  1. Type gains a new bool (alongside is_const) called is_associated_type.
  2. In ClangItemParser for Item, in the function from_ty_with_id ("one of the trickiest methods you'll find", terrific) there is a check for ty.is_associated_type() where we currently generate an opaque type. Instead, we will call build_ty_wrapper with a new parameter that indicates that this is an associated type. This is stored in the bool.
  3. Instead of pushing these into the inner_types member of the comp, we will add these to a new list, associated_types.
  4. In impl CodeGenerator for CompInfo, codegen func, we already iterate through inner types. We will also iterate through associated_types, but instead of simply generating those types we will generate:
    pub trait __bindgen_has_associated_type_ASSOCIATED_TYPE_NAME {
    type ASSOCIATED_TYPE_NAME;
    };

    (generated only once per ASSOCIATED_TYPE_NAME) and

    impl __bindgen_has_associated_type_ASSOCIATED_TYPE_NAME for TYPE {
    type ASSOCIATED_TYPE_NAME = WHATEVER_THE_TYPEDEF_POINTED_TO;
    }
  5. Where we generate any type that depends upon resolving such an associated type based on its generic parameters, we will need to constrain those generic parameters based on these generated traits. I have not yet figured out in the code where to do this.
  6. In BindgenContext::resolve_typerefs, adjust the resolution so it ends up outputting something like <TYPE as __bindgen_has_associated_type_ASSOCIATED_TYPE_NAME>::ASSOCIATED_TYPE_NAME
  7. Sometimes we'll require types to implement Debug + Copy + Clone + __bindgen_has_associated_type_ASSOCIATED_TYPE_NAME which is unpleasant but do-able.

I anticipate Much Doom in attempting to implement this plan. Any pointers for how to avoid the most egregious pits of lava would be much appreciated.

adetaylor commented 3 years ago

OK, here's my progress so far (this is a branch 90% full of logging hacks, obviously much rebasing and cleaning would occur before PRing it). https://github.com/rust-lang/rust-bindgen/compare/master...adetaylor:associated-type-wip

A few differences from the above plan:

adetaylor commented 3 years ago

OK, I came back to this after a while and found I couldn't remember much :) Notes-to-self:

How to run & test

cargo build && ./tests/test-one.sh inner_type_simple

What works

I have managed to make a suitable edge exist to the TypeParameterAssociatedType. For the following header:

struct InnerType {
    typedef int related_type;
    long int foo;
};

template <typename ContainedType> class Container {
public:
    typename ContainedType::related_type contents_;
};

typedef Container<InnerType> Concrete;

we now generate the following bindings (test code removed for clarity):

#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct InnerType {
    pub foo: ::std::os::raw::c_long,
}
pub type InnerType_related_type = ::std::os::raw::c_int;
pub trait __bindgen_has_inner_type_related_type {
    type related_type: std::fmt::Debug + Copy + Clone + Default;
}
impl __bindgen_has_inner_type_related_type for InnerType {
    type related_type = InnerType_related_type;
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct Container<ContainedType: __bindgen_has_inner_type_related_type> {
    pub contents_: ContainedType::related_type,
    pub _phantom_0: ::std::marker::PhantomData<::std::cell::UnsafeCell<ContainedType>>,
}
pub type Concrete = Container;

What doesn't work

This is all correct except:

Next steps

I'm hoping to: