diff --git a/.bazelrc b/.bazelrc index f6ec1aba3..11220b783 100644 --- a/.bazelrc +++ b/.bazelrc @@ -2,3 +2,4 @@ build --enable_platform_specific_config build:linux --@rules_rust//:extra_rustc_flags=-Clink-arg=-fuse-ld=lld build:linux --cxxopt=-std=c++17 build:macos --cxxopt=-std=c++17 +build:windows --cxxopt=/std:c++17 diff --git a/.vscode/settings.json b/.vscode/settings.json index 8a1c2c130..4e81ec6c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { "search.exclude": { "**/target": true + }, + "files.associations": { + "variant": "cpp", + "string": "cpp" } } diff --git a/demo/build.rs b/demo/build.rs index c1b55cc2b..8e1ba23db 100644 --- a/demo/build.rs +++ b/demo/build.rs @@ -1,7 +1,9 @@ fn main() { cxx_build::bridge("src/main.rs") .file("src/blobstore.cc") - .flag_if_supported("-std=c++14") + .flag_if_supported("-std=c++17") + .flag_if_supported("/std:c++17") + .flag_if_supported("/Zc:__cplusplus") .compile("cxxbridge-demo"); println!("cargo:rerun-if-changed=src/main.rs"); diff --git a/demo/include/blobstore.h b/demo/include/blobstore.h index d89583aa9..f8634353b 100644 --- a/demo/include/blobstore.h +++ b/demo/include/blobstore.h @@ -7,6 +7,7 @@ namespace blobstore { struct MultiBuf; struct BlobMetadata; +struct BlobEnum; class BlobstoreClient { public: @@ -22,5 +23,9 @@ class BlobstoreClient { std::unique_ptr new_blobstore_client(); +BlobEnum make_enum(); +void take_enum(const BlobEnum&); +void take_mut_enum(BlobEnum&); + } // namespace blobstore } // namespace org diff --git a/demo/src/blobstore.cc b/demo/src/blobstore.cc index 7cf40dfe3..90d1e8c0f 100644 --- a/demo/src/blobstore.cc +++ b/demo/src/blobstore.cc @@ -2,6 +2,7 @@ #include "demo/src/main.rs.h" #include #include +#include #include #include #include @@ -67,5 +68,30 @@ std::unique_ptr new_blobstore_client() { return std::make_unique(); } +BlobEnum make_enum() { return BlobEnum{false}; } + +std::ostream &operator<<(std::ostream &os, const BlobMetadata &md) { + os << "The size [" << md.size << "] and some tags..."; + return os; +} + +void take_enum(const BlobEnum &enm) { + std::cout << "The index of enum is " << enm.index() << std::endl; + rust::visit( + [](const auto &v) { + std::cout << "The value of enum is " << v << std::endl; + }, + enm); +} + +void take_mut_enum(BlobEnum &enm) { + take_enum(enm); + if (!::rust::holds_alternative(enm)) { + enm = false; + } else { + enm = 111; + } +} + } // namespace blobstore } // namespace org diff --git a/demo/src/main.rs b/demo/src/main.rs index 458f1f211..d6d304a40 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -6,6 +6,20 @@ mod ffi { tags: Vec, } + /// A classic. + enum BlobEnum { + /// This is my doc + Bar(i32), + Baz(bool), + Bam(BlobMetadata), + } + + enum BlobCLike { + Bar, + Baz, + Bam, + } + // Rust types and signatures exposed to C++. extern "Rust" { type MultiBuf; @@ -23,6 +37,10 @@ mod ffi { fn put(&self, parts: &mut MultiBuf) -> u64; fn tag(&self, blobid: u64, tag: &str); fn metadata(&self, blobid: u64) -> BlobMetadata; + + fn make_enum() -> BlobEnum; + fn take_enum(enm: &BlobEnum); + fn take_mut_enum(enm: &mut BlobEnum); } } @@ -42,6 +60,21 @@ pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { } fn main() { + let f = ffi::BlobEnum::Bar(1); + ffi::take_enum(&f); + let mut f = ffi::make_enum(); + match f { + ffi::BlobEnum::Bar(val) => println!("The value is {val}"), + ffi::BlobEnum::Baz(val) => println!("The value is {val}"), + _ => {} + } + ffi::take_mut_enum(&mut f); + match f { + ffi::BlobEnum::Bar(val) => println!("The value is {val}"), + ffi::BlobEnum::Baz(val) => println!("The value is {val}"), + _ => {} + } + let client = ffi::new_blobstore_client(); // Upload a blob. diff --git a/gen/src/cfg.rs b/gen/src/cfg.rs index adab6e5c2..5f3417ca8 100644 --- a/gen/src/cfg.rs +++ b/gen/src/cfg.rs @@ -29,7 +29,7 @@ pub(super) fn strip( Api::Struct(strct) => strct .fields .retain(|field| eval(cx, cfg_errors, cfg_evaluator, &field.cfg)), - Api::Enum(enm) => enm + Api::Enum(enm, _) | Api::EnumUnnamed(enm) => enm .variants .retain(|variant| eval(cx, cfg_errors, cfg_evaluator, &variant.cfg)), _ => {} @@ -113,7 +113,7 @@ impl Api { match self { Api::Include(include) => &include.cfg, Api::Struct(strct) => &strct.cfg, - Api::Enum(enm) => &enm.cfg, + Api::Enum(enm, _) | Api::EnumUnnamed(enm) => &enm.cfg, Api::CxxType(ety) | Api::RustType(ety) => &ety.cfg, Api::CxxFunction(efn) | Api::RustFunction(efn) => &efn.cfg, Api::TypeAlias(alias) => &alias.cfg, diff --git a/gen/src/namespace.rs b/gen/src/namespace.rs index 424e9d8e2..bbe61cc65 100644 --- a/gen/src/namespace.rs +++ b/gen/src/namespace.rs @@ -6,7 +6,7 @@ impl Api { match self { Api::CxxFunction(efn) | Api::RustFunction(efn) => &efn.name.namespace, Api::CxxType(ety) | Api::RustType(ety) => &ety.name.namespace, - Api::Enum(enm) => &enm.name.namespace, + Api::Enum(enm, _) | Api::EnumUnnamed(enm) => &enm.name.namespace, Api::Struct(strct) => &strct.name.namespace, Api::Impl(_) | Api::Include(_) | Api::TypeAlias(_) => Default::default(), } diff --git a/gen/src/write.rs b/gen/src/write.rs index 89037e16f..0f98ce91f 100644 --- a/gen/src/write.rs +++ b/gen/src/write.rs @@ -9,8 +9,8 @@ use crate::syntax::set::UnorderedSet; use crate::syntax::symbol::{self, Symbol}; use crate::syntax::trivial::{self, TrivialReason}; use crate::syntax::{ - derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, Struct, Trait, - Type, TypeAlias, Types, Var, + derive, mangle, Api, CEnumOpts, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, + Struct, Trait, Type, TypeAlias, Types, Var, }; use proc_macro2::Ident; @@ -34,8 +34,8 @@ pub(super) fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec true, - Api::Enum(enm) => !out.types.cxx.contains(&enm.name.rust), + Api::Struct(_) | Api::CxxType(_) | Api::RustType(_) | Api::EnumUnnamed(_) => true, + Api::Enum(enm, _) => !out.types.cxx.contains(&enm.name.rust), _ => false, }; @@ -46,12 +46,12 @@ fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) { fn write(out: &mut OutFile, ns_entries: &NamespaceEntries, indent: usize) { let apis = ns_entries.direct_content(); - for api in apis { write!(out, "{:1$}", "", indent); match api { Api::Struct(strct) => write_struct_decl(out, &strct.name), - Api::Enum(enm) => write_enum_decl(out, enm), + Api::Enum(enm, opts) => write_enum_decl(out, enm, opts), + Api::EnumUnnamed(enm) => write_struct_decl(out, &enm.name), Api::CxxType(ety) => write_struct_using(out, &ety.name), Api::RustType(ety) => write_struct_decl(out, &ety.name), _ => unreachable!(), @@ -99,14 +99,18 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { } } } - Api::Enum(enm) => { + Api::Enum(enm, opts) => { out.next_section(); if !out.types.cxx.contains(&enm.name.rust) { - write_enum(out, enm); - } else if !enm.variants_from_header { - check_enum(out, enm); + write_enum(out, enm, opts); + } else if !opts.variants_from_header { + check_enum(out, enm, opts); } } + Api::EnumUnnamed(enm) => { + out.next_section(); + write_enum_unnamed(out, enm); + } Api::RustType(ety) => { out.next_section(); let methods = methods_for_type @@ -328,8 +332,8 @@ fn write_struct_decl(out: &mut OutFile, ident: &Pair) { writeln!(out, "struct {};", ident.cxx); } -fn write_enum_decl(out: &mut OutFile, enm: &Enum) { - let repr = match &enm.repr { +fn write_enum_decl(out: &mut OutFile, enm: &Enum, opts: &CEnumOpts) { + let repr = match &opts.repr { #[cfg(feature = "experimental-enum-variants-from-header")] EnumRepr::Foreign { .. } => return, EnumRepr::Native { atom, .. } => *atom, @@ -339,6 +343,56 @@ fn write_enum_decl(out: &mut OutFile, enm: &Enum) { writeln!(out, ";"); } +fn write_enum_unnamed(out: &mut OutFile, enm: &Enum) { + write!(out, "struct {} final : public ", enm.name.cxx); + + /// Writes something like `::rust::variant` with type1... + /// being cxx types of the enum's variants. + fn write_variants(out: &mut OutFile, enm: &Enum) { + write!(out, "::rust::variant<"); + let mut iter = enm.variants.iter().peekable(); + while let Some(value) = iter.next() { + match &value.ty { + None => { + write!(out, "::rust::empty"); + } + Some(ty) => { + match ty { + Type::Ref(r) => { + // References are not allowed in variants, so we wrap them + // into std::reference_wrapper. + write!(out, "std::reference_wrapper<"); + write_type_space(out, &r.inner); + if !r.mutable { + write!(out, "const "); + } + write!(out, ">"); + } + _ => write_type(out, ty), + } + } + }; + if iter.peek().is_some() { + write!(out, ", "); + } + } + write!(out, ">"); + } + + write_variants(out, enm); + writeln!(out, "{{"); + + write!(out, " using base = "); + write_variants(out, enm); + writeln!(out, ";"); + writeln!(out, " {}() = delete;", enm.name.cxx); + writeln!(out, " {0}(const {0}&) = default;", enm.name.cxx); + writeln!(out, " {0}({0}&&) = delete;", enm.name.cxx); + writeln!(out, " using base::base;"); + writeln!(out, " using base::operator=;"); + writeln!(out, "}};"); +} + fn write_struct_using(out: &mut OutFile, ident: &Pair) { writeln!(out, "using {} = {};", ident.cxx, ident.to_fully_qualified()); } @@ -388,8 +442,8 @@ fn write_opaque_type<'a>(out: &mut OutFile<'a>, ety: &'a ExternType, methods: &[ writeln!(out, "#endif // {}", guard); } -fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { - let repr = match &enm.repr { +fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum, opts: &'a CEnumOpts) { + let repr = match &opts.repr { #[cfg(feature = "experimental-enum-variants-from-header")] EnumRepr::Foreign { .. } => return, EnumRepr::Native { atom, .. } => *atom, @@ -410,8 +464,8 @@ fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { writeln!(out, "#endif // {}", guard); } -fn check_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { - let repr = match &enm.repr { +fn check_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum, opts: &'a CEnumOpts) { + let repr = match &opts.repr { #[cfg(feature = "experimental-enum-variants-from-header")] EnumRepr::Foreign { .. } => return, EnumRepr::Native { atom, .. } => *atom, @@ -838,7 +892,8 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { write!(out, "(::rust::unsafe_bitcopy, *{})", arg.name.cxx); } else if out.types.needs_indirect_abi(&arg.ty) { out.include.utility = true; - write!(out, "::std::move(*{})", arg.name.cxx); + // Let the compiler resolve if this is a copy or a move constructor. + write!(out, "*{}", arg.name.cxx); } else { write!(out, "{}", arg.name.cxx); } diff --git a/include/cxx.h b/include/cxx.h index 002282551..cd6cf8ca8 100644 --- a/include/cxx.h +++ b/include/cxx.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,17 @@ #include #include #include +// If you're using enums and variants on windows, you need to pass also +// `/Zc:__cplusplus` as a compiler to make __cplusplus work correctly. If users +// ever report that they have a too old compiler to `/Zc:__cplusplus` we may +// fallback to the `_MSVC_LANG` define. +// +// Sources: +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +// https://learn.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 +#if __cplusplus >= 201703L +#include +#endif #include #if defined(_WIN32) #include @@ -53,8 +65,8 @@ class String final { static String lossy(const char16_t *) noexcept; static String lossy(const char16_t *, std::size_t) noexcept; - String &operator=(const String &) &noexcept; - String &operator=(String &&) &noexcept; + String &operator=(const String &) & noexcept; + String &operator=(String &&) & noexcept; explicit operator std::string() const; @@ -113,7 +125,7 @@ class Str final { Str(const char *); Str(const char *, std::size_t); - Str &operator=(const Str &) &noexcept = default; + Str &operator=(const Str &) & noexcept = default; explicit operator std::string() const; @@ -161,8 +173,8 @@ template <> struct copy_assignable_if { copy_assignable_if() noexcept = default; copy_assignable_if(const copy_assignable_if &) noexcept = default; - copy_assignable_if &operator=(const copy_assignable_if &) &noexcept = delete; - copy_assignable_if &operator=(copy_assignable_if &&) &noexcept = default; + copy_assignable_if &operator=(const copy_assignable_if &) & noexcept = delete; + copy_assignable_if &operator=(copy_assignable_if &&) & noexcept = default; }; } // namespace detail @@ -176,8 +188,8 @@ class Slice final Slice() noexcept; Slice(T *, std::size_t count) noexcept; - Slice &operator=(const Slice &) &noexcept = default; - Slice &operator=(Slice &&) &noexcept = default; + Slice &operator=(const Slice &) & noexcept = default; + Slice &operator=(Slice &&) & noexcept = default; T *data() const noexcept; std::size_t size() const noexcept; @@ -265,7 +277,7 @@ class Box final { explicit Box(const T &); explicit Box(T &&); - Box &operator=(Box &&) &noexcept; + Box &operator=(Box &&) & noexcept; const T *operator->() const noexcept; const T &operator*() const noexcept; @@ -310,7 +322,7 @@ class Vec final { Vec(Vec &&) noexcept; ~Vec() noexcept; - Vec &operator=(Vec &&) &noexcept; + Vec &operator=(Vec &&) & noexcept; Vec &operator=(const Vec &) &; std::size_t size() const noexcept; @@ -364,6 +376,542 @@ class Vec final { }; #endif // CXXBRIDGE1_RUST_VEC +#ifndef CXXBRIDGE1_RUST_VARIANT +#define CXXBRIDGE1_RUST_VARIANT + +#if __cplusplus >= 201703L + +// Adjusted from std::variant_alternative. Standard selects always the most +// specialized template specialization. See +// https://timsong-cpp.github.io/cppwp/n4140/temp.class.spec.match and +// https://timsong-cpp.github.io/cppwp/n4140/temp.class.order. +template +struct variant_alternative; + +// Specialization for gracefully handling invalid indices. +template +struct variant_alternative {}; + +template +struct variant_alternative + : variant_alternative {}; + +template +struct variant_alternative<0, First, Remainder...> { + using type = First; +}; + +template +using variant_alternative_t = typename variant_alternative::type; + +template +constexpr size_t compile_time_count() noexcept { + return 0 + (static_cast(Values) + ...); +} + +template +struct count + : std::integral_constant()> {}; + +template +struct exactly_once : std::conditional_t::value == 1, + std::true_type, std::false_type> {}; + +template +struct index_from_booleans; + +template +struct index_from_booleans {}; + +template +struct index_from_booleans + : index_from_booleans {}; + +template +struct index_from_booleans + : std::integral_constant {}; + +template +struct index_from_type + : index_from_booleans<0, std::is_same_v, Ts>...> { + static_assert(exactly_once, Ts>...>::value, + "Index must be unique"); +}; + +template +struct visitor_type; + +template +struct variant_base; + +template +constexpr decltype(auto) get(variant_base &); + +template +constexpr decltype(auto) get(const variant_base &); + +template +constexpr decltype(auto) visit(Visitor &&visitor, variant_base &); + +template +constexpr decltype(auto) visit(Visitor &&visitor, const variant_base &); + +/// @brief A std::variant like tagged union with the same memory layout as a +/// Rust Enum. +/// +/// The memory layout of the Rust enum is defined under +/// https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields +template +struct variant_base { + static_assert(sizeof...(Ts) > 0, + "variant_base must hold at least one alternative"); + + /// @brief Delete the default constructor since we cannot be in an + /// uninitialized state (if the first alternative throws). Corresponds to the + /// (1) constructor in std::variant. + variant_base() = delete; + + constexpr static bool all_copy_constructible_v = + std::conjunction_v...>; + + /// @brief Copy constructor. Participates only in the resolution if all types + /// are copy constructable. Corresponds to (2) constructor of std::variant. + variant_base(const variant_base &other) { + static_assert( + all_copy_constructible_v, + "Copy constructor requires that all types are copy constructable"); + + m_Index = other.m_Index; + visit( + [this](const auto &value) { + using type = std::decay_t; + new (static_cast(m_Buff)) type(value); + }, + other); + }; + + /// @brief Delete the move constructor since if we move this container it's + /// unclear in which state it is. Corresponds to (3) constructor of + /// std::variant. + variant_base(variant_base &&other) = delete; + + template + constexpr static bool is_unique_v = + exactly_once>...>::value; + + template + constexpr static std::size_t index_from_type_v = + index_from_type::value; + + /// @brief Converting constructor. Corresponds to (4) constructor of + /// std::variant. + template , + typename = std::enable_if_t && + std::is_constructible_v>> + variant_base(T &&other) noexcept(std::is_nothrow_constructible_v) { + m_Index = index_from_type_v; + new (static_cast(m_Buff)) D(std::forward(other)); + } + + /// @brief Participates in the resolution only if we can construct T from Args + /// and if T is unique in Ts. Corresponds to (5) constructor of std::variant. + template >, + typename = std::enable_if_t>> + explicit variant_base(std::in_place_type_t type, Args &&...args) noexcept( + std::is_nothrow_constructible_v) + : variant_base{std::in_place_index>, + std::forward(args)...} {} + + template + using type_from_index_t = variant_alternative_t; + + /// @brief Participates in the resolution only if the index is within range + /// and if the type can be constructor from Args. Corresponds to (7) of + /// std::variant. + template , + typename = std::enable_if_t>> + explicit variant_base( + std::in_place_index_t index, + Args &&...args) noexcept(std::is_nothrow_constructible_v) { + m_Index = I; + new (static_cast(m_Buff)) T(std::forward(args)...); + } + + template + constexpr static bool all_same_v = + std::conjunction_v...>; + + /// @brief Converts the std::variant to our variant. Participates only in + /// the resolution if all types in Ts are copy constructable. + template && all_copy_constructible_v>> + variant_base(const std::variant &other) { + m_Index = other.index(); + std::visit( + [this](const auto &value) { + using type = std::decay_t; + new (static_cast(m_Buff)) type(value); + }, + other); + } + + constexpr static bool all_move_constructible_v = + std::conjunction_v...>; + + /// @brief Converts the std::variant to our variant. Participates only in + /// the resolution if all types in Ts are move constructable. + template && all_move_constructible_v>> + variant_base(std::variant &&other) { + m_Index = other.index(); + std::visit( + [this](auto &&value) { + using type = std::decay_t; + new (static_cast(m_Buff)) type(std::move(value)); + }, + other); + } + + ~variant_base() { destroy(); } + + /// @brief Copy assignment. Staticly fails if not every type in Ts is copy + /// constructable. Corresponds to (1) assignment of std::variant. + variant_base &operator=(const variant_base &other) { + static_assert( + all_copy_constructible_v, + "Copy assignment requires that all types are copy constructable"); + + visit([this](const auto &value) { *this = value; }, other); + + return *this; + }; + + /// @brief Deleted move assignment. Same as for the move constructor. + /// Would correspond to (2) assignment of std::variant. + variant_base &operator=(variant_base &&other) = delete; + + /// @brief Converting assignment. Corresponds to (3) assignment of + /// std::variant. + template && std::is_constructible_v>> + variant_base &operator=(T &&other) { + constexpr auto index = index_from_type_v; + + if (m_Index == index) { + if constexpr (std::is_nothrow_assignable_v) { + get(*this) = std::forward(other); + return *this; + } + } + this->emplace>(std::forward(other)); + return *this; + } + + /// @brief Converting assignment from std::variant. Participates only in the + /// resolution if all types in Ts are copy constructable. + template && all_copy_constructible_v>> + variant_base &operator=(const std::variant &other) { + // TODO this is not really clean since we fail if std::variant has + // duplicated types. + std::visit( + [this](const auto &value) { + using type = decltype(value); + emplace>(value); + }, + other); + return *this; + } + + /// @brief Converting assignment from std::variant. Participates only in the + /// resolution if all types in Ts are move constructable. + template && all_move_constructible_v>> + variant_base &operator=(std::variant &&other) { + // TODO this is not really clean since we fail if std::variant has + // duplicated types. + std::visit( + [this](auto &&value) { + using type = decltype(value); + emplace>(std::move(value)); + }, + other); + return *this; + } + + /// @brief Emplace function. Participates in the resolution only if T is + /// unique in Ts and if T can be constructed from Args. Offers strong + /// exception guarantee. Corresponds to the (1) emplace function of + /// std::variant. + template >, + typename = std::enable_if_t>> + T &emplace(Args &&...args) { + constexpr std::size_t index = index_from_type_v; + return this->emplace(std::forward(args)...); + } + + /// @brief Emplace function. Participates in the resolution only if T can be + /// constructed from Args. Offers strong exception guarantee. Corresponds to + /// the (2) emplace function of std::variant. + /// + /// The std::variant can have no valid state if the type throws during the + /// construction. This is represented by the `valueless_by_exception` flag. + /// The same approach is also used in absl::variant [2]. + /// In our case we can't accept valueless enums since we can't represent this + /// in Rust. We must therefore provide a strong exception guarantee for all + /// operations using `emplace`. Two famous implementations of never valueless + /// variants are Boost/variant [3] and Boost/variant2 [4]. Boost/variant2 uses + /// two memory buffers - which would be not compatible with Rust Enum's memory + /// layout. The Boost/variant backs up the old object and calls its d'tor + /// before constructing the new object; It then copies the old data back to + /// the buffer if the construction fails - which might contain garbage (since) + /// the d'tor was already called. + /// + /// + /// We take a similar approach to Boost/variant. Assuming that constructing or + /// moving the new type can throw, we backup the old data, try to construct + /// the new object in the final buffer, swap the buffers, such that the old + /// object is back in its original place, detroy it and move the new object + /// from the old buffer back to the final place. + /// + /// Sources + /// + /// [1] + /// https://en.cppreference.com/w/cpp/utility/variant/valueless_by_exception + /// [2] + /// https://github.com/abseil/abseil-cpp/blob/master/absl/types/variant.h + /// [3] + /// https://www.boost.org/doc/libs/1_84_0/libs/variant2/doc/html/variant2.html + /// [4] + /// https://www.boost.org/doc/libs/1_84_0/doc/html/variant/design.html#variant.design.never-empty + template , + typename = std::enable_if_t>> + T &emplace(Args &&...args) { + if constexpr (std::is_nothrow_constructible_v) { + destroy(); + new (static_cast(m_Buff)) T(std::forward(args)...); + } else if constexpr (std::is_nothrow_move_constructible_v) { + // This operation may throw, but we know that the move does not. + const T tmp{std::forward(args)...}; + + // The operations below are save. + destroy(); + new (static_cast(m_Buff)) T(std::move(tmp)); + } else { + // Backup the old data. + alignas(Ts...) std::byte old_buff[std::max({sizeof(Ts)...})]; + std::memcpy(old_buff, m_Buff, sizeof(m_Buff)); + + try { + // Try to construct the new object + new (static_cast(m_Buff)) T(std::forward(args)...); + } catch (...) { + // Restore the old buffer + std::memcpy(m_Buff, old_buff, sizeof(m_Buff)); + throw; + } + // Fetch the old buffer and detroy it. + std::swap_ranges(m_Buff, m_Buff + sizeof(m_Buff), old_buff); + + destroy(); + std::memcpy(m_Buff, old_buff, sizeof(m_Buff)); + } + + m_Index = I; + return get(*this); + } + + constexpr std::size_t index() const noexcept { return m_Index; } + void swap(variant_base &other) { + // TODO + } + + struct my_bad_variant_access : std::runtime_error { + my_bad_variant_access(std::size_t index) + : std::runtime_error{"The index should be " + std::to_string(index)} {} + }; + +private: + template + void throw_if_invalid() const { + static_assert(I < (sizeof...(Ts)), "Invalid index"); + + if (m_Index != I) + throw my_bad_variant_access(m_Index); + } + + void destroy() { + visit( + [](const auto &value) { + using type = std::decay_t; + value.~type(); + }, + *this); + } + + // The underlying type is not fixed, but should be int - which we will verify + // statically. See + // https://timsong-cpp.github.io/cppwp/n4659/dcl.enum#7 + int m_Index; + + // std::aligned_storage is deprecated and may be replaced with the construct + // below. See + // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf + alignas(Ts...) std::byte m_Buff[std::max({sizeof(Ts)...})]; + + // The friend zone + template + friend constexpr decltype(auto) get(variant_base &variant); + + template + friend constexpr decltype(auto) get(const variant_base &variant); + + template + friend struct visitor_type; +}; + +template +struct visitor_type { + template + constexpr static decltype(auto) visit(Visitor &&visitor, Variant &&variant) { + return visit(std::forward(visitor), variant.m_Index, + variant.m_Buff); + } + + /// @brief The visit method which will pick the right type depending on the + /// `index` value. + template + constexpr static auto visit(Visitor &&visitor, std::size_t index, + std::byte *data) + -> decltype(visitor(*reinterpret_cast(data))) { + if (index == 0) { + return visitor(*reinterpret_cast(data)); + } + if constexpr (sizeof...(Remainder) != 0) { + return visitor_type::visit(std::forward(visitor), + --index, data); + } + throw std::out_of_range("invalid"); + } + + template + constexpr static auto visit(Visitor &&visitor, std::size_t index, + const std::byte *data) + -> decltype(visitor(*reinterpret_cast(data))) { + if (index == 0) { + return visitor(*reinterpret_cast(data)); + } + if constexpr (sizeof...(Remainder) != 0) { + return visitor_type::visit(std::forward(visitor), + --index, data); + } + throw std::out_of_range("invalid"); + } +}; + +/// @brief Applies the visitor to the variant. Corresponds to the (3) +/// std::visit defintion. +template +constexpr decltype(auto) visit(Visitor &&visitor, + variant_base &variant) { + return visitor_type::visit(std::forward(visitor), variant); +} + +/// @brief Applies the visitor to the variant. Corresponds to the (4) +/// std::visit defintion. +template +constexpr decltype(auto) visit(Visitor &&visitor, + const variant_base &variant) { + return visitor_type::visit(std::forward(visitor), variant); +} + +template +constexpr decltype(auto) get(variant_base &variant) { + variant.template throw_if_invalid(); + return *reinterpret_cast *>(variant.m_Buff); +} + +template +constexpr decltype(auto) get(const variant_base &variant) { + variant.template throw_if_invalid(); + return *reinterpret_cast *>( + variant.m_Buff); +} + +template >...>::value>> +constexpr const T &get(const variant_base &variant) { + constexpr auto index = index_from_type::value; + return get(variant); +} + +template >...>::value>> +constexpr T &get(variant_base &variant) { + constexpr auto index = index_from_type::value; + return get(variant); +} + +template +constexpr bool holds_alternative(const variant_base &variant) { + return variant.index() == I; +} + +template >...>::value>> +constexpr bool holds_alternative(const variant_base &variant) { + return variant.index() == index_from_type::value; +} + +template +struct copy_control; + +template <> +struct copy_control { + copy_control() = default; + copy_control(const copy_control &other) = default; + copy_control &operator=(const copy_control &) = default; +}; + +template <> +struct copy_control { + copy_control() = default; + copy_control(const copy_control &other) = delete; + copy_control &operator=(const copy_control &) = delete; +}; + +template +using allow_copy = + copy_control...>>; + +template +struct variant : public variant_base, private allow_copy { + using base = variant_base; + + variant() = delete; + variant(const variant &) = default; + variant(variant &&) = delete; + + using base::base; + + variant &operator=(const variant &) = default; + variant &operator=(variant &&) = delete; + + using base::operator=; +}; + +/// An empty type used for unit variants from Rust. +struct empty {}; + +#endif + +#endif + #ifndef CXXBRIDGE1_RUST_FN // https://cxx.rs/binding/fn.html template @@ -391,7 +939,7 @@ class Error final : public std::exception { ~Error() noexcept override; Error &operator=(const Error &) &; - Error &operator=(Error &&) &noexcept; + Error &operator=(Error &&) & noexcept; const char *what() const noexcept override; @@ -763,7 +1311,7 @@ Box::~Box() noexcept { } template -Box &Box::operator=(Box &&other) &noexcept { +Box &Box::operator=(Box &&other) & noexcept { if (this->ptr) { this->drop(); } @@ -851,7 +1399,7 @@ Vec::~Vec() noexcept { } template -Vec &Vec::operator=(Vec &&other) &noexcept { +Vec &Vec::operator=(Vec &&other) & noexcept { this->drop(); this->repr = other.repr; new (&other) Vec(); diff --git a/macro/src/derive.rs b/macro/src/derive.rs index a439bf907..acb7c20b7 100644 --- a/macro/src/derive.rs +++ b/macro/src/derive.rs @@ -97,6 +97,37 @@ pub(crate) fn expand_enum(enm: &Enum, actual_derives: &mut Option) expanded } +pub(crate) fn expand_enum_unnamed( + enm: &Enum, + actual_derives: &mut Option, +) -> TokenStream { + let mut traits = Vec::new(); + + // I don't get it why we're using everywhere self written derives. For now + // I just use the build ins until someone complains. + for derive in &enm.derives { + let span = derive.span; + match derive.what { + Trait::Copy => traits.push(quote_spanned!(span=> Copy)), + Trait::Clone => traits.push(quote_spanned!(span=> Clone)), + Trait::Eq => traits.push(quote_spanned!(span=> Eq)), + Trait::PartialEq => traits.push(quote_spanned!(span=> PartialEq)), + Trait::Debug => traits.push(quote_spanned!(span=> Debug)), + Trait::Default => unreachable!(), + Trait::ExternType => unreachable!(), + Trait::Hash => traits.push(quote_spanned!(span=> ::cxx::core::hash::Hash)), + Trait::Ord => traits.push(quote_spanned!(span=> Ord)), + Trait::PartialOrd => traits.push(quote_spanned!(span=> PartialOrd)), + Trait::Serialize => traits.push(quote_spanned!(span=> ::serde::Serialize)), + Trait::Deserialize => traits.push(quote_spanned!(span=> ::serde::Deserialize)), + } + } + + *actual_derives = Some(quote!(#[derive(#(#traits),*)])); + + TokenStream::new() +} + fn struct_copy(strct: &Struct, span: Span) -> TokenStream { let ident = &strct.name.rust; let generics = &strct.generics; diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 8a0db43fb..e44ec11d0 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -7,8 +7,8 @@ use crate::syntax::qualified::QualifiedName; use crate::syntax::report::Errors; use crate::syntax::symbol::Symbol; use crate::syntax::{ - self, check, mangle, Api, Doc, Enum, ExternFn, ExternType, Impl, Lifetimes, Pair, Signature, - Struct, Trait, Type, TypeAlias, Types, + self, check, mangle, Api, CEnumOpts, Doc, Enum, ExternFn, ExternType, Impl, Lifetimes, Pair, + Signature, Struct, Trait, Type, TypeAlias, Types, }; use crate::type_id::Crate; use crate::{derive, generics}; @@ -68,7 +68,10 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types) hidden.extend(expand_struct_operators(strct)); forbid.extend(expand_struct_forbid_drop(strct)); } - Api::Enum(enm) => expanded.extend(expand_enum(enm)), + Api::Enum(enm, opts) => expanded.extend(expand_enum(enm, opts)), + Api::EnumUnnamed(enm) => { + expanded.extend(expand_enum_unnamed(enm)); + } Api::CxxType(ety) => { let ident = &ety.name.rust; if !types.structs.contains_key(ident) && !types.enums.contains_key(ident) { @@ -317,11 +320,67 @@ fn expand_struct_forbid_drop(strct: &Struct) -> TokenStream { } } -fn expand_enum(enm: &Enum) -> TokenStream { +fn expand_enum_unnamed(enm: &Enum) -> TokenStream { + let ident = &enm.name.rust; + let doc = &enm.doc; + let attrs = &enm.attrs; + let generics = &enm.generics; + + let variants = enm.variants.iter().map(|variant| { + let doc = &variant.doc; + let attrs = &variant.attrs; + let ident = &variant.name.rust; + + match &variant.ty { + None => { + quote!(#doc #attrs #ident) + } + Some(ty) => { + let vis = &variant.vis; + // We add the visibility if defined here and let it crash since the doc + // says that the syntax allows visibility specifiers but they should be + // rejected. See + // https://doc.rust-lang.org/reference/items/enumerations.html#variant-visibility + quote!(#doc #attrs #ident(#vis #ty)) + } + } + }); + let mut derives = None; + let derived_traits = derive::expand_enum_unnamed(enm, &mut derives); + + let span = ident.span(); + let visibility = enm.visibility; + let enum_token = enm.enum_token; + let type_id = type_id(&enm.name); + let enum_def = quote_spanned! {span=> + #visibility #enum_token #ident #generics { + #(#variants,)* + } + }; + + quote! { + #doc + #derives + #attrs + #[repr(C)] + #enum_def + + unsafe impl #generics ::cxx::ExternType for #ident #generics { + #[allow(unused_attributes)] // incorrect lint + #[doc(hidden)] + type Id = #type_id; + type Kind = ::cxx::kind::Trivial; + } + + #derived_traits + } +} + +fn expand_enum(enm: &Enum, opts: &CEnumOpts) -> TokenStream { let ident = &enm.name.rust; let doc = &enm.doc; let attrs = &enm.attrs; - let repr = &enm.repr; + let repr = &opts.repr; let type_id = type_id(&enm.name); let variants = enm.variants.iter().map(|variant| { let doc = &variant.doc; diff --git a/src/cxx.cc b/src/cxx.cc index 2522d61aa..3dc6a20bf 100644 --- a/src/cxx.cc +++ b/src/cxx.cc @@ -1,6 +1,7 @@ #include "../include/cxx.h" #include #include +#include #include #include @@ -186,7 +187,7 @@ String String::lossy(const char16_t *s, std::size_t len) noexcept { return String(lossy_t{}, s, len); } -String &String::operator=(const String &other) &noexcept { +String &String::operator=(const String &other) & noexcept { if (this != &other) { cxxbridge1$string$drop(this); cxxbridge1$string$clone(this, other); @@ -194,7 +195,7 @@ String &String::operator=(const String &other) &noexcept { return *this; } -String &String::operator=(String &&other) &noexcept { +String &String::operator=(String &&other) & noexcept { cxxbridge1$string$drop(this); this->repr = other.repr; cxxbridge1$string$new(&other); @@ -449,6 +450,232 @@ static_assert(!std::is_same::const_iterator, Vec::iterator>::value, "Vec::const_iterator != Vec::iterator"); + +#if __cplusplus >= 201703L +/// @brief Below static_asserts for out variant. +/// +/// We go quite overboard with the tests since the variant has some intricate +/// function resolution based on the types it can hold. +namespace detail { + +/// @brief A type which can only be move-constructed/assigned but not copied. +/// +/// If it's part of the variant's types the variant cannot not be copy +/// constructed. The variant can be constructed/assigned with an rvalue of this +/// type. +struct MoveType { + MoveType() = default; + MoveType(MoveType &&other) = default; + MoveType(const MoveType &other) = delete; + + MoveType &operator=(const MoveType &other) = delete; + MoveType &operator=(MoveType &&other) = default; +}; + +/// @brief A type which can only be copy-constructed/assigned but not moved. +/// +/// If every type in the variant can be copy constructed, then the variant can +/// be copy constructed. The variant it can be constructed/assigned with a +/// lvalue of this type. +struct CopyType { + CopyType() = default; + CopyType(const CopyType &other) = default; + CopyType(CopyType &&other) = delete; + CopyType(int, std::string) {} + + CopyType &operator=(const CopyType &other) = default; + CopyType &operator=(CopyType &&other) = delete; +}; + +/// @brief A type which can be copy and move constructed/assigned. +/// +/// If every type in the variant can be copy constructed, then the variant can +/// be copy constructed. The variant it can be constructed/assigned with this +/// type. +struct CopyAndMoveType { + CopyAndMoveType() = default; + CopyAndMoveType(const CopyAndMoveType &other) = default; + CopyAndMoveType(CopyAndMoveType &&other) = default; + + CopyAndMoveType &operator=(const CopyAndMoveType &other) = default; + CopyAndMoveType &operator=(CopyAndMoveType &&other) = default; +}; + + +/// @brief A type which can be neither copy nor move constructed/assigned. +/// +/// If it's part of the variant's types the variant cannot not be copy +/// constructed. The variant can not constructed/assigned with any value of this +/// type - but you can emplace it. +struct NoCopyAndNoMoveType { + NoCopyAndNoMoveType() = default; + NoCopyAndNoMoveType(const NoCopyAndNoMoveType &other) = delete; + NoCopyAndNoMoveType(NoCopyAndNoMoveType &&other) = delete; + + NoCopyAndNoMoveType &operator=(const NoCopyAndNoMoveType &other) = delete; + NoCopyAndNoMoveType &operator=(NoCopyAndNoMoveType &&other) = delete; +}; + +// Checks with a variant containing all types. +using all_variant = + variant; + +// Check of copy and move construction/assignment. +static_assert(!std::is_default_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_copy_assignable_v); +static_assert(!std::is_move_assignable_v); + +// Checks for converting construction/assignment. For every type we check +// construction by value, move, ref, const ref, in_place_type_t and +// in_place_index_t. The last two should always work. + +// Checks for the first type (MoveType). We expect that everything requiring a +// copy will not work. +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert( + std::is_constructible_v>); +static_assert(std::is_constructible_v>); + +// Checks for the second type (CopyType). We expect that everything requiring a +// move will not work. +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert( + std::is_constructible_v>); +static_assert(std::is_constructible_v< + all_variant, std::in_place_type_t, int, std::string>); +static_assert(std::is_constructible_v>); + +// Checks for the third type (CopyAndMoveType). We expect that everything works. +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v>); +static_assert(std::is_constructible_v>); + +// Checks for the fourth type (NoCopyAndNoMoveType). We expect that only +// in_place constructors work. +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert( + !std::is_constructible_v); +static_assert(std::is_constructible_v< + all_variant, std::in_place_type_t>); +static_assert(std::is_constructible_v>); + +// Checks with invalid input. +static_assert(!std::is_constructible_v>); +static_assert(!std::is_constructible_v>); + +// Checks with the construction from std::variant. We can't move and we can't +// copy the types - therefore there is no safe way to construct our variant +// from std::variant. +using std_all_variant = + std::variant; + +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_assignable_v); +static_assert(!std::is_assignable_v); + +// Checks with a variant consisting of only movable types. +using move_variant = variant; + +// Check of copy and move construction/assignment. Since we disallow move +// constructors/assignments none of the copy and move constructors should work. +static_assert(!std::is_default_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_copy_assignable_v); +static_assert(!std::is_move_assignable_v); + +// Checks with the construction from std::variant. Since we can move every type +// we should be able to move construct from std::variant. Copy constructors +// should not work since MoveType has no copy constructor. +using std_move_variant = std::variant; +static_assert(!std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(!std::is_assignable_v); +static_assert(std::is_assignable_v); + +// Checks with a variant consisting of only copyable types. +using copy_variant = variant; + +// Check of copy and move construction/assignment. Copy constructor/assignment +// should work since every type can be copy constructed/assigned. +static_assert(!std::is_default_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(std::is_copy_assignable_v); +static_assert(!std::is_move_assignable_v); + +// Checks with the construction from std::variant. Since we can copy every type +// we should be able to copy construct from std::variant. Move constructors +// should not work since CopyType has no move constructor. +using std_copy_variant = std::variant; +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(std::is_assignable_v); +static_assert(std::is_assignable_v); + +// Checks with a variant containing duplicate types: Constructors where just +// the type is specified should not work since there is no way of telling which +// index to use. +using duplicate_variant = variant; +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v, int>); +static_assert( + std::is_constructible_v, int>); +static_assert( + std::is_constructible_v, int>); +static_assert( + !std::is_constructible_v, int>); + +// We're using the std::reference_wrapper if the enum holds a reference. The +// standard however does not guarantee that the size of std::reference_wrapper +// is the same size as a pointer (required to be compatible with Rust's +// references). Therefore we check it at compile time. +// +// [1] https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper +// [2] https://doc.rust-lang.org/std/mem/fn.size_of.html#examples +static_assert(sizeof(std::reference_wrapper) == + sizeof(std::ptrdiff_t)); + + +// Check that getting something works and actually returns the same type. +template +using copy_variant_alternative_t = + variant_alternative_t; + +static_assert(std::is_same_v(std::declval())), + const copy_variant_alternative_t<0> &>); + +static_assert(std::is_same_v(std::declval())), + const copy_variant_alternative_t<1> &>); + +static_assert(sizeof(empty) == sizeof(std::uint8_t)); + +// Verify that enums are represented as ints. We kind of assume that the enums +// with more fields would be represented by the underlying type not smaller +// than this enum (which is our smallest...). If this fails, we would need +// to pass the enum-type to the variant. +enum a_enum { AA }; + +static_assert(sizeof(std::underlying_type_t) == sizeof(int)); +} // namespace detail +#endif + static const char *errorCopy(const char *ptr, std::size_t len) { char *copy = new char[len]; std::memcpy(copy, ptr, len); @@ -487,7 +714,7 @@ Error &Error::operator=(const Error &other) & { return *this; } -Error &Error::operator=(Error &&other) &noexcept { +Error &Error::operator=(Error &&other) & noexcept { std::exception::operator=(std::move(other)); delete[] this->msg; this->msg = other.msg; diff --git a/syntax/check.rs b/syntax/check.rs index b5fd45e11..5e7677f39 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -2,8 +2,9 @@ use crate::syntax::atom::Atom::{self, *}; use crate::syntax::report::Errors; use crate::syntax::visit::{self, Visit}; use crate::syntax::{ - error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Lifetimes, - NamedType, Ptr, Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types, + error, ident, trivial, Api, Array, CEnumOpts, Enum, ExternFn, ExternType, Impl, Lang, + Lifetimes, NamedType, Ptr, Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, + TypeAlias, Types, }; use proc_macro2::{Delimiter, Group, Ident, TokenStream}; use quote::{quote, ToTokens}; @@ -64,7 +65,8 @@ fn do_typecheck(cx: &mut Check) { match api { Api::Include(_) => {} Api::Struct(strct) => check_api_struct(cx, strct), - Api::Enum(enm) => check_api_enum(cx, enm), + Api::Enum(enm, opts) => check_api_enum(cx, enm, opts), + Api::EnumUnnamed(enm) => check_api_enum_unnamed(cx, enm), Api::CxxType(ety) | Api::RustType(ety) => check_api_type(cx, ety), Api::CxxFunction(efn) | Api::RustFunction(efn) => check_api_fn(cx, efn), Api::TypeAlias(alias) => check_api_type_alias(cx, alias), @@ -353,11 +355,11 @@ fn check_api_struct(cx: &mut Check, strct: &Struct) { } } -fn check_api_enum(cx: &mut Check, enm: &Enum) { +fn check_api_enum(cx: &mut Check, enm: &Enum, opts: &CEnumOpts) { check_reserved_name(cx, &enm.name.rust); check_lifetimes(cx, &enm.generics); - if enm.variants.is_empty() && !enm.explicit_repr && !enm.variants_from_header { + if enm.variants.is_empty() && !opts.explicit_repr && !opts.variants_from_header { let span = span_for_enum_error(enm); cx.error( span, @@ -373,6 +375,43 @@ fn check_api_enum(cx: &mut Check, enm: &Enum) { } } +fn check_api_enum_unnamed(cx: &mut Check, enm: &Enum) { + let name = &enm.name.rust; + check_reserved_name(cx, name); + check_lifetimes(cx, &enm.generics); + + if enm.variants.is_empty() { + let span = span_for_enum_unnamed_error(enm); + cx.error(span, "enums without any variants are not supported"); + } + + // TODO how do I check if cxx has the variant. + // TODO I think the derives below make sense. + for derive in &enm.derives { + if derive.what == Trait::Default || derive.what == Trait::ExternType { + let msg = format!("derive({}) on shared struct is not supported", derive); + cx.error(derive, msg); + } + } + + for variant in &enm.variants { + if variant.ty.is_none() { + continue; + } + let ty = variant.ty.as_ref().unwrap(); + if let Type::Fn(_) = ty { + cx.error( + variant, + "function pointers in a enum variant are not implemented yet", + ); + } else if is_unsized(cx, ty) { + let desc = describe(cx, ty); + let msg = format!("using {} by value is not supported", desc); + cx.error(variant, msg); + } + } +} + fn check_api_type(cx: &mut Check, ety: &ExternType) { check_reserved_name(cx, &ety.name.rust); check_lifetimes(cx, &ety.generics); @@ -682,6 +721,13 @@ fn span_for_enum_error(enm: &Enum) -> TokenStream { quote!(#enum_token #brace_token) } +fn span_for_enum_unnamed_error(enm: &Enum) -> TokenStream { + let enum_token = enm.enum_token; + let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new()); + brace_token.set_span(enm.brace_token.span.join()); + quote!(#enum_token #brace_token) +} + fn span_for_receiver_error(receiver: &Receiver) -> TokenStream { let ampersand = receiver.ampersand; let lifetime = &receiver.lifetime; diff --git a/syntax/ident.rs b/syntax/ident.rs index bb2281e72..7c26f6c07 100644 --- a/syntax/ident.rs +++ b/syntax/ident.rs @@ -34,7 +34,7 @@ pub(crate) fn check_all(cx: &mut Check, apis: &[Api]) { check(cx, &field.name); } } - Api::Enum(enm) => { + Api::Enum(enm, _) | Api::EnumUnnamed(enm) => { check(cx, &enm.name); for variant in &enm.variants { check(cx, &variant.name); diff --git a/syntax/improper.rs b/syntax/improper.rs index a19f5b7d6..dffeaf892 100644 --- a/syntax/improper.rs +++ b/syntax/improper.rs @@ -18,6 +18,12 @@ impl<'a> Types<'a> { Definite(atom == RustString) } else if let Some(strct) = self.structs.get(ident) { Depends(&strct.name.rust) // iterate to fixed-point + } else if let Some(enm) = self.enums.get(ident) { + if enm.variants.iter().any(|variant| variant.ty.is_some()) { + Depends(&enm.name.rust) // iterate to fixed-point + } else { + Definite(self.rust.contains(ident) || self.aliases.contains_key(ident)) + } } else { Definite(self.rust.contains(ident) || self.aliases.contains_key(ident)) } diff --git a/syntax/mod.rs b/syntax/mod.rs index eacba5541..f2ce859d2 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -38,7 +38,7 @@ use self::symbol::Symbol; use proc_macro2::{Ident, Span}; use syn::punctuated::Punctuated; use syn::token::{Brace, Bracket, Paren}; -use syn::{Attribute, Expr, Generics, Lifetime, LitInt, Token, Type as RustType}; +use syn::{Attribute, Expr, Generics, Lifetime, LitInt, Token, Type as RustType, Visibility}; pub(crate) use self::atom::Atom; pub(crate) use self::derive::{Derive, Trait}; @@ -52,7 +52,8 @@ pub(crate) enum Api { #[allow(dead_code)] // only used by cxx-build, not cxxbridge-macro Include(Include), Struct(Struct), - Enum(Enum), + Enum(Enum, CEnumOpts), + EnumUnnamed(Enum), CxxType(ExternType), CxxFunction(ExternFn), RustType(ExternType), @@ -131,6 +132,9 @@ pub(crate) struct Enum { pub generics: Lifetimes, pub brace_token: Brace, pub variants: Vec, +} + +pub(crate) struct CEnumOpts { pub variants_from_header: bool, #[allow(dead_code)] pub variants_from_header_attr: Option, @@ -254,10 +258,13 @@ pub(crate) struct Variant { pub doc: Doc, #[allow(dead_code)] // only used by cxxbridge-macro, not cxx-build pub attrs: OtherAttrs, + #[allow(dead_code)] // only used by cxxbridge-macro, not cxx-build + pub vis: Visibility, pub name: Pair, pub discriminant: Discriminant, #[allow(dead_code)] pub expr: Option, + pub ty: Option, } pub(crate) enum Type { diff --git a/syntax/parse.rs b/syntax/parse.rs index 850dcc8d1..175ff1528 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -1,13 +1,13 @@ use crate::syntax::attrs::OtherAttrs; use crate::syntax::cfg::CfgExpr; -use crate::syntax::discriminant::DiscriminantSet; +use crate::syntax::discriminant::{Discriminant, DiscriminantSet}; use crate::syntax::file::{Item, ItemForeignMod}; use crate::syntax::report::Errors; use crate::syntax::Atom::*; use crate::syntax::{ - attrs, error, Api, Array, Derive, Doc, Enum, EnumRepr, ExternFn, ExternType, ForeignName, Impl, - Include, IncludeKind, Lang, Lifetimes, NamedType, Namespace, Pair, Ptr, Receiver, Ref, - Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, Variant, + attrs, error, Api, Array, CEnumOpts, Derive, Doc, Enum, EnumRepr, ExternFn, ExternType, + ForeignName, Impl, Include, IncludeKind, Lang, Lifetimes, NamedType, Namespace, Pair, Ptr, + Receiver, Ref, Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, Variant, }; use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; use quote::{format_ident, quote, quote_spanned}; @@ -40,7 +40,22 @@ pub(crate) fn parse_items( Ok(strct) => apis.push(strct), Err(err) => cx.push(err), }, - Item::Enum(item) => apis.push(parse_enum(cx, item, namespace)), + Item::Enum(item) => { + // Check if we have any unnamed field - in this ase we have to make sure + // that there is no field with an determinant. + if item + .variants + .iter() + .any(|variant| matches!(variant.fields, Fields::Unnamed(_))) + { + match parse_enum_unnamed(cx, item, namespace) { + Ok(enumct) => apis.push(enumct), + Err(err) => cx.push(err), + } + } else { + apis.push(parse_enum(cx, item, namespace)); + } + } Item::ForeignMod(foreign_mod) => { parse_foreign_mod(cx, foreign_mod, &mut apis, trusted, namespace); } @@ -55,38 +70,10 @@ pub(crate) fn parse_items( apis } -fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> Result { - let mut cfg = CfgExpr::Unconditional; - let mut doc = Doc::new(); - let mut derives = Vec::new(); - let mut namespace = namespace.clone(); - let mut cxx_name = None; - let mut rust_name = None; - let attrs = attrs::parse( - cx, - mem::take(&mut item.attrs), - attrs::Parser { - cfg: Some(&mut cfg), - doc: Some(&mut doc), - derives: Some(&mut derives), - namespace: Some(&mut namespace), - cxx_name: Some(&mut cxx_name), - rust_name: Some(&mut rust_name), - ..Default::default() - }, - ); - - let named_fields = match item.fields { - Fields::Named(fields) => fields, - Fields::Unit => return Err(Error::new_spanned(item, "unit structs are not supported")), - Fields::Unnamed(_) => { - return Err(Error::new_spanned(item, "tuple structs are not supported")); - } - }; - +fn parse_generics(cx: &mut Errors, generics: Generics) -> Lifetimes { let mut lifetimes = Punctuated::new(); let mut has_unsupported_generic_param = false; - for pair in item.generics.params.into_pairs() { + for pair in generics.params.into_pairs() { let (param, punct) = pair.into_tuple(); match param { GenericParam::Lifetime(param) => { @@ -102,14 +89,14 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> } GenericParam::Type(param) => { if !has_unsupported_generic_param { - let msg = "struct with generic type parameter is not supported yet"; + let msg = "user defined type with generic type parameter is not supported yet"; cx.error(¶m, msg); has_unsupported_generic_param = true; } } GenericParam::Const(param) => { if !has_unsupported_generic_param { - let msg = "struct with const generic parameter is not supported yet"; + let msg = "user defined type with const generic parameter is not supported yet"; cx.error(¶m, msg); has_unsupported_generic_param = true; } @@ -117,13 +104,49 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> } } - if let Some(where_clause) = &item.generics.where_clause { + if let Some(where_clause) = &generics.where_clause { cx.error( where_clause, - "struct with where-clause is not supported yet", + "user defined type with where-clause is not supported yet", ); } + Lifetimes { + lt_token: generics.lt_token, + lifetimes, + gt_token: generics.gt_token, + } +} + +fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> Result { + let mut cfg = CfgExpr::Unconditional; + let mut doc = Doc::new(); + let mut derives = Vec::new(); + let mut namespace = namespace.clone(); + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( + cx, + mem::take(&mut item.attrs), + attrs::Parser { + cfg: Some(&mut cfg), + doc: Some(&mut doc), + derives: Some(&mut derives), + namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), + ..Default::default() + }, + ); + + let named_fields = match item.fields { + Fields::Named(fields) => fields, + Fields::Unit => return Err(Error::new_spanned(item, "unit structs are not supported")), + Fields::Unnamed(_) => { + return Err(Error::new_spanned(item, "tuple structs are not supported")); + } + }; + let mut fields = Vec::new(); for field in named_fields.named { let ident = field.ident.unwrap(); @@ -166,11 +189,7 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> let struct_token = item.struct_token; let visibility = visibility_pub(&item.vis, struct_token.span); let name = pair(namespace, &item.ident, cxx_name, rust_name); - let generics = Lifetimes { - lt_token: item.generics.lt_token, - lifetimes, - gt_token: item.generics.gt_token, - }; + let generics = parse_generics(cx, item.generics); let brace_token = named_fields.brace_token; Ok(Api::Struct(Struct { @@ -187,6 +206,119 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> })) } +/// Parses an "Rust" enum with unnamed fields. +fn parse_enum_unnamed(cx: &mut Errors, mut item: ItemEnum, namespace: &Namespace) -> Result { + let mut cfg = CfgExpr::Unconditional; + let mut doc = Doc::new(); + let mut derives = Vec::new(); + let mut namespace = namespace.clone(); + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( + cx, + item.attrs.clone(), + attrs::Parser { + cfg: Some(&mut cfg), + doc: Some(&mut doc), + derives: Some(&mut derives), + namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), + ..Default::default() + }, + ); + + // Generate the variants. + let mut variants = Vec::with_capacity(item.variants.len()); + + for variant in &mut item.variants { + // Having both, Rust typed variants and c-style value variants is + // illegal. + if variant.discriminant.is_some() { + return Err(Error::new_spanned( + item, + "Mixed c-style enums with Rust enums", + )); + } + + // Get the unnamed field of the variant. + let (ty, vis) = match variant.fields { + Fields::Named(_) => { + return Err(Error::new_spanned(item, "Named variants are not supported")) + } + Fields::Unit => { + (None, Visibility::Inherited) + } + Fields::Unnamed(ref unnamed_variant) => { + // Having move than one unnamed field is also illegal since we can't + // represent it in c++. + if unnamed_variant.unnamed.len() != 1 { + return Err(Error::new_spanned( + item, + "More than one unnamed field is not supported", + )); + } + let field = unnamed_variant.unnamed.first().unwrap(); + let ty = match parse_type(&field.ty) { + Ok(ty) => ty, + Err(err) => { + cx.push(err); + continue; + } + }; + (Some(ty), field.vis.clone()) + } + }; + + + let mut cfg = CfgExpr::Unconditional; + let mut doc = Doc::new(); + + // This is kind of stupid - there are no "names" in c++ for the variant + // but just indices... + let name = pair(Namespace::default(), &variant.ident, None, None); + let variant_attrs = attrs::parse( + cx, + mem::take(&mut variant.attrs), + attrs::Parser { + cfg: Some(&mut cfg), + doc: Some(&mut doc), + ..Default::default() + }, + ); + variants.push(Variant { + cfg, + doc, + attrs: variant_attrs, + name, + vis, + discriminant: Discriminant::zero(), + expr: None, + ty, + }); + } + + let enum_token = item.enum_token; + let visibility = visibility_pub(&item.vis, enum_token.span); + let brace_token = item.brace_token; + let name = pair(namespace, &item.ident, cxx_name, rust_name); + let generics = parse_generics(cx, item.generics); + + Ok(Api::EnumUnnamed(Enum { + cfg, + doc, + derives, + attrs, + visibility, + enum_token, + name, + generics, + brace_token, + variants, + })) +} + +/// Parses a "C-like" enum where all fields are units. fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Api { let mut cfg = CfgExpr::Unconditional; let mut doc = Doc::new(); @@ -262,22 +394,26 @@ fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Api { let variants_from_header_attr = variants_from_header; let variants_from_header = variants_from_header_attr.is_some(); - Api::Enum(Enum { - cfg, - doc, - derives, - attrs, - visibility, - enum_token, - name, - generics, - brace_token, - variants, - variants_from_header, - variants_from_header_attr, - repr, - explicit_repr, - }) + Api::Enum( + Enum { + cfg, + doc, + derives, + attrs, + visibility, + enum_token, + name, + generics, + brace_token, + variants, + }, + CEnumOpts { + variants_from_header, + variants_from_header_attr, + repr, + explicit_repr, + }, + ) } fn parse_variant( @@ -326,9 +462,11 @@ fn parse_variant( cfg, doc, attrs, + vis: Visibility::Inherited, name, discriminant, expr, + ty: None, }) } diff --git a/syntax/pod.rs b/syntax/pod.rs index 506e53cb5..0d951e5af 100644 --- a/syntax/pod.rs +++ b/syntax/pod.rs @@ -18,8 +18,13 @@ impl<'a> Types<'a> { .fields .iter() .all(|field| self.is_guaranteed_pod(&field.ty)) + } else if let Some(enm) = self.enums.get(ident) { + // The data enums are not pods, since the c++ side + // implements custom copy constructors and destructors. The + // c-like enums are pods, though. + !enm.variants.iter().any(|variant| variant.ty.is_some()) } else { - self.enums.contains_key(ident) + false } } Type::RustBox(_) diff --git a/syntax/tokens.rs b/syntax/tokens.rs index 05eddc703..f12b0b623 100644 --- a/syntax/tokens.rs +++ b/syntax/tokens.rs @@ -1,7 +1,7 @@ use crate::syntax::atom::Atom::*; use crate::syntax::{ - Array, Atom, Derive, Enum, EnumRepr, ExternFn, ExternType, Impl, Lifetimes, NamedType, Ptr, - Ref, Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, + Array, Atom, Derive, Enum, EnumRepr, ExternFn, ExternType, Impl, Lifetimes, + NamedType, Ptr, Ref, Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, Variant, }; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote_spanned, ToTokens}; @@ -56,6 +56,16 @@ impl ToTokens for Var { } } +impl ToTokens for Variant { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = &self.name; + name.rust.to_tokens(tokens); + if let Some(ref ty) = self.ty { + ty.to_tokens(tokens); + } + } +} + impl ToTokens for Ty1 { fn to_tokens(&self, tokens: &mut TokenStream) { let Ty1 { diff --git a/syntax/trivial.rs b/syntax/trivial.rs index 953340055..bd6ad3881 100644 --- a/syntax/trivial.rs +++ b/syntax/trivial.rs @@ -38,6 +38,7 @@ pub(crate) fn required_trivial_reasons<'a>( for api in apis { match api { + // TODO Add here the enums Api::Struct(strct) => { for field in &strct.fields { if let Type::Ident(ident) = &field.ty { diff --git a/syntax/types.rs b/syntax/types.rs index 623a8b8d6..d8315427d 100644 --- a/syntax/types.rs +++ b/syntax/types.rs @@ -87,8 +87,8 @@ impl<'a> Types<'a> { } add_resolution(&strct.name, &strct.generics); } - Api::Enum(enm) => { - match &enm.repr { + Api::Enum(enm, opts) => { + match &opts.repr { EnumRepr::Native { atom: _, repr_type } => { all.insert(repr_type); } @@ -107,13 +107,34 @@ impl<'a> Types<'a> { duplicate_name(cx, enm, ident); } enums.insert(ident, enm); - if enm.variants_from_header { + if opts.variants_from_header { // #![variants_from_header] enums are implicitly extern // C++ type. cxx.insert(&enm.name.rust); } add_resolution(&enm.name, &enm.generics); } + Api::EnumUnnamed(enm) => { + let ident = &enm.name.rust; + if !type_names.insert(ident) + && (!cxx.contains(ident) + || structs.contains_key(ident) + || enums.contains_key(ident)) + { + // If already declared as a struct or enum, or if + // colliding with something other than an extern C++ + // type, then error. + duplicate_name(cx, enm, ident); + } + + enums.insert(ident, enm); + for variant in &enm.variants { + if variant.ty.is_some() { + visit(&mut all, variant.ty.as_ref().unwrap()); + } + } + add_resolution(&enm.name, &enm.generics); + } Api::CxxType(ety) => { let ident = &ety.name.rust; if !type_names.insert(ident) @@ -238,6 +259,35 @@ impl<'a> Types<'a> { }); } + let mut unresolved_enums = types.enums.keys(); + new_information = true; + while new_information { + new_information = false; + unresolved_enums.retain(|ident| { + let mut retain = false; + for var in &types.enums[ident].variants { + // If the ty is missing we're dealing with the C-style enum. + let ty = match &var.ty { + None => return false, + Some(ty) => ty, + }; + if match types.determine_improper_ctype(ty) { + ImproperCtype::Depends(inner) => { + retain = true; + types.struct_improper_ctypes.contains(inner) + } + ImproperCtype::Definite(improper) => improper, + } { + types.struct_improper_ctypes.insert(ident); + new_information = true; + return false; + } + } + // If all fields definite false, remove from unresolved_structs. + retain + }); + } + types } diff --git a/tests/ffi/Cargo.toml b/tests/ffi/Cargo.toml index 167bbb02d..b6830787d 100644 --- a/tests/ffi/Cargo.toml +++ b/tests/ffi/Cargo.toml @@ -9,7 +9,7 @@ publish = false path = "lib.rs" [dependencies] -cxx = { path = "../..", default-features = false } +cxx = { path = "../..", features = ["c++17"] } [build-dependencies] cxx-build = { path = "../../gen/build" } diff --git a/tests/ffi/build.rs b/tests/ffi/build.rs index 7051cf0b8..d7b36d911 100644 --- a/tests/ffi/build.rs +++ b/tests/ffi/build.rs @@ -9,6 +9,7 @@ fn main() { let sources = vec!["lib.rs", "module.rs"]; let mut build = cxx_build::bridges(sources); build.file("tests.cc"); + build.flag_if_supported("/Zc:__cplusplus"); build.std(cxxbridge_flags::STD); build.warnings_into_errors(cfg!(deny_warnings)); if cfg!(not(target_env = "msvc")) { diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs index f3a8310f1..509e2e4f6 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -92,6 +92,23 @@ pub mod ffi { s: &'a str, } + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + enum EnumSimple { + AVal(bool), + BVal(Shared), + CVal, + } + + enum EnumImproper { + AVal(i32), + BVal(SharedString), + } + + enum EnumWithLifeTime<'a> { + AVal(&'a str), + BVal(&'a i32), + } + unsafe extern "C++" { include!("tests/ffi/tests.h"); @@ -131,6 +148,9 @@ pub mod ffi { fn c_return_nested_ns_enum(n: u16) -> ABEnum; fn c_return_const_ptr(n: usize) -> *const C; fn c_return_mut_ptr(n: usize) -> *mut C; + fn c_return_enum_simple(value: i32) -> EnumSimple; + fn c_return_enum_improper(first: bool) -> EnumImproper; + fn c_return_enum_with_lifetime<'a>(val: &'a i32) -> EnumWithLifeTime<'a>; fn c_take_primitive(n: usize); fn c_take_shared(shared: Shared); @@ -179,6 +199,9 @@ pub mod ffi { fn c_take_rust_vec_nested_ns_shared(v: Vec); unsafe fn c_take_const_ptr(c: *const C) -> usize; unsafe fn c_take_mut_ptr(c: *mut C) -> usize; + fn c_take_enum_simple(enm: EnumSimple) -> i32; + fn c_take_enum_improper(enm: EnumImproper) -> i32; + fn c_take_enum_with_lifetime<'a>(enm: &'a EnumWithLifeTime<'a>) -> i32; fn c_try_return_void() -> Result<()>; fn c_try_return_primitive() -> Result; diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index 8cf74bebb..825e508af 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -229,7 +229,28 @@ std::unique_ptr c_return_borrow(const std::string &s) { return std::unique_ptr(new Borrow(s)); } +EnumSimple c_return_enum_simple(int value) { + if (value == 0) + return false; + else if (value == 1) + return Shared{123}; + return rust::empty{}; +} + +EnumImproper c_return_enum_improper(bool first) { + if (first) { + return 2; + } + return SharedString{rust::String{"Some string"}}; +} + +EnumWithLifeTime c_return_enum_with_lifetime(const int &val) { + return std::reference_wrapper(val); +} + void c_take_primitive(size_t n) { + int v = 123; + c_return_enum_with_lifetime(v); if (n == 2020) { cxx_test_suite_set_correct(); } @@ -555,6 +576,26 @@ size_t c_take_mut_ptr(C *c) { return result; } +int _visit(bool v) { return static_cast(v); }; +int _visit(const Shared &v) { return v.z; }; +int _visit(const ::rust::empty &) { return -1; }; +int _visit(rust::Str value) { return value.size(); } + +int c_take_enum_simple(EnumSimple enm) { + return ::rust::visit([](const auto &val) { return _visit(val); }, enm); +} + +int c_take_enum_improper(EnumImproper enm) { return enm.index(); } + +int c_take_enum_with_lifetime(const EnumWithLifeTime &enm) { + try { + return ::rust::get>(enm) + + ::rust::get<1>(enm); + } catch (...) { + return ::rust::get<0>(enm).size(); + } +} + void c_try_return_void() {} size_t c_try_return_primitive() { return 2020; } diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h index dc02e4ff8..0d75078a7 100644 --- a/tests/ffi/tests.h +++ b/tests/ffi/tests.h @@ -39,6 +39,10 @@ struct Shared; struct SharedString; enum class Enum : uint16_t; +struct EnumSimple; +struct EnumImproper; +struct EnumWithLifeTime; + class C { public: C(size_t n); @@ -124,6 +128,9 @@ ::A::B::ABEnum c_return_nested_ns_enum(uint16_t n); std::unique_ptr c_return_borrow(const std::string &s); const C *c_return_const_ptr(size_t n); C *c_return_mut_ptr(size_t n); +EnumSimple c_return_enum_simple(int value); +EnumImproper c_return_enum_improper(bool first); +EnumWithLifeTime c_return_enum_with_lifetime(const int& val); void c_take_primitive(size_t n); void c_take_shared(Shared shared); @@ -173,6 +180,9 @@ void c_take_ns_enum(::A::AEnum e); void c_take_nested_ns_enum(::A::B::ABEnum e); size_t c_take_const_ptr(const C *c); size_t c_take_mut_ptr(C *c); +int c_take_enum_simple(EnumSimple enm); +int c_take_enum_improper(EnumImproper enm); +int c_take_enum_with_lifetime(const EnumWithLifeTime& enm); void c_try_return_void(); size_t c_try_return_primitive(); diff --git a/tests/test.rs b/tests/test.rs index 1611d9717..6feca3026 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -379,3 +379,64 @@ fn test_raw_ptr() { assert_eq!(2025, unsafe { ffi::c_take_const_ptr(c3) }); assert_eq!(2025, unsafe { ffi::c_take_mut_ptr(c3 as *mut ffi::C) }); // deletes c3 } + +#[test] +fn test_data_enums() { + use ffi::{c_return_enum_improper, c_return_enum_simple, c_return_enum_with_lifetime}; + use ffi::{c_take_enum_improper, c_take_enum_simple, c_take_enum_with_lifetime}; + use ffi::{EnumImproper, EnumSimple, EnumWithLifeTime}; + + assert!(matches!(c_return_enum_simple(0), EnumSimple::AVal(false))); + + assert!(matches!( + c_return_enum_simple(1), + EnumSimple::BVal(ffi::Shared { z: 123 }) + )); + + assert!(matches!(c_return_enum_simple(2), EnumSimple::CVal)); + + assert!(matches!( + c_return_enum_improper(true), + EnumImproper::AVal(2) + )); + + let msg = "Some string".to_string(); + match c_return_enum_improper(false) { + EnumImproper::BVal(val) => { + assert_eq!(val.msg, msg); + } + EnumImproper::AVal(_) => { + assert!(false); + } + } + + let a = 0xdead; + match c_return_enum_with_lifetime(&a) { + EnumWithLifeTime::AVal(_) => assert!(false), + EnumWithLifeTime::BVal(&v) => { + assert_eq!(a, v); + } + } + + assert_eq!(c_take_enum_simple(EnumSimple::AVal(false)), 0); + assert_eq!(c_take_enum_simple(EnumSimple::AVal(true)), 1); + assert_eq!( + c_take_enum_simple(EnumSimple::BVal(ffi::Shared { z: 100 })), + 100 + ); + assert_eq!(c_take_enum_simple(EnumSimple::CVal), -1); + + assert_eq!(c_take_enum_improper(EnumImproper::AVal(1)), 0); + assert_eq!( + c_take_enum_improper(EnumImproper::BVal(ffi::SharedString { + msg: "foo".to_string() + })), + 1 + ); + + let a = EnumWithLifeTime::BVal(&10); + assert_eq!(c_take_enum_with_lifetime(&a), 20); + + let a = EnumWithLifeTime::AVal("foo"); + assert_eq!(c_take_enum_with_lifetime(&a), 3); +} diff --git a/tests/ui/data_enums.stderr b/tests/ui/data_enums.stderr deleted file mode 100644 index d8aa09e39..000000000 --- a/tests/ui/data_enums.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: enums with data are not supported yet - --> tests/ui/data_enums.rs:4:9 - | -4 | Field(u64), - | ^^^^^^^^^^ diff --git a/tests/ui/enum_many_types_tuple.rs b/tests/ui/enum_many_types_tuple.rs new file mode 100644 index 000000000..c845caaca --- /dev/null +++ b/tests/ui/enum_many_types_tuple.rs @@ -0,0 +1,9 @@ +#[cxx::bridge] +mod ffi { + enum Bad { + A(i32, bool), + B(i32), + } +} + +fn main() {} diff --git a/tests/ui/enum_many_types_tuple.stderr b/tests/ui/enum_many_types_tuple.stderr new file mode 100644 index 000000000..e54d535ff --- /dev/null +++ b/tests/ui/enum_many_types_tuple.stderr @@ -0,0 +1,8 @@ +error: More than one unnamed field is not supported + --> tests/ui/enum_many_types_tuple.rs:3:5 + | +3 | / enum Bad { +4 | | A(i32, bool), +5 | | B(i32), +6 | | } + | |_____^ diff --git a/tests/ui/data_enums.rs b/tests/ui/enum_mixed.rs similarity index 50% rename from tests/ui/data_enums.rs rename to tests/ui/enum_mixed.rs index aa23200dc..d2dbae0c0 100644 --- a/tests/ui/data_enums.rs +++ b/tests/ui/enum_mixed.rs @@ -1,7 +1,8 @@ #[cxx::bridge] mod ffi { - enum A { - Field(u64), + enum Bad { + A(i32), + B = 1, } } diff --git a/tests/ui/enum_mixed.stderr b/tests/ui/enum_mixed.stderr new file mode 100644 index 000000000..ab546bcc0 --- /dev/null +++ b/tests/ui/enum_mixed.stderr @@ -0,0 +1,8 @@ +error: Mixed c-style enums with Rust enums + --> tests/ui/enum_mixed.rs:3:5 + | +3 | / enum Bad { +4 | | A(i32), +5 | | B = 1, +6 | | } + | |_____^ diff --git a/tests/ui/enum_named.rs b/tests/ui/enum_named.rs new file mode 100644 index 000000000..493487f39 --- /dev/null +++ b/tests/ui/enum_named.rs @@ -0,0 +1,9 @@ +#[cxx::bridge] +mod ffi { + enum Bad { + A{age: i32}, + B(i32), + } +} + +fn main() {} diff --git a/tests/ui/enum_named.stderr b/tests/ui/enum_named.stderr new file mode 100644 index 000000000..12a9033cc --- /dev/null +++ b/tests/ui/enum_named.stderr @@ -0,0 +1,8 @@ +error: Named variants are not supported + --> tests/ui/enum_named.rs:3:5 + | +3 | / enum Bad { +4 | | A{age: i32}, +5 | | B(i32), +6 | | } + | |_____^ diff --git a/tests/ui/enum_visibility.rs b/tests/ui/enum_visibility.rs new file mode 100644 index 000000000..fe53c5b4a --- /dev/null +++ b/tests/ui/enum_visibility.rs @@ -0,0 +1,9 @@ +#[cxx::bridge] +mod ffi { + enum Bad { + A(pub i32), + B(bool), + } +} + +fn main() {} diff --git a/tests/ui/enum_visibility.stderr b/tests/ui/enum_visibility.stderr new file mode 100644 index 000000000..0a6688557 --- /dev/null +++ b/tests/ui/enum_visibility.stderr @@ -0,0 +1,7 @@ +error[E0449]: visibility qualifiers are not permitted here + --> tests/ui/enum_visibility.rs:4:11 + | +4 | A(pub i32), + | ^^^ + | + = note: enum variants and their fields always share the visibility of the enum they are in diff --git a/tools/buck/toolchains/BUCK b/tools/buck/toolchains/BUCK index e120a29ba..572795095 100644 --- a/tools/buck/toolchains/BUCK +++ b/tools/buck/toolchains/BUCK @@ -9,7 +9,7 @@ system_cxx_toolchain( cxx_flags = select({ "config//os:linux": ["-std=c++17"], "config//os:macos": ["-std=c++17"], - "config//os:windows": [], + "config//os:windows": ["/std:c++17", "/Zc:__cplusplus"], }), link_flags = select({ "config//os:linux": ["-lstdc++"],