From 8a60085646bfd3e2cfeb593073530f29169ead64 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Mon, 27 Oct 2025 22:21:35 +0000 Subject: [PATCH 1/2] Encode patch-version in the prefix of cxx-generated symbols. Fixes https://github.com/dtolnay/cxx/issues/1507 --- syntax/mangle.rs | 5 ++++- tests/cxx_gen.rs | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/syntax/mangle.rs b/syntax/mangle.rs index 1b10fb7e7..2b478b4db 100644 --- a/syntax/mangle.rs +++ b/syntax/mangle.rs @@ -76,7 +76,10 @@ use crate::syntax::symbol::{self, Symbol}; use crate::syntax::{ExternFn, Pair, Types}; -const CXXBRIDGE: &str = "cxxbridge1"; +// Ignoring `CARGO_PKG_VERSION_MAJOR` and `...MINOR`, because they don't agree across +// all the crates. For example `gen/lib/Cargo.toml` says `version = "0.7.xxx"`, but +// `macro/Cargo.toml` says `version = "1.0.xxx"`. +const CXXBRIDGE: &'static str = concat!("cxxbridge1_", env!("CARGO_PKG_VERSION_PATCH")); macro_rules! join { ($($segment:expr),+ $(,)?) => { diff --git a/tests/cxx_gen.rs b/tests/cxx_gen.rs index 93e25307e..0d8fb89b1 100644 --- a/tests/cxx_gen.rs +++ b/tests/cxx_gen.rs @@ -18,7 +18,7 @@ fn test_extern_c_function() { let output = str::from_utf8(&generated.implementation).unwrap(); // To avoid continual breakage we won't test every byte. // Let's look for the major features. - assert!(output.contains("void cxxbridge1$do_cpp_thing(::rust::Str foo)")); + assert!(output.contains(&format!("void {CXXBRIDGE}$do_cpp_thing(::rust::Str foo)"))); } #[test] @@ -28,9 +28,13 @@ fn test_impl_annotation() { let source = BRIDGE0.parse().unwrap(); let generated = generate_header_and_cc(source, &opt).unwrap(); let output = str::from_utf8(&generated.implementation).unwrap(); - assert!(output.contains("ANNOTATION void cxxbridge1$do_cpp_thing(::rust::Str foo)")); + assert!(output.contains(&format!( + "ANNOTATION void {CXXBRIDGE}$do_cpp_thing(::rust::Str foo)" + ))); } +const CXXBRIDGE: &'static str = concat!("cxxbridge1_", env!("CARGO_PKG_VERSION_PATCH")); + const BRIDGE1: &str = r#" #[cxx::bridge] mod ffi { @@ -66,10 +70,13 @@ fn test_extern_rust_method_on_c_type() { assert!(!header.contains("rust_method_cpp_receiver")); // Check that there is a generated C signature bridging to the Rust method. - assert!(implementation - .contains("void cxxbridge1$CppType$rust_method_cpp_receiver(::CppType &self) noexcept;")); + assert!(implementation.contains(&format!( + "void {CXXBRIDGE}$CppType$rust_method_cpp_receiver(::CppType &self) noexcept;" + ))); // Check that there is an implementation on the C++ class calling the Rust method. assert!(implementation.contains("void CppType::rust_method_cpp_receiver() noexcept {")); - assert!(implementation.contains("cxxbridge1$CppType$rust_method_cpp_receiver(*this);")); + assert!(implementation.contains(&format!( + "{CXXBRIDGE}$CppType$rust_method_cpp_receiver(*this);" + ))); } From cb395caf5fca9fbbabe172243d06d99f8d62d2c0 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Tue, 28 Oct 2025 17:38:54 +0000 Subject: [PATCH 2/2] More clearly document+implement when CXXVERSION is present or missing. --- syntax/mangle.rs | 32 ++++++++++++++++++++------------ tests/cxx_gen.rs | 10 +++++----- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/syntax/mangle.rs b/syntax/mangle.rs index 2b478b4db..418a6e776 100644 --- a/syntax/mangle.rs +++ b/syntax/mangle.rs @@ -7,6 +7,7 @@ // defining characteristics: // - 2 segments // - starts with cxxbridge +// TODO: should these also include {CXXVERSION}? // // (b) Behavior on a builtin binding without generic parameter. // pattern: {CXXBRIDGE} $ {TYPE} $ {NAME} @@ -15,6 +16,7 @@ // defining characteristics: // - 3 segments // - starts with cxxbridge +// TODO: should these also include {CXXVERSION}? // // (c) Behavior on a builtin binding with generic parameter. // pattern: {CXXBRIDGE} $ {TYPE} $ {PARAM...} $ {NAME} @@ -24,33 +26,35 @@ // defining characteristics: // - 4+ segments // - starts with cxxbridge +// TODO: should these also include {CXXVERSION}? (always? or only for +// ones implicitly or explicitly `impl`-ed by the user?) // // (d) User-defined extern function. -// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {NAME} +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {CXXVERSION} $ {NAME} // examples: -// - cxxbridge1$new_client -// - org$rust$cxxbridge1$new_client +// - cxxbridge1$v187$new_client +// - org$rust$cxxbridge1$v187$new_client // defining characteristics: -// - cxxbridge is second from end +// - cxxbridge is third from end // FIXME: conflict with (a) if they collide with one of our one-off symbol names in the global namespace // // (e) User-defined extern member function. -// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE} $ {NAME} +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {CXXVERSION} $ {TYPE} $ {NAME} // examples: -// - org$cxxbridge1$Struct$get +// - org$cxxbridge1$v187$Struct$get // defining characteristics: -// - cxxbridge is third from end +// - cxxbridge is fourth from end // FIXME: conflict with (b) if e.g. user binds a type in global namespace that collides with our builtin type names // // (f) Operator overload. -// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE} $ operator $ {NAME} +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {CXXVERSION} $ {TYPE} $ operator $ {NAME} // examples: -// - org$rust$cxxbridge1$Struct$operator$eq +// - org$rust$cxxbridge1$v187$Struct$operator$eq // defining characteristics: // - second segment from end is `operator` (not possible in type or namespace names) // // (g) Closure trampoline. -// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE?} $ {NAME} $ {ARGUMENT} $ {DIRECTION} +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {CXXVERSION} $ {TYPE?} $ {NAME} $ {ARGUMENT} $ {DIRECTION} // examples: // - org$rust$cxxbridge1$Struct$invoke$f$0 // defining characteristics: @@ -76,10 +80,12 @@ use crate::syntax::symbol::{self, Symbol}; use crate::syntax::{ExternFn, Pair, Types}; +const CXXBRIDGE: &str = "cxxbridge1"; + // Ignoring `CARGO_PKG_VERSION_MAJOR` and `...MINOR`, because they don't agree across // all the crates. For example `gen/lib/Cargo.toml` says `version = "0.7.xxx"`, but // `macro/Cargo.toml` says `version = "1.0.xxx"`. -const CXXBRIDGE: &'static str = concat!("cxxbridge1_", env!("CARGO_PKG_VERSION_PATCH")); +const CXXVERSION: &str = concat!("v", env!("CARGO_PKG_VERSION_PATCH")); macro_rules! join { ($($segment:expr),+ $(,)?) => { @@ -94,11 +100,12 @@ pub(crate) fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol { join!( efn.name.namespace, CXXBRIDGE, + CXXVERSION, self_type_ident.name.cxx, efn.name.rust, ) } - None => join!(efn.name.namespace, CXXBRIDGE, efn.name.rust), + None => join!(efn.name.namespace, CXXBRIDGE, CXXVERSION, efn.name.rust), } } @@ -106,6 +113,7 @@ pub(crate) fn operator(receiver: &Pair, operator: &'static str) -> Symbol { join!( receiver.namespace, CXXBRIDGE, + CXXVERSION, receiver.cxx, "operator", operator, diff --git a/tests/cxx_gen.rs b/tests/cxx_gen.rs index 0d8fb89b1..d476caa45 100644 --- a/tests/cxx_gen.rs +++ b/tests/cxx_gen.rs @@ -18,7 +18,7 @@ fn test_extern_c_function() { let output = str::from_utf8(&generated.implementation).unwrap(); // To avoid continual breakage we won't test every byte. // Let's look for the major features. - assert!(output.contains(&format!("void {CXXBRIDGE}$do_cpp_thing(::rust::Str foo)"))); + assert!(output.contains(&format!("void {CXXPREFIX}$do_cpp_thing(::rust::Str foo)"))); } #[test] @@ -29,11 +29,11 @@ fn test_impl_annotation() { let generated = generate_header_and_cc(source, &opt).unwrap(); let output = str::from_utf8(&generated.implementation).unwrap(); assert!(output.contains(&format!( - "ANNOTATION void {CXXBRIDGE}$do_cpp_thing(::rust::Str foo)" + "ANNOTATION void {CXXPREFIX}$do_cpp_thing(::rust::Str foo)" ))); } -const CXXBRIDGE: &'static str = concat!("cxxbridge1_", env!("CARGO_PKG_VERSION_PATCH")); +const CXXPREFIX: &'static str = concat!("cxxbridge1$v", env!("CARGO_PKG_VERSION_PATCH")); const BRIDGE1: &str = r#" #[cxx::bridge] @@ -71,12 +71,12 @@ fn test_extern_rust_method_on_c_type() { // Check that there is a generated C signature bridging to the Rust method. assert!(implementation.contains(&format!( - "void {CXXBRIDGE}$CppType$rust_method_cpp_receiver(::CppType &self) noexcept;" + "void {CXXPREFIX}$CppType$rust_method_cpp_receiver(::CppType &self) noexcept;" ))); // Check that there is an implementation on the C++ class calling the Rust method. assert!(implementation.contains("void CppType::rust_method_cpp_receiver() noexcept {")); assert!(implementation.contains(&format!( - "{CXXBRIDGE}$CppType$rust_method_cpp_receiver(*this);" + "{CXXPREFIX}$CppType$rust_method_cpp_receiver(*this);" ))); }