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

This commit is contained in:
Matthias Koefferlein 2024-08-07 00:11:48 +02:00
parent 82b3030352
commit fd1dc842e0
13 changed files with 357 additions and 17 deletions

View File

@ -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

View File

@ -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) {

View File

@ -83,7 +83,9 @@ public:
Destroy,
Create,
IsConst,
Destroyed,
ConstCast,
ToConst,
Destroyed,
Assign,
Dup
};

View File

@ -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)");
}
}

View File

@ -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) {

View File

@ -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<bool> (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);

View File

@ -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<ExpressionNode> &n)
{

View File

@ -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;

View File

@ -2738,6 +2738,17 @@ void *Variant::user_unshare () const
return const_cast<void *> (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 ());

View File

@ -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
*

View File

@ -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"));
}

View File

@ -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()

View File

@ -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