From 22f6b38ff3ccae8887a426d4ac0e4d257c95eedc Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 25 Feb 2026 21:36:51 -0800 Subject: [PATCH 01/10] Update headers --- include/rice/rice.hpp | 26 ++++++-------------------- include/rice/stl.hpp | 26 +++++++++++++++++--------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/include/rice/rice.hpp b/include/rice/rice.hpp index 85809b3c..b4c89380 100644 --- a/include/rice/rice.hpp +++ b/include/rice/rice.hpp @@ -8854,31 +8854,15 @@ namespace Rice::detail } } - void* convert(VALUE value) + std::nullptr_t convert(VALUE value) { if (value == Qnil) { return nullptr; } - if (this->arg_ && this->arg_->isOpaque()) - { - return (void*)value; - } - - switch (rb_type(value)) - { - case RUBY_T_NIL: - { - return nullptr; - break; - } - default: - { - throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", - detail::protect(rb_obj_classname, value), "nil"); - } - } + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + detail::protect(rb_obj_classname, value), "nil"); } private: Arg* arg_ = nullptr; @@ -10277,7 +10261,9 @@ namespace Rice::detail if constexpr (is_complete_v) { - if constexpr (std::is_destructible_v) + // is_abstract_v requires a complete type, so nest inside is_complete_v. + // Deleting an abstract class through a non-virtual destructor is UB. + if constexpr (std::is_destructible_v && !std::is_abstract_v) { if (this->isOwner_) { diff --git a/include/rice/stl.hpp b/include/rice/stl.hpp index 6b1fc79d..c3dca90c 100644 --- a/include/rice/stl.hpp +++ b/include/rice/stl.hpp @@ -1310,8 +1310,12 @@ namespace Rice private: void define_constructors() { - klass_.define_constructor(Constructor()) - .define_constructor(Constructor(), Arg("x").keepAlive(), Arg("y").keepAlive()); + if constexpr (std::is_default_constructible_v) + { + klass_.define_constructor(Constructor()); + } + + klass_.define_constructor(Constructor(), Arg("x").keepAlive(), Arg("y").keepAlive()); if constexpr (std::is_copy_constructible_v && std::is_copy_constructible_v) { @@ -1650,7 +1654,7 @@ namespace Rice }, Arg("key")) .define_method("[]=", [](T& map, Key_T key, Mapped_Parameter_T value) -> Mapped_T { - map[key] = value; + map.insert_or_assign(key, value); return value; }, Arg("key").keepAlive(), Arg("value").keepAlive()); @@ -1772,7 +1776,7 @@ namespace Rice // exceptions propogate back to Ruby return cpp_protect([&] { - result->operator[](From_Ruby().convert(key)) = From_Ruby().convert(value); + result->insert_or_assign(From_Ruby().convert(key), From_Ruby().convert(value)); return ST_CONTINUE; }); } @@ -3201,7 +3205,11 @@ namespace Rice if constexpr (detail::is_complete_v && !std::is_void_v) { - result.define_constructor(Constructor(), Arg("value").takeOwnership()); + // is_abstract_v requires a complete type, so it must be nested inside the is_complete_v check + if constexpr (!std::is_abstract_v) + { + result.define_constructor(Constructor(), Arg("value").takeOwnership()); + } } // Forward methods to wrapped T @@ -3256,7 +3264,7 @@ namespace Rice::detail } else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType)) { - return this->data_.get(); + return (void*)this->data_.get(); } else { @@ -3931,7 +3939,7 @@ namespace Rice::detail } else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType)) { - return this->data_.get(); + return (void*)this->data_.get(); } else { @@ -4171,7 +4179,7 @@ namespace Rice }, Arg("key")) .define_method("[]=", [](T& unordered_map, Key_T key, Mapped_Parameter_T value) -> Mapped_T { - unordered_map[key] = value; + unordered_map.insert_or_assign(key, value); return value; }, Arg("key").keepAlive(), Arg("value").keepAlive()); @@ -4293,7 +4301,7 @@ namespace Rice // exceptions propogate back to Ruby return cpp_protect([&] { - result->operator[](From_Ruby().convert(key)) = From_Ruby().convert(value); + result->insert_or_assign(From_Ruby().convert(key), From_Ruby().convert(value)); return ST_CONTINUE; }); } From f9ee0573adf7411551aea9c01207f1b68072e024 Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 25 Feb 2026 21:37:17 -0800 Subject: [PATCH 02/10] Version 4.11.3. --- CHANGELOG.md | 6 ++++-- lib/rice/version.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ecf767..606d934c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,14 @@ # Changelog -## 4.11.3 (?) +## 4.11.3 (2026-02-25) ### Enhancements * Add support for `std::shared_ptr` * Add support for `std::unique_ptr` - +* Add support for non-constructible objects in std::pair, std::map, std::multimap +* Add support for wrapping pointers to abstract types that cannot be created nor destroyed +* Fix converting std::nullptr_t to Ruby ## 4.11.2 (2026-02-21) diff --git a/lib/rice/version.rb b/lib/rice/version.rb index a42b3e4d..864cf92f 100644 --- a/lib/rice/version.rb +++ b/lib/rice/version.rb @@ -1,3 +1,3 @@ module Rice - VERSION = "4.11.2" + VERSION = "4.11.3" end From 574723e7d1cad901ccd415bc19fee2946367c940 Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 10 Mar 2026 23:40:56 -0700 Subject: [PATCH 03/10] Update AGENTS.md --- Agents.md => AGENTS.md | 4 ++++ 1 file changed, 4 insertions(+) rename Agents.md => AGENTS.md (74%) diff --git a/Agents.md b/AGENTS.md similarity index 74% rename from Agents.md rename to AGENTS.md index 327bd596..36440ffa 100644 --- a/Agents.md +++ b/AGENTS.md @@ -27,3 +27,7 @@ Run specific test suites by passing suite names as arguments: ```bash ./build/linux-debug/test/unittest Array Hash Iterator ``` + +## Quick Compile Testing + +To quickly test if a single test file compiles, temporarily edit `test/CMakeLists.txt` to include only that file (plus `unittest.cpp` and `embed_ruby.cpp`). Revert the change when done. From f37f0a1666fc0cb6d108c7ecb5bfbb8aa60bf717 Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 10 Mar 2026 23:43:26 -0700 Subject: [PATCH 04/10] Add test case for attributes which are function pointers. --- test/test_Attribute.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/test_Attribute.cpp b/test/test_Attribute.cpp index 399b84b1..5d87be0e 100644 --- a/test/test_Attribute.cpp +++ b/test/test_Attribute.cpp @@ -590,4 +590,35 @@ TESTCASE(KeepAlive) // This should work because keepAlive prevents MyClass2 from being GC'd ASSERT_NOT_EQUAL(nullptr, dataStruct->myClass2); ASSERT_EQUAL(43, dataStruct->myClass2->value); +} + +namespace +{ + struct FuncPtrStruct + { + int (*callback)(int) = nullptr; + }; +} + +TESTCASE(function_pointer_attribute) +{ + Module m = define_module("Testing"); + + Class c = define_class("FuncPtrStruct") + .define_constructor(Constructor()) + .define_attr("callback", &FuncPtrStruct::callback); + + Object o = c.call("new"); + + // Set the callback via Ruby using a lambda + std::string code = R"(struct = FuncPtrStruct.new + struct.callback = lambda { |x| x * 2 } + struct)"; + + Data_Object funcPtrStruct = m.module_eval(code); + + // Invoke the callback from C++ to verify it works + ASSERT_NOT_EQUAL(nullptr, funcPtrStruct->callback); + int result = funcPtrStruct->callback(5); + ASSERT_EQUAL(10, result); } \ No newline at end of file From d528d6b25e6d05f9401dbd00b053851bf2a7d0cc Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 11 Mar 2026 00:30:28 -0700 Subject: [PATCH 05/10] Fix C++20 compile issues. #395 --- CMakePresets.json | 6 +++--- docs/packaging/build_settings.md | 6 +++++- rice/stl/ostream.ipp | 6 +++++- rice/traits/function_traits.hpp | 21 +++++++++++++++++++++ test/test_Callback.cpp | 6 ++---- 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index bff8f9da..e43ae4d4 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -116,7 +116,7 @@ "toolchainFile": "$env{VCPKG_ROOT}\\scripts\\buildsystems\\vcpkg.cmake", "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", - "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE" + "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /Zc:__cplusplus /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE" }, "condition": { "type": "equals", @@ -155,7 +155,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_CXX_COMPILER": "clang-cl.exe", - "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field", + "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /Zc:__cplusplus /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field", "CMAKE_CXX_FLAGS_DEBUG": "/Od /Zi" } }, @@ -166,7 +166,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "CMAKE_CXX_COMPILER": "clang-cl.exe", - "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field", + "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /Zc:__cplusplus /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field", "CMAKE_CXX_FLAGS_RELEASE": "/O2 /DNDEBUG", "CMAKE_INTERPROCEDURAL_OPTIMIZATION": "ON" } diff --git a/docs/packaging/build_settings.md b/docs/packaging/build_settings.md index 30933e13..b101068e 100644 --- a/docs/packaging/build_settings.md +++ b/docs/packaging/build_settings.md @@ -19,7 +19,7 @@ For MINGW: For Microsoft Visual C++ and Windows Clang: ```bash -/std:c++17 /EHs /permissive- /bigobj /utf-8 -D_ALLOW_KEYWORD_MACROS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE +/std:c++17 /EHs /permissive- /bigobj /utf-8 /Zc:__cplusplus -D_ALLOW_KEYWORD_MACROS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE ``` These options are described below: @@ -36,6 +36,10 @@ Second, bigobject support needs to enabled. This tells the compiler to increase Rice uses UTF-8 characters when [mapping](../stl/stl.md#automatically-generated-ruby-classes) instantiated STL templates to Ruby class names. +### __cplusplus Macro + +By default, MSVC does not update the `__cplusplus` preprocessor macro to reflect the actual C++ standard in use — it always reports `199711L`. The `/Zc:__cplusplus` flag fixes this so that Rice's `#if __cplusplus` checks work correctly. + ### Exception Handling Model For Visual C++, the default exception [model](https://learn.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-170) setting of `/EHsc` crashes Ruby when calling longjmp with optimizations enabled (/O2). Therefore you must `/EHs` instead. diff --git a/rice/stl/ostream.ipp b/rice/stl/ostream.ipp index d9314b2f..67f9d0ab 100644 --- a/rice/stl/ostream.ipp +++ b/rice/stl/ostream.ipp @@ -59,8 +59,12 @@ namespace Rice::stl void define_methods() { +#if __cplusplus >= 202002L + klass_.define_method("str", &std::ostringstream::str) +#else klass_.define_method("str", &std::ostringstream::str) - .define_method("str=", [](std::ostringstream& stream, const std::string& s) { stream.str(s); }, Arg("str")); +#endif + .define_method("str=", [](std::ostringstream& stream, const std::string& s) { stream.str(s); }, Arg("str")); rb_define_alias(klass_, "to_s", "str"); } diff --git a/rice/traits/function_traits.hpp b/rice/traits/function_traits.hpp index 2f6eb9ca..2bb33039 100644 --- a/rice/traits/function_traits.hpp +++ b/rice/traits/function_traits.hpp @@ -102,6 +102,27 @@ namespace Rice::detail { }; + // ref-qualified member Functions on C++ classes (C++20 uses these for std library types) + template + struct function_traits : public function_traits + { + }; + + template + struct function_traits : public function_traits + { + }; + + template + struct function_traits : public function_traits + { + }; + + template + struct function_traits : public function_traits + { + }; + /*// Functors and lambdas template struct function_traits : public function_traits diff --git a/test/test_Callback.cpp b/test/test_Callback.cpp index 1a64b886..e4773c63 100644 --- a/test/test_Callback.cpp +++ b/test/test_Callback.cpp @@ -55,10 +55,8 @@ TESTCASE(LambdaCallBack) ASSERT((globalCallback != nullptr)); int ref = 4; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wwrite-strings" - char* result = triggerCallback(1, 2, true, "hello", ref); -#pragma GCC diagnostic pop + char hello[] = "hello"; + char* result = triggerCallback(1, 2, true, hello, ref); ASSERT_EQUAL("1 - 2.0 - true - hello - 4", result); } From 4a2a35e357f25717ec78776d0b5b957ee6694666 Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 11 Mar 2026 00:47:59 -0700 Subject: [PATCH 06/10] Make C++ version configurable via command-line in mkmf-rice.rb --- lib/mkmf-rice.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/mkmf-rice.rb b/lib/mkmf-rice.rb index a7be4a27..da4b7fcc 100644 --- a/lib/mkmf-rice.rb +++ b/lib/mkmf-rice.rb @@ -24,14 +24,16 @@ def cpp_command(outfile, opt="") # Now pull in the C++ support include MakeMakefile['C++'] -# Rice needs c++17. +# Rice needs c++17 or higher. Use --with-cxx-standard=20 to override. +std = with_config('cxx-standard', '17') + if IS_MSWIN - $CXXFLAGS += " /std:c++17 /EHs /permissive- /bigobj /utf-8" + $CXXFLAGS += " /std:c++#{std} /EHs /permissive- /bigobj /utf-8 /Zc:__cplusplus" $CPPFLAGS += " -D_ALLOW_KEYWORD_MACROS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE" elsif IS_MINGW - $CXXFLAGS += " -std=c++17 -Wa,-mbig-obj" + $CXXFLAGS += " -std=c++#{std} -Wa,-mbig-obj" else - $CXXFLAGS += " -std=c++17" + $CXXFLAGS += " -std=c++#{std}" end # Rice needs to include its header. Let's setup the include path From f96698535a69cb34f2fd2530f1a40ea9a7d7ed61 Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 11 Mar 2026 00:49:45 -0700 Subject: [PATCH 07/10] Add CMake and different C++ versions to test matrix. --- .github/workflows/testing.yml | 39 ++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 42e7caa2..8034537f 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -33,4 +33,41 @@ jobs: if: always() with: name: mkmf-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.ruby }} - path: test/mkmf.log \ No newline at end of file + path: test/mkmf.log + + cpp-standard: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-2025] + cpp_standard: ['17', '20', '23'] + include: + - os: ubuntu-latest + preset: linux-debug + - os: macos-latest + preset: macos-debug + - os: windows-2025 + preset: msvc-debug + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '4.0' + - name: Install libffi (Ubuntu) + if: runner.os == 'Linux' + run: sudo apt-get install -y libffi-dev + - name: Install libffi (macOS) + if: runner.os == 'macOS' + run: brew install libffi + - name: Install libffi (Windows) + if: runner.os == 'Windows' + run: vcpkg install libffi:x64-windows + - name: Configure + run: cmake --preset ${{ matrix.preset }} -DCMAKE_CXX_STANDARD=${{ matrix.cpp_standard }} + - name: Build + run: cmake --build --preset ${{ matrix.preset }} + - name: Test + run: ./build/${{ matrix.preset }}/test/unittest + shell: bash \ No newline at end of file From 3a601933bc3b2bc37f3d16058965864e5cc87036 Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 11 Mar 2026 00:50:17 -0700 Subject: [PATCH 08/10] Remove older Ubuntu test. --- .github/workflows/testing.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8034537f..4d7cb81d 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -13,9 +13,6 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-2025] ruby: ['3.2', '3.3', '3.4', '4.0'] - include: - - os: ubuntu-22.04 - ruby: '3.2' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 From 29341b21017eddd3da5ad7d04333091d041829eb Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 11 Mar 2026 00:52:52 -0700 Subject: [PATCH 09/10] Update test names. --- .github/workflows/testing.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4d7cb81d..77f5d5bd 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,7 +7,8 @@ on: branches: [ master ] jobs: - tests: + mkmf: + name: mkmf (${{ matrix.os }}, ${{ matrix.ruby }}, C++17) strategy: fail-fast: false matrix: @@ -32,7 +33,8 @@ jobs: name: mkmf-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.ruby }} path: test/mkmf.log - cpp-standard: + cmake: + name: cmake (${{ matrix.os }}, 4.0, C++${{ matrix.cpp_standard }}) strategy: fail-fast: false matrix: From 580009e3cf0e30c0aa1cc4848c1d90661cb3feba Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 11 Mar 2026 01:08:18 -0700 Subject: [PATCH 10/10] See if we can get msvc builds working. --- .github/workflows/testing.yml | 56 +++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 77f5d5bd..73499ba5 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -34,7 +34,7 @@ jobs: path: test/mkmf.log cmake: - name: cmake (${{ matrix.os }}, 4.0, C++${{ matrix.cpp_standard }}) + name: cmake (${{ matrix.os }}, C++${{ matrix.cpp_standard }}) strategy: fail-fast: false matrix: @@ -43,30 +43,42 @@ jobs: include: - os: ubuntu-latest preset: linux-debug + ruby: '4.0' - os: macos-latest preset: macos-debug + ruby: '4.0' - os: windows-2025 preset: msvc-debug + ruby: mswin + runs-on: ${{ matrix.os }} + steps: - - uses: actions/checkout@v6 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '4.0' - - name: Install libffi (Ubuntu) - if: runner.os == 'Linux' - run: sudo apt-get install -y libffi-dev - - name: Install libffi (macOS) - if: runner.os == 'macOS' - run: brew install libffi - - name: Install libffi (Windows) - if: runner.os == 'Windows' - run: vcpkg install libffi:x64-windows - - name: Configure - run: cmake --preset ${{ matrix.preset }} -DCMAKE_CXX_STANDARD=${{ matrix.cpp_standard }} - - name: Build - run: cmake --build --preset ${{ matrix.preset }} - - name: Test - run: ./build/${{ matrix.preset }}/test/unittest - shell: bash \ No newline at end of file + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y libffi-dev + + - if: runner.os == 'macOS' + run: brew install libffi + + - if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - if: runner.os == 'Windows' + uses: seanmiddleditch/gha-setup-ninja@v6 + + - if: runner.os == 'Windows' + shell: pwsh + run: | + git clone https://github.com/microsoft/vcpkg.git "$env:RUNNER_TEMP\vcpkg" + & "$env:RUNNER_TEMP\vcpkg\bootstrap-vcpkg.bat" + echo "VCPKG_ROOT=$env:RUNNER_TEMP\vcpkg" >> $env:GITHUB_ENV + & "$env:RUNNER_TEMP\vcpkg\vcpkg.exe" install libffi:x64-windows + + - run: cmake --preset ${{ matrix.preset }} -DCMAKE_CXX_STANDARD=${{ matrix.cpp_standard }} + - run: cmake --build --preset ${{ matrix.preset }}