Skip to content

Commit

Permalink
When a trait has a nontrivial parameter, materialize it in the caller.
Browse files Browse the repository at this point in the history
(Technically, this does let you call trait methods with nontrivial arguments,
but it's not in a final/decent place.)

This is an alternate version of unknown commit which tried to continue to defer
materialization. However, as discussed in[]
currently impossible.

In particular, we cannot define a trait like this:

```rs
impl<T: Ctor<Output=A>> MyTrait<T> for S {...}
impl<T: Ctor<Output=B>> MyTrait<T> for S {...}
```

... because Rust does not understand that these impls are disjoint:

rust-lang/rust#20400

#### What's next?

(Apologies if this is a bit unorganized, I've spent too much time in the trenches.)

So this CL is just a first step: we *must*  monomorphize the implementation.
Rather than accepting any `T: Ctor`, accept an `RvalueReference` (a concrete type).

After this CL, I think we have a slightly more open field than I thought. In particular,
we should be able to regain the `Ctor` API, except using only *one* parameterized impl.
So, instead of the broken impls above, we can have this impl:

```rs
impl<'a> MyTrait<RvalueReference<'a, A>> for S {...}
impl<'a> MyTrait<RvalueReference<'a, B>> for S {...}

impl<U, CtorType> MyTrait<CtorType> for S
where
  &C : for<'a> MyTrait<RvalueReference<'a, U>>,
  CtorType: Ctor<Output=U>
{...}
```

Because this is only _one_ parameterized impl, there's no conflicts. It is
implemented in terms of the concrete non-parameterized impls as generated by
this change.

However, I'm not yet 100% certain this will work, and it is actually not a small task
to do, even on top of this CL. For example, there's a bunch of refactoring to let one
generate a second blanket impl using knowledge about the trait function etc. from the
concrete impl.

##### RvalueReference might need to get replaced.

If we can use the `Ctor` approach described above... we can't use `RvalueReference`,
actually, because Rust will then recurse infinitely. The `RvalueReference` type used
for the `for<'a> MyTrait<RvalueReference<...>>` bound must be in the _same_ crate so
that Rust knows that `RvalueReference` doesn't itself impl `Ctor`. And unfortunately,
no, negative impls aren't good enough here, yet, apparently. At least, it didn't
resolve it when I tried it!

You can test this in a local two-crate setup.

Crate 1:

```rs
pub trait Ctor {
  type Output;
}
pub struct RvalueReference<'a, T>(&'a T);
```

Crate 2:

```rs
use lib1::*;

pub struct A1;
pub struct A2;
pub struct B;

impl <'a> From<RvalueReference<'a, A1>> for B {
  fn from(_: RvalueReference<'a, A1>) -> Self { todo!(); }
}

impl <'a> From<RvalueReference<'a, A2>> for B {
  fn from(_: RvalueReference<'a, A2>) -> Self { todo!(); }
}

impl <T: Ctor> From<T> for B
where B : for<'a> From<RvalueReference<'a, T::Output>> {
  fn from(_: T) -> Self { todo!(); }
}
```

If you build crate 2, it will fail with the following error:

```
error[E0275]: overflow evaluating the requirement `for<'a> B: From<lib1::RvalueReference<'a, _>>`
   |
   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`lib2`)
note: required because of the requirements on the impl of `for<'a> From<lib1::RvalueReference<'a, _>>` for `B`
  --> src/lib.rs:15:16
   |
15 | impl <T: Ctor> From<T> for B
   |                ^^^^^^^     ^
   = note: 126 redundant requirements hidden
   = note: required because of the requirements on the impl of `for<'a> From<lib1::RvalueReference<'a, _>>` for `B`

For more information about this error, try `rustc --explain E0275`.
error: could not compile `lib2` due to previous error
```

But it will work fine if you move `RvalueReference` to another crate!

##### If all else fails, we'll force the caller to materialize the Ctor

If even the approach outlined above doesn't work, well, we'll just have to force callers
to materialize the `Ctor`: call `Trait::method(mov(emplace!(foo())))` instead of
`Trait::method(foo())`.

That's what I described in[]
but I'm hoping we can work our way out after all!

Either way, both approaches build on this change. Investigating the followups may take some
time, so I'd rather not leave this change sitting around generating merge conflicts,
if possible. :X

PiperOrigin-RevId: 464613254
  • Loading branch information
ssbr authored and copybara-github committed Aug 1, 2022
1 parent 7ed8c6f commit 2b1e46d
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 44 deletions.
97 changes: 83 additions & 14 deletions rs_bindings_from_cc/src_code_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,10 @@ fn is_visible_by_adl(enclosing_record: &Record, param_types: &[RsTypeKind]) -> b

/// Returns the shape of the generated Rust API for a given function definition.
///
/// If the shape is a trait, this also mutates the parameter types to be
/// trait-compatible. In particular, types which would be `impl Ctor<Output=T>`
/// become a `RvalueReference<'_, T>`.
///
/// Returns:
///
/// * `Err(_)`: something went wrong importing this function.
Expand All @@ -504,7 +508,7 @@ fn is_visible_by_adl(enclosing_record: &Record, param_types: &[RsTypeKind]) -> b
fn api_func_shape(
func: &Func,
ir: &IR,
param_types: &[RsTypeKind],
param_types: &mut [RsTypeKind],
) -> Result<Option<(Ident, ImplKind)>> {
let maybe_record: Option<&Rc<Record>> = ir.record_for_member_func(func)?;
let has_pointer_params = param_types.iter().any(|p| matches!(p, RsTypeKind::Pointer { .. }));
Expand Down Expand Up @@ -555,6 +559,7 @@ fn api_func_shape(
if record.is_unpin() {
bail!("operator= for Unpin types is not yet supported.");
}
materialize_ctor_in_caller(param_types);
let rhs = &param_types[1];
impl_kind = ImplKind::new_trait(
TraitName::Other {
Expand All @@ -575,6 +580,7 @@ fn api_func_shape(
}
}

materialize_ctor_in_caller(param_types);
let (record, impl_for) = if let Some(record) = maybe_record {
(&**record, ImplFor::RefT)
} else {
Expand Down Expand Up @@ -660,6 +666,7 @@ fn api_func_shape(
);
func_name = make_rs_ident("drop");
} else {
materialize_ctor_in_caller(param_types);
impl_kind = ImplKind::new_trait(
TraitName::Other {
name: quote! {::ctor::PinnedDrop},
Expand Down Expand Up @@ -693,6 +700,7 @@ fn api_func_shape(
);
}

materialize_ctor_in_caller(param_types);
let record_name = make_rs_ident(&record.rs_name);
if !record.is_unpin() {
func_name = make_rs_ident("ctor_new");
Expand Down Expand Up @@ -763,6 +771,45 @@ fn api_func_shape(
Ok(Some((func_name, impl_kind)))
}

/// Mutates the provided parameters so that nontrivial by-value parameters are,
/// instead, materialized in the caller and passed by rvalue reference.
///
/// This produces rvalue references with elided lifetimes. If they compile,
/// they are correct, but the resulting signatures can be surprising.
///
/// For example, consider `From`. This rewriting of function parameters might
/// create an impl as follows:
///
/// ```
/// // C++: struct Foo{ Foo(Nontrivial x); };
/// impl From<RvalueReference<'_, Nontrivial>> for Foo {
/// fn from(x: RvalueReference<'_, Nontrivial>) -> Self { ... }
/// }
/// ```
///
/// Each `'_` is actually a different lifetime! However, due to lifetime
/// subtyping, they are allowed to be used in this sort of way, interchangeably.
/// And indeed, this is even idiomatic. For example, the exact same pattern is
/// in use here:
///
/// https://doc.rust-lang.org/std/string/struct.String.html#impl-From%3C%26%27_%20String%3E
///
/// If there is some case where this is actually _not_ okay, then we would need
/// to generate new named lifetimes, rather than elided lifetimes.
fn materialize_ctor_in_caller(params: &mut [RsTypeKind]) {
for param in params {
if param.is_unpin() {
continue;
}
let value = std::mem::replace(param, RsTypeKind::Unit); // Temporarily swap in a garbage value.
*param = RsTypeKind::RvalueReference {
referent: Rc::new(value),
mutability: Mutability::Mut,
lifetime: Lifetime::Elided,
};
}
}

/// Generates Rust source code for a given `Func`.
///
/// Returns:
Expand All @@ -778,7 +825,7 @@ fn generate_func(
) -> Result<Option<RcEq<(RsSnippet, RsSnippet, Rc<FunctionId>)>>> {
let ir = db.ir();
let mut features = BTreeSet::new();
let param_types = func
let mut param_types = func
.params
.iter()
.map(|p| {
Expand All @@ -788,12 +835,12 @@ fn generate_func(
})
.collect::<Result<Vec<_>>>()?;

let (func_name, mut impl_kind) = if let Some(values) = api_func_shape(&func, &ir, &param_types)?
{
values
} else {
return Ok(None);
};
let (func_name, mut impl_kind) =
if let Some(values) = api_func_shape(&func, &ir, &mut param_types)? {
values
} else {
return Ok(None);
};

let mut return_type = db
.rs_type_kind(func.return_type.rs_type.clone())
Expand All @@ -807,8 +854,12 @@ fn generate_func(
for (i, (ident, type_)) in param_idents.iter().zip(param_types.iter()).enumerate() {
if !type_.is_unpin() {
// `impl Ctor` will fail to compile in a trait.
// This will only be hit if there was a bug in api_func_shape.
if let ImplKind::Trait { .. } = &impl_kind {
bail!("b/200067242: non-Unpin types are not yet supported by value in traits");
panic!(
"non-Unpin types cannot work by value in traits; this should have instead \
become an rvalue reference to force the caller to materialize the Ctor."
);
}
// The generated bindings require a move constructor.
if !type_.is_move_constructible() {
Expand Down Expand Up @@ -1016,8 +1067,7 @@ fn generate_func(
};

let fn_generic_params: TokenStream;
if let ImplKind::Trait { trait_name, trait_generic_params, impl_for, .. } = &mut impl_kind
{
if let ImplKind::Trait { trait_name, trait_generic_params, impl_for, .. } = &mut impl_kind {
// When the impl block is for some kind of reference to T, consider the lifetime
// parameters on the self parameter to be trait lifetimes so they can be
// introduced before they are used.
Expand Down Expand Up @@ -6388,12 +6438,31 @@ mod tests {
Nontrivial& operator=(Nontrivial) {}
~Nontrivial();
};
struct Trivial final {
/*implicit*/ Trivial(Nontrivial) {}
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
// the assign trait shouldn't be implemented for `Nontrivial`.
// (In fact, it shouldn't be referenced at all -- thus the very minimal test!)
assert_rs_not_matches!(rs_api, quote! {Assign});
assert_rs_matches!(
rs_api,
quote! {
impl From<::ctor::RvalueReference<'_, crate::Nontrivial>> for Trivial {
#[inline(always)]
fn from(__param_0: ::ctor::RvalueReference<'_, crate::Nontrivial>) -> Self {
let mut tmp = ::std::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN7TrivialC1E10Nontrivial(
&mut tmp,
__param_0
);
tmp.assume_init()
}
}
}
}
);
Ok(())
}

Expand Down
8 changes: 6 additions & 2 deletions rs_bindings_from_cc/test/golden/nontrivial_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,12 @@ NontrivialUnpin& TakesByReferenceUnpin(NontrivialUnpin& nontrivial);

// Finally, testing for strange by-value APIs.
struct NontrivialByValue {
// NOLINTNEXTLINE(misc-unconventional-assign-operator)
NontrivialByValue operator=(NontrivialByValue other);
NontrivialByValue(const NontrivialByValue& other) = default;
NontrivialByValue(NontrivialByValue&& other) = default;
NontrivialByValue& operator=(const NontrivialByValue& other) = default;
NontrivialByValue& operator=(NontrivialByValue&& other) = default;
// // NOLINTNEXTLINE(misc-unconventional-assign-operator)
NontrivialByValue operator=(Nontrivial other);
NontrivialByValue operator==(NontrivialByValue other);
};

Expand Down
112 changes: 91 additions & 21 deletions rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,50 +645,106 @@ forward_declare::unsafe_define!(
crate::NontrivialByValue
);

impl ::ctor::CtorNew<()> for NontrivialByValue {
type CtorType = impl ::ctor::Ctor<Output = Self>;
impl<'b> ::ctor::CtorNew<&'b crate::NontrivialByValue> for NontrivialByValue {
type CtorType = impl ::ctor::Ctor<Output = Self> + ::ctor::Captures<'b>;
#[inline(always)]
fn ctor_new(args: ()) -> Self::CtorType {
let () = args;
fn ctor_new(args: &'b crate::NontrivialByValue) -> Self::CtorType {
let other = args;
::ctor::FnCtor::new(
move |dest: ::std::pin::Pin<&mut ::std::mem::MaybeUninit<Self>>| unsafe {
crate::detail::__rust_thunk___ZN17NontrivialByValueC1Ev(
crate::detail::__rust_thunk___ZN17NontrivialByValueC1ERKS_(
::std::pin::Pin::into_inner_unchecked(dest),
other,
);
},
)
}
}
impl<'b> ::ctor::CtorNew<(&'b crate::NontrivialByValue,)> for NontrivialByValue {
type CtorType = impl ::ctor::Ctor<Output = Self> + ::ctor::Captures<'b>;
#[inline(always)]
fn ctor_new(args: (&'b crate::NontrivialByValue,)) -> Self::CtorType {
let (arg,) = args;
<Self as ::ctor::CtorNew<&'b crate::NontrivialByValue>>::ctor_new(arg)
}
}

impl<'b> ::ctor::CtorNew<&'b crate::NontrivialByValue> for NontrivialByValue {
impl<'b> ::ctor::CtorNew<::ctor::RvalueReference<'b, crate::NontrivialByValue>>
for NontrivialByValue
{
type CtorType = impl ::ctor::Ctor<Output = Self> + ::ctor::Captures<'b>;
#[inline(always)]
fn ctor_new(args: &'b crate::NontrivialByValue) -> Self::CtorType {
let __param_0 = args;
fn ctor_new(args: ::ctor::RvalueReference<'b, crate::NontrivialByValue>) -> Self::CtorType {
let other = args;
::ctor::FnCtor::new(
move |dest: ::std::pin::Pin<&mut ::std::mem::MaybeUninit<Self>>| unsafe {
crate::detail::__rust_thunk___ZN17NontrivialByValueC1ERKS_(
crate::detail::__rust_thunk___ZN17NontrivialByValueC1EOS_(
::std::pin::Pin::into_inner_unchecked(dest),
__param_0,
other,
);
},
)
}
}
impl<'b> ::ctor::CtorNew<(&'b crate::NontrivialByValue,)> for NontrivialByValue {
impl<'b> ::ctor::CtorNew<(::ctor::RvalueReference<'b, crate::NontrivialByValue>,)>
for NontrivialByValue
{
type CtorType = impl ::ctor::Ctor<Output = Self> + ::ctor::Captures<'b>;
#[inline(always)]
fn ctor_new(args: (&'b crate::NontrivialByValue,)) -> Self::CtorType {
fn ctor_new(args: (::ctor::RvalueReference<'b, crate::NontrivialByValue>,)) -> Self::CtorType {
let (arg,) = args;
<Self as ::ctor::CtorNew<&'b crate::NontrivialByValue>>::ctor_new(arg)
<Self as ::ctor::CtorNew<::ctor::RvalueReference<'b, crate::NontrivialByValue>>>::ctor_new(
arg,
)
}
}

impl<'b> ::ctor::Assign<&'b crate::NontrivialByValue> for NontrivialByValue {
#[inline(always)]
fn assign<'a>(self: ::std::pin::Pin<&'a mut Self>, other: &'b crate::NontrivialByValue) {
unsafe {
crate::detail::__rust_thunk___ZN17NontrivialByValueaSERKS_(self, other);
}
}
}

// rs_bindings_from_cc/test/golden/nontrivial_type.h;l=97
// Error while generating bindings for item 'NontrivialByValue::operator=':
// b/200067242: non-Unpin types are not yet supported by value in traits
impl<'b> ::ctor::Assign<::ctor::RvalueReference<'b, crate::NontrivialByValue>>
for NontrivialByValue
{
#[inline(always)]
fn assign<'a>(
self: ::std::pin::Pin<&'a mut Self>,
other: ::ctor::RvalueReference<'b, crate::NontrivialByValue>,
) {
unsafe {
crate::detail::__rust_thunk___ZN17NontrivialByValueaSEOS_(self, other);
}
}
}

impl ::ctor::Assign<::ctor::RvalueReference<'_, crate::Nontrivial>> for NontrivialByValue {
#[inline(always)]
fn assign<'a>(
self: ::std::pin::Pin<&'a mut Self>,
other: ::ctor::RvalueReference<'_, crate::Nontrivial>,
) {
unsafe {
let _ = ::ctor::emplace!(::ctor::FnCtor::new(
move |dest: ::std::pin::Pin<
&mut ::std::mem::MaybeUninit<crate::NontrivialByValue>,
>| {
crate::detail::__rust_thunk___ZN17NontrivialByValueaSE10Nontrivial(
::std::pin::Pin::into_inner_unchecked(dest),
self,
other,
);
}
));
}
}
}

// rs_bindings_from_cc/test/golden/nontrivial_type.h;l=98
// rs_bindings_from_cc/test/golden/nontrivial_type.h;l=102
// Error while generating bindings for item 'NontrivialByValue::operator==':
// operator== where operands are not const references

Expand Down Expand Up @@ -728,7 +784,7 @@ impl Nonmovable {
}
}

// rs_bindings_from_cc/test/golden/nontrivial_type.h;l=110
// rs_bindings_from_cc/test/golden/nontrivial_type.h;l=114
// Error while generating bindings for item 'TakesNonmovableByValue':
// Non-movable, non-trivial_abi type 'crate :: Nonmovable' is not supported by value as parameter #0

Expand Down Expand Up @@ -922,12 +978,26 @@ mod detail {
pub(crate) fn __rust_thunk___Z21TakesByReferenceUnpinR15NontrivialUnpin<'a>(
nontrivial: &'a mut crate::NontrivialUnpin,
) -> &'a mut crate::NontrivialUnpin;
pub(crate) fn __rust_thunk___ZN17NontrivialByValueC1Ev<'a>(
pub(crate) fn __rust_thunk___ZN17NontrivialByValueC1ERKS_<'a, 'b>(
__this: &'a mut ::std::mem::MaybeUninit<crate::NontrivialByValue>,
other: &'b crate::NontrivialByValue,
);
pub(crate) fn __rust_thunk___ZN17NontrivialByValueC1ERKS_<'a, 'b>(
pub(crate) fn __rust_thunk___ZN17NontrivialByValueC1EOS_<'a, 'b>(
__this: &'a mut ::std::mem::MaybeUninit<crate::NontrivialByValue>,
__param_0: &'b crate::NontrivialByValue,
other: ::ctor::RvalueReference<'b, crate::NontrivialByValue>,
);
pub(crate) fn __rust_thunk___ZN17NontrivialByValueaSERKS_<'a, 'b>(
__this: ::std::pin::Pin<&'a mut crate::NontrivialByValue>,
other: &'b crate::NontrivialByValue,
) -> ::std::pin::Pin<&'a mut crate::NontrivialByValue>;
pub(crate) fn __rust_thunk___ZN17NontrivialByValueaSEOS_<'a, 'b>(
__this: ::std::pin::Pin<&'a mut crate::NontrivialByValue>,
other: ::ctor::RvalueReference<'b, crate::NontrivialByValue>,
) -> ::std::pin::Pin<&'a mut crate::NontrivialByValue>;
pub(crate) fn __rust_thunk___ZN17NontrivialByValueaSE10Nontrivial<'a>(
__return: &mut ::std::mem::MaybeUninit<crate::NontrivialByValue>,
__this: ::std::pin::Pin<&'a mut crate::NontrivialByValue>,
other: ::ctor::RvalueReference<'_, crate::Nontrivial>,
);
#[link_name = "_ZN10NonmovableC1Ev"]
pub(crate) fn __rust_thunk___ZN10NonmovableC1Ev<'a>(
Expand Down
27 changes: 20 additions & 7 deletions rs_bindings_from_cc/test/golden/nontrivial_type_rs_api_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,27 @@ extern "C" void __rust_thunk___Z18TakesByValueInline16NontrivialInline(
extern "C" void __rust_thunk___Z14ReturnsByValuev(struct Nontrivial* __return) {
new (__return) auto(ReturnsByValue());
}
extern "C" void __rust_thunk___ZN17NontrivialByValueC1Ev(
struct NontrivialByValue* __this) {
crubit::construct_at(__this);
}
extern "C" void __rust_thunk___ZN17NontrivialByValueC1ERKS_(
struct NontrivialByValue* __this,
const struct NontrivialByValue* __param_0) {
crubit::construct_at(__this, *__param_0);
struct NontrivialByValue* __this, const struct NontrivialByValue* other) {
crubit::construct_at(__this, *other);
}
extern "C" void __rust_thunk___ZN17NontrivialByValueC1EOS_(
struct NontrivialByValue* __this, struct NontrivialByValue* other) {
crubit::construct_at(__this, std::move(*other));
}
extern "C" struct NontrivialByValue*
__rust_thunk___ZN17NontrivialByValueaSERKS_(
struct NontrivialByValue* __this, const struct NontrivialByValue* other) {
return &__this->operator=(*other);
}
extern "C" struct NontrivialByValue* __rust_thunk___ZN17NontrivialByValueaSEOS_(
struct NontrivialByValue* __this, struct NontrivialByValue* other) {
return &__this->operator=(std::move(*other));
}
extern "C" void __rust_thunk___ZN17NontrivialByValueaSE10Nontrivial(
struct NontrivialByValue* __return, struct NontrivialByValue* __this,
struct Nontrivial* other) {
new (__return) auto(__this->operator=(std::move(*other)));
}
extern "C" void __rust_thunk___Z24ReturnsNonmovableByValuev(
struct Nonmovable* __return) {
Expand Down

0 comments on commit 2b1e46d

Please sign in to comment.