diff --git a/rice/Data_Type.ipp b/rice/Data_Type.ipp index 41cb7d25..06a640be 100644 --- a/rice/Data_Type.ipp +++ b/rice/Data_Type.ipp @@ -267,9 +267,14 @@ namespace Rice if (access == AttrAccess::ReadWrite || access == AttrAccess::Read) detail::NativeAttributeGet::define(klass_, name, std::forward(attribute)); - // Define native attribute setter - if (access == AttrAccess::ReadWrite || access == AttrAccess::Write) - detail::NativeAttributeSet::define(klass_, name, std::forward(attribute)); + using Attr_T = typename detail::NativeAttributeSet::Attr_T; + if constexpr (!std::is_const_v && + (std::is_fundamental_v || std::is_assignable_v)) + { + // Define native attribute setter + if (access == AttrAccess::ReadWrite || access == AttrAccess::Write) + detail::NativeAttributeSet::define(klass_, name, std::forward(attribute)); + } return *this; } diff --git a/rice/detail/NativeAttributeSet.hpp b/rice/detail/NativeAttributeSet.hpp index f5efd83e..7140ca36 100644 --- a/rice/detail/NativeAttributeSet.hpp +++ b/rice/detail/NativeAttributeSet.hpp @@ -13,11 +13,10 @@ namespace Rice { public: using NativeAttribute_T = NativeAttributeSet; - - using T = typename attribute_traits::attr_type; - using T_Unqualified = remove_cv_recursive_t; + using Attr_T = typename attribute_traits::attr_type; + using T_Unqualified = remove_cv_recursive_t; using Receiver_T = typename attribute_traits::class_type; - + public: // Register attribute getter/setter with Ruby static void define(VALUE klass, std::string name, Attribute_T attribute); diff --git a/rice/detail/NativeAttributeSet.ipp b/rice/detail/NativeAttributeSet.ipp index 1060cdfc..284b3031 100644 --- a/rice/detail/NativeAttributeSet.ipp +++ b/rice/detail/NativeAttributeSet.ipp @@ -15,11 +15,6 @@ namespace Rice::detail NativeAttribute_T* nativeAttribute = new NativeAttribute_T(klass, name, std::forward(attribute)); std::unique_ptr native(nativeAttribute); - if (std::is_const_v>) - { - throw std::runtime_error(name + " is readonly"); - } - // Define the write method name std::string setter = name + "="; @@ -51,31 +46,40 @@ namespace Rice::detail template inline VALUE NativeAttributeSet::operator()(int argc, VALUE* argv, VALUE self) { - if constexpr (std::is_fundamental_v> && std::is_pointer_v) + if constexpr (std::is_fundamental_v> && std::is_pointer_v) { - static_assert(true, "An fundamental value, such as an integer, cannot be assigned to an attribute that is a pointer"); + static_assert(true, "An fundamental value, such as an integer, cannot be assigned to an attribute that is a pointer."); } - else if constexpr (std::is_same_v, std::string> && std::is_pointer_v) + else if constexpr (std::is_same_v, std::string> && std::is_pointer_v) { - static_assert(true, "An string cannot be assigned to an attribute that is a pointer"); + static_assert(true, "An string cannot be assigned to an attribute that is a pointer."); } if (argc != 1) { - throw std::runtime_error("Incorrect number of parameter set to attribute writer"); + throw std::runtime_error("Incorrect number of parameters for setting attribute. Attribute: " + this->name_); } VALUE value = argv[0]; - if constexpr (!std::is_null_pointer_v) + if constexpr (!std::is_null_pointer_v && + !std::is_const_v && + (std::is_fundamental_v || std::is_assignable_v)) { Receiver_T* nativeSelf = From_Ruby().convert(self); nativeSelf->*attribute_ = From_Ruby().convert(value); } - else if constexpr (!std::is_const_v>) + else if constexpr (std::is_null_pointer_v && + !std::is_const_v && + (std::is_fundamental_v || std::is_assignable_v)) { *attribute_ = From_Ruby().convert(value); } + else + { + // Should never get here because define_attr won't compile this code, but just in case! + throw std::invalid_argument("Could not set attribute. Attribute: " + this->name_); + } return value; } diff --git a/test/test_Attribute.cpp b/test/test_Attribute.cpp index 1810500c..5073c82c 100644 --- a/test/test_Attribute.cpp +++ b/test/test_Attribute.cpp @@ -20,6 +20,11 @@ namespace { }; + class NotAssignable + { + NotAssignable& operator=(const NotAssignable&) = delete; + }; + struct DataStruct { static inline float staticFloat = 1.0; @@ -29,7 +34,9 @@ namespace std::string readWriteString = "Read Write"; int writeInt = 0; const char* readChars = "Read some chars!"; + const int constInt = 5; SomeClass someClass; + NotAssignable notAssignable; std::string inspect() { @@ -82,6 +89,42 @@ TESTCASE(attributes) ASSERT_EQUAL("Set a string", detail::From_Ruby().convert(result.value())); } +TESTCASE(const_attribute) +{ + Class c = define_class("DataStruct") + .define_constructor(Constructor()) + .define_attr("const_int", &DataStruct::constInt); + + Data_Object o = c.call("new"); + const DataStruct* dataStruct = o.get(); + + ASSERT_EXCEPTION_CHECK( + Exception, + o.call("const_int=", 5), + ASSERT(std::string(ex.what()).find("undefined method `const_int='") == 0) + ); +} + +TESTCASE(not_copyable_attribute) +{ + Class notAssignableClass = define_class("NotAssignable") + .define_constructor(Constructor()); + + Class c = define_class("DataStruct") + .define_constructor(Constructor()) + .define_attr("not_assignable", &DataStruct::notAssignable); + + Data_Object notAssignable = notAssignableClass.call("new"); + + Data_Object o = c.call("new"); + + ASSERT_EXCEPTION_CHECK( + Exception, + o.call("not_assignable=", notAssignable), + ASSERT(std::string(ex.what()).find("undefined method `not_assignable='") == 0) + ); +} + TESTCASE(static_attributes) { Class c = define_class("DataStruct") @@ -144,4 +187,4 @@ TESTCASE(not_defined) c.define_attr("some_class", &DataStruct::someClass), ASSERT_EQUAL(message, ex.what()) ); -} +} \ No newline at end of file