From fd1dc842e0c9e3f04c0c6778d9856158dbcf4705 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 7 Aug 2024 00:11:48 +0200 Subject: [PATCH] Generic GSI methods #_to_const_object (for testing) and #_const_cast. Fixed a class initialization issue - sub classes should not be registered by name at top level in Expressions --- src/gsi/gsi/gsiClassBase.cc | 45 +++++++++++++++++++ src/gsi/gsi/gsiExpression.cc | 24 +++++++--- src/gsi/gsi/gsiMethods.h | 4 +- src/gsi/unit_tests/gsiExpressionTests.cc | 34 ++++++++++++++ src/pya/pya/pyaCallables.cc | 48 ++++++++++++++++++++ src/rba/rba/rba.cc | 57 +++++++++++++++++++----- src/tl/tl/tlExpression.cc | 22 +++++++++ src/tl/tl/tlExpression.h | 36 +++++++++++++++ src/tl/tl/tlVariant.cc | 11 +++++ src/tl/tl/tlVariant.h | 11 +++++ src/tl/unit_tests/tlExpressionTests.cc | 3 ++ testdata/python/basic.py | 38 ++++++++++++++++ testdata/ruby/basic_testcore.rb | 41 +++++++++++++++++ 13 files changed, 357 insertions(+), 17 deletions(-) diff --git a/src/gsi/gsi/gsiClassBase.cc b/src/gsi/gsi/gsiClassBase.cc index c9824a46d..6e75b461e 100644 --- a/src/gsi/gsi/gsiClassBase.cc +++ b/src/gsi/gsi/gsiClassBase.cc @@ -353,6 +353,48 @@ sm_is_const (const char *name) return sm; } +static SpecialMethod * +sm_to_const (const char *name, const gsi::ClassBase *cls) +{ + SpecialMethod *sm = new SpecialMethod (name, + tl::to_string (tr ("@hide")), // provided for test purposes mainly + true, // const + false, // non-static + MethodBase::ToConst); + + gsi::ArgType ret; + ret.set_is_cptr (true); + ret.set_type (gsi::T_object); + ret.set_pass_obj (false); + ret.set_cls (cls); + sm->set_return (ret); + + return sm; +} + +static SpecialMethod * +sm_const_cast (const char *name, const gsi::ClassBase *cls) +{ + SpecialMethod *sm = new SpecialMethod (name, + tl::to_string (tr ("@brief Returns a non-const reference to self.\n" + "Basically, this method allows turning a const object reference to a non-const one. " + "This method is provided as last resort to remove the constness from an object. Usually there is a good reason for a const object reference, so using this method may have undesired side effects.\n" + "\n" + "This method has been introduced in version 0.29.6.")), + true, // const + false, // non-static + MethodBase::ConstCast); + + gsi::ArgType ret; + ret.set_is_ptr (true); + ret.set_type (gsi::T_object); + ret.set_pass_obj (false); + ret.set_cls (cls); + sm->set_return (ret); + + return sm; +} + static SpecialMethod * sm_destroyed (const char *name) { @@ -598,6 +640,9 @@ ClassBase::merge_declarations () non_const_decl->add_method (sm_is_const ("_is_const_object?")); } + non_const_decl->add_method (sm_to_const ("_to_const_object", &*c)); + non_const_decl->add_method (sm_const_cast ("_const_cast", &*c)); + } // finally merge the new classes into the existing ones diff --git a/src/gsi/gsi/gsiExpression.cc b/src/gsi/gsi/gsiExpression.cc index 46ca33374..58795abd4 100644 --- a/src/gsi/gsi/gsiExpression.cc +++ b/src/gsi/gsi/gsiExpression.cc @@ -305,13 +305,17 @@ initialize_expressions () // install the method table: ExpressionMethodTable::initialize_class (*c); - // register a function that creates a class object (use a function to avoid issues with - // late destruction of global variables which the class object is already gone) - const tl::VariantUserClassBase *cc = (*c)->var_cls_cls (); - if (cc) { - tl::Eval::define_global_function ((*c)->name (), new EvalClassFunction (cc)); - } + // Note: skip non-top-level classes + if ((*c)->parent () == 0) { + // register a function that creates a class object (use a function to avoid issues with + // late destruction of global variables which the class object is already gone) + const tl::VariantUserClassBase *cc = (*c)->var_cls_cls (); + if (cc) { + tl::Eval::define_global_function ((*c)->name (), new EvalClassFunction (cc)); + } + + } } } @@ -630,6 +634,14 @@ special_method_impl (gsi::MethodBase::special_method_type smt, tl::Variant &self // nothing to do here for GSI objects } else if (smt == gsi::MethodBase::IsConst) { return tl::Variant (self.user_is_const ()); + } else if (smt == gsi::MethodBase::ToConst) { + tl::Variant res (self); + res.user_change_constness (true); + return res; + } else if (smt == gsi::MethodBase::ConstCast) { + tl::Variant res (self); + res.user_change_constness (false); + return res; } else if (smt == gsi::MethodBase::Destroyed) { if (self.type_code () == tl::Variant::t_user) { diff --git a/src/gsi/gsi/gsiMethods.h b/src/gsi/gsi/gsiMethods.h index e807ad955..d399ff309 100644 --- a/src/gsi/gsi/gsiMethods.h +++ b/src/gsi/gsi/gsiMethods.h @@ -83,7 +83,9 @@ public: Destroy, Create, IsConst, - Destroyed, + ConstCast, + ToConst, + Destroyed, Assign, Dup }; diff --git a/src/gsi/unit_tests/gsiExpressionTests.cc b/src/gsi/unit_tests/gsiExpressionTests.cc index d85f566fe..16c7f4dfd 100644 --- a/src/gsi/unit_tests/gsiExpressionTests.cc +++ b/src/gsi/unit_tests/gsiExpressionTests.cc @@ -829,3 +829,37 @@ TEST(15) v = e.parse("var bb = BB.new; bb.d4(1, 'a', 2.0, BB.E.E3B, 42)").execute(); EXPECT_EQ (v.to_string (), "1,a,2,101,42"); } + +// constness +TEST(16) +{ + tl::Eval e; + tl::Variant v; + v = e.parse ("var b=B.new(); b._is_const_object").execute (); + EXPECT_EQ (v.to_string (), std::string ("false")); + try { + v = e.parse ("var b=B.new(); var bc=b._to_const_object; bc.set_str('abc')").execute (); + EXPECT_EQ (1, 0); + } catch (tl::Exception &ex) { + EXPECT_EQ (ex.msg (), "Cannot call non-const method set_str, class B on a const reference at position 44 (...set_str('abc'))"); + } + v = e.parse ("var e=E.new(); var ec=e.dup; [e._is_const_object, ec._to_const_object._is_const_object]").execute (); + EXPECT_EQ (v.to_string (), std::string ("false,true")); + v = e.parse ("var e=E.new(); var ec=e._to_const_object; e.x=17; [e.x, ec.x]").execute (); + EXPECT_EQ (v.to_string (), std::string ("17,17")); + v = e.parse ("var e=E.new(); var ec=e._to_const_object; ec._is_const_object").execute (); + EXPECT_EQ (v.to_string (), std::string ("true")); + v = e.parse ("var e=E.new(); var ec=e._to_const_object; ec=ec._const_cast; ec._is_const_object").execute (); + EXPECT_EQ (v.to_string (), std::string ("false")); + v = e.parse ("var e=E.new(); var ec=e._to_const_object; ec=ec._const_cast; ec.x=42; e.x").execute (); + EXPECT_EQ (v.to_string (), std::string ("42")); + v = e.parse ("var e=E.new(); var ec=e._to_const_object; e.x=17; ec.x").execute (); + EXPECT_EQ (v.to_string (), std::string ("17")); + try { + v = e.parse ("var e=E.new(); var ec=e._to_const_object; e.x=17; e._destroy; ec.x").execute (); + EXPECT_EQ (1, 0); + } catch (tl::Exception &ex) { + EXPECT_EQ (ex.msg (), "Object has been destroyed already at position 64 (...x)"); + } +} + diff --git a/src/pya/pya/pyaCallables.cc b/src/pya/pya/pyaCallables.cc index 5422c9968..5dfc3e4f1 100644 --- a/src/pya/pya/pyaCallables.cc +++ b/src/pya/pya/pyaCallables.cc @@ -721,6 +721,50 @@ object_is_const (PyObject *self, PyObject *args) return c2python (PYAObjectBase::from_pyobject (self)->const_ref ()); } +/** + * @brief Implements to_const and const_cast + */ +static PyObject * +object_change_const (PyObject *self, PyObject *args, bool to_const) +{ + if (PYAObjectBase::from_pyobject (self)->const_ref () == to_const) { + return self; + } + + const gsi::ClassBase *cls_decl_self = PythonModule::cls_for_type (Py_TYPE (self)); + tl_assert (cls_decl_self != 0); + + if (! PyArg_ParseTuple (args, "")) { + return NULL; + } + + PyObject *new_object = Py_TYPE (self)->tp_alloc (Py_TYPE (self), 0); + PythonRef obj (new_object); + PYAObjectBase *new_pya_base = PYAObjectBase::from_pyobject_unsafe (new_object); + new (new_pya_base) PYAObjectBase (cls_decl_self, new_object); + new_pya_base->set (PYAObjectBase::from_pyobject (self)->obj (), false, to_const, false); + + return obj.release (); +} + +/** + * @brief Implements to_const + */ +static PyObject * +object_to_const (PyObject *self, PyObject *args) +{ + return object_change_const (self, args, true); +} + +/** + * @brief Implements const_cast + */ +static PyObject * +object_const_cast (PyObject *self, PyObject *args) +{ + return object_change_const (self, args, false); +} + static PyObject * special_method_impl (gsi::MethodBase::special_method_type smt, PyObject *self, PyObject *args) { @@ -734,6 +778,10 @@ special_method_impl (gsi::MethodBase::special_method_type smt, PyObject *self, P return object_create (self, args); } else if (smt == gsi::MethodBase::IsConst) { return object_is_const (self, args); + } else if (smt == gsi::MethodBase::ToConst) { + return object_to_const (self, args); + } else if (smt == gsi::MethodBase::ConstCast) { + return object_const_cast (self, args); } else if (smt == gsi::MethodBase::Destroyed) { return object_destroyed (self, args); } else if (smt == gsi::MethodBase::Assign) { diff --git a/src/rba/rba/rba.cc b/src/rba/rba/rba.cc index 21743409b..efda4fce5 100644 --- a/src/rba/rba/rba.cc +++ b/src/rba/rba/rba.cc @@ -913,6 +913,16 @@ handle_exception (const std::string &where) handle_exception ((where)); \ } +static void free_proxy (void *p) +{ + delete ((Proxy *) p); +} + +static void mark_proxy (void *p) +{ + ((Proxy *) p)->mark (); +} + static VALUE destroy (VALUE self) { @@ -971,6 +981,37 @@ is_const (VALUE self) return c2ruby (p->const_ref ()); } +static VALUE +to_const (VALUE self) +{ + Proxy *p = 0; + Data_Get_Struct (self, Proxy, p); + if (! p->const_ref ()) { + // promote to const object + // NOTE: there is only ONE instance we're going to change this instance + // to const here. This has a global effect and this is the reason why this + // method is not public. It is provided for testing purposes mainly. + p->set_const_ref (true); + } + + return self; +} + +static VALUE +const_cast_ (VALUE self) +{ + Proxy *p = 0; + Data_Get_Struct (self, Proxy, p); + if (p->const_ref ()) { + // promote to non-const object + // NOTE: this is a global change of constness and will affect all references + // that exist for this object. + p->set_const_ref (false); + } + + return self; +} + static VALUE assign (VALUE self, VALUE src) { @@ -1024,6 +1065,12 @@ special_method_impl (const gsi::MethodBase *meth, int argc, VALUE *argv, VALUE s } else if (smt == gsi::MethodBase::IsConst) { tl_assert (!ctor); return is_const (self); + } else if (smt == gsi::MethodBase::ToConst) { + tl_assert (!ctor); + return to_const (self); + } else if (smt == gsi::MethodBase::ConstCast) { + tl_assert (!ctor); + return const_cast_ (self); } else if (smt == gsi::MethodBase::Destroyed) { tl_assert (!ctor); return destroyed (self); @@ -1043,16 +1090,6 @@ special_method_impl (const gsi::MethodBase *meth, int argc, VALUE *argv, VALUE s } } -static void free_proxy (void *p) -{ - delete ((Proxy *) p); -} - -static void mark_proxy (void *p) -{ - ((Proxy *) p)->mark (); -} - static VALUE alloc_proxy (VALUE klass) { tl_assert (TYPE (klass) == T_CLASS); diff --git a/src/tl/tl/tlExpression.cc b/src/tl/tl/tlExpression.cc index 0db330f5c..35eaa01a4 100644 --- a/src/tl/tl/tlExpression.cc +++ b/src/tl/tl/tlExpression.cc @@ -3192,6 +3192,17 @@ Eval::set_var (const std::string &name, const tl::Variant &var) m_local_vars.insert (std::make_pair (name, tl::Variant ())).first->second = var; } +tl::Variant * +Eval::var (const std::string &name) +{ + auto f = m_local_vars.find (name); + if (f != m_local_vars.end ()) { + return &f->second; + } else { + return 0; + } +} + void Eval::define_function (const std::string &name, EvalFunction *function) { @@ -3202,6 +3213,17 @@ Eval::define_function (const std::string &name, EvalFunction *function) f = function; } +EvalFunction * +Eval::function (const std::string &name) +{ + auto f = m_local_functions.find (name); + if (f != m_local_functions.end ()) { + return f->second; + } else { + return 0; + } +} + void Eval::eval_top (ExpressionParserContext &ex, std::unique_ptr &n) { diff --git a/src/tl/tl/tlExpression.h b/src/tl/tl/tlExpression.h index 9c7d84d4e..3912d0a5b 100644 --- a/src/tl/tl/tlExpression.h +++ b/src/tl/tl/tlExpression.h @@ -448,6 +448,12 @@ public: */ void define_function (const std::string &name, EvalFunction *function); + /** + * @brief Gets the function for the given name + * Returns 0 if there is no such function. + */ + EvalFunction *function (const std::string &name); + /** * @brief Define a global variable for use within an expression */ @@ -461,6 +467,12 @@ public: */ void set_var (const std::string &name, const tl::Variant &var); + /** + * @brief Gets the function for the given name + * Returns 0 if there is no such function. + */ + tl::Variant *var (const std::string &name); + /** * @brief Parse an expression from the extractor * @@ -543,6 +555,30 @@ public: return m_match_substrings; } + /** + * @brief Gets the global context + */ + static tl::Eval &global_context () + { + return m_global; + } + + /** + * @brief Gets the global context for this context + */ + tl::Eval *global () + { + return mp_global; + } + + /** + * @brief Gets the parent context for this context + */ + tl::Eval *parent () + { + return mp_parent; + } + private: friend class Expression; diff --git a/src/tl/tl/tlVariant.cc b/src/tl/tl/tlVariant.cc index 5bdca8fec..68f7240e9 100644 --- a/src/tl/tl/tlVariant.cc +++ b/src/tl/tl/tlVariant.cc @@ -2738,6 +2738,17 @@ void *Variant::user_unshare () const return const_cast (to_user ()); } +void Variant::user_change_constness (bool constness) +{ + tl_assert (is_user ()); + + if (m_type == t_user) { + m_var.mp_user.cls = m_var.mp_user.cls->change_constness (constness); + } else if (m_type == t_user_ref) { + m_var.mp_user_ref.cls = m_var.mp_user_ref.cls->change_constness (constness); + } +} + void Variant::user_assign (const tl::Variant &other) { tl_assert (is_user ()); diff --git a/src/tl/tl/tlVariant.h b/src/tl/tl/tlVariant.h index 9a81c329d..8669f5c82 100644 --- a/src/tl/tl/tlVariant.h +++ b/src/tl/tl/tlVariant.h @@ -89,6 +89,7 @@ public: virtual const gsi::ClassBase *gsi_cls () const = 0; virtual const tl::EvalClass *eval_cls () const = 0; virtual void *deref_proxy (tl::Object *proxy) const = 0; + virtual const tl::VariantUserClassBase *change_constness (bool constness) const = 0; const void *deref_proxy_const (const tl::Object *proxy) const { @@ -126,6 +127,11 @@ public: return VariantUserClassBase::instance (typeid (T), is_const); } + const tl::VariantUserClassBase *change_constness (bool constness) const + { + return instance (constness); + } + private: static const tl::VariantUserClassBase *ms_instances[4]; @@ -1001,6 +1007,11 @@ public: */ void *user_unshare () const; + /** + * @brief Changes the constness of the user object + */ + void user_change_constness (bool constness); + /** * @brief Assigns the object stored in other to self * diff --git a/src/tl/unit_tests/tlExpressionTests.cc b/src/tl/unit_tests/tlExpressionTests.cc index 3f4710ad4..f00ed10be 100644 --- a/src/tl/unit_tests/tlExpressionTests.cc +++ b/src/tl/unit_tests/tlExpressionTests.cc @@ -465,6 +465,7 @@ public: virtual bool is_ref () const { return false; } virtual void *deref_proxy (tl::Object *) const { return 0; } virtual const gsi::ClassBase*gsi_cls() const { return 0; } + virtual const tl::VariantUserClassBase *change_constness (bool) const { return this; } static BoxClassClass instance; }; @@ -533,6 +534,7 @@ public: virtual bool is_ref () const { return false; } virtual void *deref_proxy (tl::Object *) const { return 0; } virtual const gsi::ClassBase*gsi_cls() const { return 0; } + virtual const tl::VariantUserClassBase *change_constness (bool) const { return this; } static EdgeClassClass instance; }; @@ -1209,3 +1211,4 @@ TEST(20) v = e.parse ("i2.dtrans.disp.y").execute (); EXPECT_EQ (v.to_string (), std::string ("0.3")); } + diff --git a/testdata/python/basic.py b/testdata/python/basic.py index fe720a451..238e6822d 100644 --- a/testdata/python/basic.py +++ b/testdata/python/basic.py @@ -3280,6 +3280,44 @@ class BasicTest(unittest.TestCase): self.assertEqual(bb.d4(1, "a", d=pya.BB.E.E3B, c=2.5), "1,a,2.5,101,nil") self.assertEqual(bb.d4(1, "a", 2.0, pya.BB.E.E3B, 42), "1,a,2,101,42") + # constness + def test_93(self): + + b = pya.B() + self.assertEqual(b.is_const_object(), False) + + bc = b._to_const_object() + self.assertEqual(bc.is_const_object(), True) + + m = "" + try: + bc.set_str("abc") + self.assertEqual(1, 0) + except Exception as ex: + m = str(ex) + + self.assertEqual(m, "Cannot call non-const method on a const reference in B.set_str") + + b = pya.B() + bc = b + self.assertEqual(b._is_const_object(), False) + self.assertEqual(bc._is_const_object(), False) + bc = bc._to_const_object() + b.set_str("abc") + self.assertEqual(b._is_const_object(), False) + self.assertEqual(bc._is_const_object(), True) + self.assertEqual(b.str(), "abc") + self.assertEqual(bc.str(), "abc") + + bnc = bc._const_cast() + self.assertEqual(b._is_const_object(), False) + self.assertEqual(bc._is_const_object(), True) + self.assertEqual(bnc._is_const_object(), False) + bnc.set_str("xyz") + self.assertEqual(b.str(), "xyz") + self.assertEqual(bc.str(), "xyz") + self.assertEqual(bnc.str(), "xyz") + # run unit tests if __name__ == '__main__': suite = unittest.TestSuite() diff --git a/testdata/ruby/basic_testcore.rb b/testdata/ruby/basic_testcore.rb index 6183a77d4..ec073d8b5 100644 --- a/testdata/ruby/basic_testcore.rb +++ b/testdata/ruby/basic_testcore.rb @@ -3233,4 +3233,45 @@ class Basic_TestClass < TestBase end + # constness + def test_82 + + b = RBA::B::new + assert_equal(b.is_const_object, false) + + bc = b._to_const_object + assert_equal(bc.is_const_object, true) + + m = "" + begin + bc.set_str("abc") + assert_equal(1, 0) + rescue => ex + m = ex.to_s + end + + assert_equal(m, "Cannot call non-const method on a const reference in B::set_str") + + b = RBA::B::new + bc = b + assert_equal(b._is_const_object, false) + assert_equal(bc._is_const_object, false) + b.set_str("abc") + bc._to_const_object + assert_equal(b._is_const_object, true) # special + assert_equal(bc._is_const_object, true) + assert_equal(b.str, "abc") + assert_equal(bc.str, "abc") + + bnc = bc._const_cast + assert_equal(b._is_const_object, false) # special + assert_equal(bc._is_const_object, false) # special + assert_equal(bnc._is_const_object, false) + bnc.set_str("xyz") + assert_equal(b.str, "xyz") + assert_equal(bc.str, "xyz") + assert_equal(bnc.str, "xyz") + + end + end