-
Notifications
You must be signed in to change notification settings - Fork 390
Arbitrary inner type in named impl key #1658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Arbitrary inner type in named impl key #1658
Conversation
6e061fe to
9212d1d
Compare
This commit refactors `write_...` functions to enable formatting a C++ representation of a `Type` into any generic `impl std::fmt::Write` instead of only supporting formatting into an `OutFile`. This is then used to provide `fn stringify_type`. The new function is not used in this commit, but will become quite handy in a follow-up commit when `inner` type of generics/templates may not necessarily be a simple `std::fmt`-friendly `Type::Ident`. Without the refactored `write_...` APIs, the follow-up would have to split *single* `writeln!` invocations (such as the ones in `fn write_shared_ptr` or `fn write_cxx_vector`) into *multiple* ones - e.g. into: `write!` + `write_type` + `write!` + `write_type` + `writeln!`. The new function is a little bit redundant wrt the already existing `trait ToTypename`, but we can't replace that trait just yet: * The trait has slightly different semantics (formatting not the whole type represented by `self`, but only formatting the inner `T`). * The trait works with `Ident` rather than `Type` (this will change in the follow-up commit mentioned above).
Before this commit `NamedImplKey` could only represent the inner type as
`rust: &'a Ident`. For example, it could represent `Vec<Foo>` but could
not represent `Vec<Box<Foo>>` where the inner type (`Box<Foo>` in our
example) is not a simple identifier.
After this commit `NamedImplKey` is refactored to support an arbitrary
inner type. Note that (to simplify and to minimize risks associated
with this commit) this new ability is not actually used at this point -
it is planned to be used in follow-up commits to incrementally relax
generic type argument restrictions in `syntax/check.rs`.
This commit is quite big, but it seems difficult to extract some changes
to smaller, separate commits, because all of the changes stem from the
refactoring of the `NamedImplKey`. At a high-level this commit contains
the following changes:
1. `syntax/instantiate.rs`: Changing `pub rust: &'a Ident` field of
`NamedImplKey` to `pub inner: &'a Type`. This is the main/root
change in this commit.
2. `gen/src/write.rs`: supporting arbitrary inner types
when writing C++ thunks exposing instantiations/monomorphizations of
templates/generics supported by `cxx`.
* This depends on `fn stringify_type` introduced in
`gen/src/write.rs` in an earlier commit.
* Handling arbitrary inner types *in general* means that we can
delete `enum UniquePtr` which provided handling of two *specific*
inner types.
3. `macro/src/expand.rs`: supporting arbitrary inner types
when writing Rust thunks exposing instantiations/monomorphizations of
templates/generics supported by `cxx`.
* Using `#inner` instead of `#ident` may now (optionally) cover
generic lifetime arguments. This is why this commit also changes
`macro/src/generics.rs`. And this is why we can no longer need
the `ty_generics` field from `struct Impl`.
* One minor functional change here is changing the error messages
so that references to type names are generated purely in the
generated bindings, without depending on `fn display_namespaced`.
4. `syntax/mangle.rs`: supporting mangling of individual types. This
helps to:
* Support the (long-term, not-yet-realized) high-level goal of
actually allowing and using arbitrary inner types
* Deduplicate mangling code details that were somewhat duplicated
in `macro/src/expand.rs` and `gen/src/write.rs`.
5. `syntax/types.rs`: Supporting arbitrary inner types in
* `fn is_maybe_trivial`
* `fn is_local` (this function supports an earlier refactoring
that changed how `cxx` decides whether to provide an *implicit*
impl of a given generic/template instantiation/monomorphization)
9212d1d to
18a1c1c
Compare
|
Rebased + minor tweak after a self-review: s/ |
dtolnay
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you — I am merging this with some changes in #1669.
| let inner = ty.to_typename(out.types); | ||
| let instance = ty.to_mangled(out.types); | ||
| let inner = stringify_type(ty, out.types); | ||
| let instance = crate::syntax::mangle::type_(ty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mangle module is already imported and used elsewhere in this file.
| let instance = crate::syntax::mangle::type_(ty) | |
| let instance = mangle::type_(ty) |
|
|
||
| impl<'a> Write for OutFile<'a> { | ||
| fn write_str(&mut self, s: &str) -> fmt::Result { | ||
| self.content.borrow_mut().write(s); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| self.content.borrow_mut().write(s); | |
| self.content.get_mut().write(s); |
| /// before `syntax/check.rs` has rejected unsupported generic type parameters. | ||
| pub(crate) fn type_(t: &Type) -> Option<Symbol> { | ||
| match t { | ||
| Type::Ident(named_type) => Some(join!(named_type.rust)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to give conflicting symbols when an identical Rust name exists in multiple namespaces. The original code before this PR was mangling using the C++ namespace and name.
// src/lib.rs
mod two;
#[cxx::bridge(namespace = "one")]
pub mod one {
struct Context { x: i32 }
impl Box<Context> {}
}// src/two.rs
#[cxx::bridge(namespace = "two")]
pub mod two {
struct Context { x: i8 }
impl Box<Context> {}
}error: symbol `cxxbridge1$box$Context$alloc` is already defined
--> src/two.rs:6:23
|
6 | impl Box<Context> {}
| ^^or for a C++ type:
= note: rust-lld: error: duplicate symbol: cxxbridge1$unique_ptr$Context$null
>>> defined at main.rs.cc:64 (target/debug/build/example-aff44aa21d738c2a/out/cxxbridge/sources/example/src/main.rs.cc:64)
>>> 4be10217b215df70-main.rs.o:(cxxbridge1$unique_ptr$Context$null) in archive target/debug/build/example-aff44aa21d738c2a/out/libexample-cxx.a
>>> defined at two.rs.cc:64 (target/debug/build/example-aff44aa21d738c2a/out/cxxbridge/sources/example/src/two.rs.cc:64)
>>> 4be10217b215df70-two.rs.o:(.text.cxxbridge1$unique_ptr$Context$null+0x0) in archive target/debug/build/example-aff44aa21d738c2a/out/libexample-cxx.a| .map_or(key.end_span, |explicit| explicit.brace_token.span.join()); | ||
| let unsafe_token = format_ident!("unsafe", span = begin_span); | ||
| let prevent_unwind_drop_label = format!("::{} as Drop>::drop", ident); | ||
| let prevent_unwind_drop_label = quote! { #inner }.to_string(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not an adequate replacement for what was here before.
#pragma once
#include <rust/cxx.h>
class Thing;
inline void take_thing(rust::Box<Thing>) {}#[cxx::bridge]
mod ffi {
extern "Rust" {
type Thing;
}
unsafe extern "C++" {
include!("example/include/header.h");
fn take_thing(_: Box<Thing>);
}
}
struct Thing;
impl Drop for Thing {
fn drop(&mut self) {
panic!("...");
}
}
fn main() {
ffi::take_thing(Box::new(Thing));
}Before:
panic in ffi function <example::ffi::Thing as Drop>::drop, aborting.After:
panic in ffi function <example::ffiThing, aborting.| #[cfg_attr(not(proc_macro), expect(dead_code))] | ||
| pub gt_token: Option<Token![>]>, | ||
| #[cfg_attr(not(proc_macro), expect(dead_code))] | ||
| /// Mangled form of the `outer` type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment does not match the behavior of NamedImplKey::new.
| /// Mangled form of the `outer` type. | |
| /// Mangled form of the `inner` type. |
| "cxxbridge1$unique_ptr$std$vector${}$", | ||
| resolve.name.to_symbol(), | ||
| ); | ||
| let unique_ptr_prefix = format!("cxxbridge1$unique_ptr$std$vector${}$", key.symbol,); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let unique_ptr_prefix = format!("cxxbridge1$unique_ptr$std$vector${}$", key.symbol,); | |
| let unique_ptr_prefix = format!("cxxbridge1$unique_ptr$std$vector${}$", key.symbol); |
| unsafe extern "C" { | ||
| #[link_name = #link_new] | ||
| fn __vector_new #impl_generics() -> *mut ::cxx::CxxVector<#elem #ty_generics>; | ||
| fn __vector_new #impl_generics() -> *mut ::cxx::CxxVector<#inner>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not going to compile without #ty_generics.
#[cxx::bridge]
mod ffi {
struct Borrowed<'a> {
s: &'a str,
}
extern "Rust" {
fn f(_: &CxxVector<Borrowed>);
}
}
fn f(_: &cxx::CxxVector<ffi::Borrowed>) {}error[E0106]: missing lifetime specifier
--> src/main.rs:7:28
|
7 | fn f(_: &CxxVector<Borrowed>);
| ^^^^^^^^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'a` lifetime
|
7 | fn f(_: &CxxVector<Borrowed<'a>>);
| ++++| #unsafe_token impl #impl_generics ::cxx::memory::SharedPtrTarget for #inner #ty_generics { | ||
| fn __typename(f: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result { | ||
| f.write_str(#name) | ||
| f.write_str(::core::stringify!(#inner)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using stringify for this is not a good approach.
#[cxx::bridge]
pub mod ffi {
struct Context<'a> {
session: &'a str,
}
struct Server<'srv> {
ctx: SharedPtr<Context<'srv>>,
}
struct Client<'clt> {
ctx: SharedPtr<Context<'clt>>,
}
}
fn main() {
let _ = *cxx::SharedPtr::<ffi::Context>::null();
}Before:
called deref on a null SharedPtr<Context>After:
called deref on a null SharedPtr<Context < 'srv >>The spacing is wonky, and this is using a random one of the lifetime names which is going to be misleading for the user if their panic is happening in "client" code that has nothing to do with "server" code.
| (resolved_generics, Some(resolved_generics)) | ||
| } else { | ||
| ( | ||
| explicit_generics, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explicit_generics is never the best set of lifetime names to use for an impl. In this codepath there is no explicit impl, so explicit_generics is populated with lifetime names from some random usage of the generic type in a function signature or struct. In general those lifetime names are not meaningful at all outside the context of that single function or struct. This code should not be propagating them onto an impl block (which is going to become visible in rustdoc).
For example:
struct Context<'sess> {
session: &'sess str,
}
struct Server<'srv> {
ctx: SharedPtr<Context<'srv>>,
}
struct Client<'clt> {
ctx: SharedPtr<Context<'clt>>,
}It does not make sense for the documentation of Context to be rendered as:
impl<'sess> ExternType for Context<'sess>impl<'srv> SharedPtrTarget for Context<'srv>
The correct impl would be impl<'sess> SharedPtrTarget for Context<'sess> (which was generated before this PR).
| Type::RustBox(ty1) => type_(&ty1.inner).map(|s| join!("box", s)), | ||
| Type::RustVec(ty1) => type_(&ty1.inner).map(|s| join!("rust_vec", s)), | ||
| Type::UniquePtr(ty1) => type_(&ty1.inner).map(|s| join!("unique_ptr", s)), | ||
| Type::SharedPtr(ty1) => type_(&ty1.inner).map(|s| join!("shared_ptr", s)), | ||
| Type::WeakPtr(ty1) => type_(&ty1.inner).map(|s| join!("weak_ptr", s)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are all currently unreachable, right?
| Type::RustBox(ty1) => type_(&ty1.inner).map(|s| join!("box", s)), | |
| Type::RustVec(ty1) => type_(&ty1.inner).map(|s| join!("rust_vec", s)), | |
| Type::UniquePtr(ty1) => type_(&ty1.inner).map(|s| join!("unique_ptr", s)), | |
| Type::SharedPtr(ty1) => type_(&ty1.inner).map(|s| join!("shared_ptr", s)), | |
| Type::WeakPtr(ty1) => type_(&ty1.inner).map(|s| join!("weak_ptr", s)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, correct.
PTAL?
If this PR looks okay, then the next commit/PR (anforowicz@8ab4ecc) should be able to add support for
Vec<Box<T>>(because of howBox<T>is special when evaluating the orphan rule./cc @zetafunction who has kindly provided initial feedback/review at anforowicz#4