[vala/wip/singleton: 90/90] Add support for SingleInstance attribute for GObject classes



commit dc7202d69d9945a31e0b89caf6a5731aef60fabc
Author: Rico Tzschichholz <ricotz ubuntu com>
Date:   Tue Jul 3 12:34:07 2018 +0200

    Add support for SingleInstance attribute for GObject classes
    
    Fixes https://gitlab.gnome.org/GNOME/vala/issues/647

 codegen/valagobjectmodule.vala                  | 73 +++++++++++++++++++++++++
 tests/Makefile.am                               |  3 +
 tests/objects/singleton.vala                    | 59 ++++++++++++++++++++
 tests/semantic/class-singleton-base.test        | 11 ++++
 tests/semantic/class-singleton-non-gobject.test |  8 +++
 vala/valaclass.vala                             | 34 ++++++++++++
 vala/valausedattr.vala                          |  1 +
 valadoc/treebuilder.vala                        |  1 +
 8 files changed, 190 insertions(+)
---
diff --git a/codegen/valagobjectmodule.vala b/codegen/valagobjectmodule.vala
index ad808a138..837046742 100644
--- a/codegen/valagobjectmodule.vala
+++ b/codegen/valagobjectmodule.vala
@@ -454,6 +454,63 @@ public class Vala.GObjectModule : GTypeModule {
                        ccode.add_declaration ("GObject *", new CCodeVariableDeclarator ("obj"));
                        ccode.add_declaration ("GObjectClass *", new CCodeVariableDeclarator 
("parent_class"));
 
+                       if (cl.is_singleton) {
+                               var singleton_ref_name = "%s_singleton__ref".printf (get_ccode_name (cl));
+                               var singleton_lock_name = "%s_singleton__lock".printf (get_ccode_name (cl));
+                               var singleton_once_name = "%s_singleton__volatile".printf (get_ccode_name 
(cl));
+
+                               var singleton_ref = new CCodeDeclaration("GObject *");
+                               singleton_ref.add_declarator (new CCodeVariableDeclarator 
(singleton_ref_name, new CCodeConstant ("NULL")));
+                               singleton_ref.modifiers = CCodeModifiers.STATIC;
+                               ccode.add_statement (singleton_ref);
+
+                               var mutex_lock = new CCodeDeclaration("GMutex");
+                               mutex_lock.add_declarator (new CCodeVariableDeclarator (singleton_lock_name));
+                               mutex_lock.modifiers = CCodeModifiers.STATIC;
+                               ccode.add_statement (mutex_lock);
+
+                               var once_lock = new CCodeDeclaration("gsize");
+                               once_lock.add_declarator (new CCodeVariableDeclarator (singleton_once_name, 
new CCodeConstant ("0")));
+                               once_lock.modifiers = CCodeModifiers.STATIC | CCodeModifiers.VOLATILE;
+                               ccode.add_statement (once_lock);
+
+                               var once_init = new CCodeFunctionCall (new CCodeIdentifier 
("g_once_init_enter"));
+                               once_init.add_argument (new CCodeUnaryExpression 
(CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (singleton_once_name)));
+
+                               var once_block = new CCodeBlock();
+
+                               var singleton_mutex_init = new CCodeFunctionCall (new CCodeIdentifier 
("g_mutex_init"));
+                               singleton_mutex_init.add_argument (new CCodeUnaryExpression 
(CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (singleton_lock_name)));
+                               once_block.add_statement (new CCodeExpressionStatement 
(singleton_mutex_init));
+
+                               var once_leave = new CCodeFunctionCall (new CCodeIdentifier 
("g_once_init_leave"));
+                               once_leave.add_argument (new CCodeUnaryExpression 
(CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (singleton_once_name)));
+                               once_leave.add_argument (new CCodeConstant ("42"));
+                               once_block.add_statement (new CCodeExpressionStatement (once_leave));
+
+                               var if_once = new CCodeIfStatement (once_init, once_block);
+                               ccode.add_statement (if_once);
+
+                               var singleton_mutex_lock = new CCodeFunctionCall (new CCodeIdentifier 
("g_mutex_lock"));
+                               singleton_mutex_lock.add_argument (new CCodeUnaryExpression 
(CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (singleton_lock_name)));
+                               ccode.add_statement (new CCodeExpressionStatement (singleton_mutex_lock));
+
+                               var check_existance = new CCodeBinaryExpression 
(CCodeBinaryOperator.INEQUALITY, new CCodeIdentifier (singleton_ref_name), new CCodeConstant ("NULL"));
+                               var return_singleton = new CCodeBlock();
+
+                               var ref_object = new CCodeFunctionCall (new CCodeIdentifier ("g_object_ref"));
+                               ref_object.add_argument (new CCodeIdentifier (singleton_ref_name));
+                               return_singleton.add_statement (new CCodeExpressionStatement (ref_object));
+
+                               var singleton_mutex_unlock = new CCodeFunctionCall (new CCodeIdentifier 
("g_mutex_unlock"));
+                               singleton_mutex_unlock.add_argument (new CCodeUnaryExpression 
(CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (singleton_lock_name)));
+                               return_singleton.add_statement (new CCodeExpressionStatement 
(singleton_mutex_unlock));
+                               return_singleton.add_statement (new CCodeReturnStatement (new CCodeIdentifier 
(singleton_ref_name)));
+
+                               var if_singleton_alive = new CCodeIfStatement (check_existance, 
return_singleton);
+                               ccode.add_statement (if_singleton_alive);
+                       }
+
                        var ccast = new CCodeFunctionCall (new CCodeIdentifier ("G_OBJECT_CLASS"));
                        ccast.add_argument (new CCodeIdentifier ("%s_parent_class".printf 
(get_ccode_lower_case_name (cl, null))));
                        ccode.add_assignment (new CCodeIdentifier ("parent_class"), ccast);
@@ -479,6 +536,22 @@ public class Vala.GObjectModule : GTypeModule {
                                ccode.add_declaration ("GError *", new CCodeVariableDeclarator.zero 
("_inner_error_", new CCodeConstant ("NULL")));
                        }
 
+                       if (cl.is_singleton) {
+                               var singleton_ref_name = "%s_singleton__ref".printf (get_ccode_name (cl));
+                               var singleton_lock_name = "%s_singleton__lock".printf (get_ccode_name (cl));
+
+                               ccode.add_assignment (new CCodeIdentifier (singleton_ref_name), new 
CCodeIdentifier ("obj"));
+
+                               var set_weak_ref_to_volatile = new CCodeFunctionCall (new CCodeIdentifier 
("g_object_add_weak_pointer"));
+                               set_weak_ref_to_volatile.add_argument (new CCodeIdentifier 
(singleton_ref_name));
+                               set_weak_ref_to_volatile.add_argument (new CCodeCastExpression (new 
CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (singleton_ref_name)), "gpointer"));
+                               ccode.add_statement (new CCodeExpressionStatement (set_weak_ref_to_volatile));
+
+                               var final_singleton_mutex_unlock = new CCodeFunctionCall (new CCodeIdentifier 
("g_mutex_unlock"));
+                               final_singleton_mutex_unlock.add_argument (new CCodeUnaryExpression 
(CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (singleton_lock_name)));
+                               ccode.add_statement (new CCodeExpressionStatement 
(final_singleton_mutex_unlock));
+                       }
+
                        ccode.add_return (new CCodeIdentifier ("obj"));
 
                        pop_function ();
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d18208a13..59cf5685c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -256,6 +256,7 @@ TESTS = \
        objects/regex.vala \
        objects/signals.vala \
        objects/signals-delegate.vala \
+       objects/singleton.vala \
        objects/test-025.vala \
        objects/test-026.vala \
        objects/test-029.vala \
@@ -508,6 +509,8 @@ TESTS = \
        semantic/class-missing-implement-method.test \
        semantic/class-missing-implement-property.test \
        semantic/class-missing-prerequisites.test \
+       semantic/class-singleton-base.test \
+       semantic/class-singleton-non-gobject.test \
        semantic/class-too-few-type-arguments.test \
        semantic/class-too-many-type-arguments.test \
        semantic/constant-extern.test \
diff --git a/tests/objects/singleton.vala b/tests/objects/singleton.vala
new file mode 100644
index 000000000..6a54b5c22
--- /dev/null
+++ b/tests/objects/singleton.vala
@@ -0,0 +1,59 @@
+[SingleInstance]
+public class Foo : Object {
+       public int bar = 42;
+       construct {
+       }
+}
+
+[SingleInstance]
+public class Bar : Object {
+       public int foo = 42;
+}
+
+void lifetime_1 () {
+       Foo a = new Foo ();
+       Foo b = (Foo) Object.new (typeof (Foo));
+
+       assert (a == b);
+       assert (a.bar == 23);
+}
+
+void lifetime_2 () {
+       Foo a = new Foo ();
+       Foo b = (Foo) Object.new (typeof (Foo));
+
+       assert (a == b);
+       assert (a.bar == 42);
+}
+
+void lifetime_3 () {
+       Bar a = new Bar ();
+       Bar b = (Bar) Object.new (typeof (Bar));
+
+       assert (a == b);
+       assert (a.foo == 23);
+}
+
+void main () {
+       {
+               // create singleton instance here
+               // which lives as long until it runs out of scope
+               Foo singleton = new Foo ();
+               singleton.bar = 23;
+               lifetime_1 ();
+       }
+
+       {
+               // create new singleton instance here
+               Foo singleton = new Foo ();
+               assert (singleton.bar == 42);
+               lifetime_2 ();
+       }
+
+       {
+               // create singleton instance here
+               Bar singleton = new Bar ();
+               singleton.foo = 23;
+               lifetime_3 ();
+       }
+}
diff --git a/tests/semantic/class-singleton-base.test b/tests/semantic/class-singleton-base.test
new file mode 100644
index 000000000..7b894035d
--- /dev/null
+++ b/tests/semantic/class-singleton-base.test
@@ -0,0 +1,11 @@
+Invalid Code
+
+[SingleInstance]
+public class Foo : Object {
+}
+
+public class Bar : Foo {
+}
+
+void main () {
+}
diff --git a/tests/semantic/class-singleton-non-gobject.test b/tests/semantic/class-singleton-non-gobject.test
new file mode 100644
index 000000000..89dc9fb02
--- /dev/null
+++ b/tests/semantic/class-singleton-non-gobject.test
@@ -0,0 +1,8 @@
+Invalid Code
+
+[SingleInstance]
+public class Foo {
+}
+
+void main () {
+}
diff --git a/vala/valaclass.vala b/vala/valaclass.vala
index 2167e4e4b..4f5aa4b87 100644
--- a/vala/valaclass.vala
+++ b/vala/valaclass.vala
@@ -79,6 +79,22 @@ public class Vala.Class : ObjectTypeSymbol {
                }
        }
 
+       /**
+        * Instances of immutable classes are immutable after construction.
+        */
+       public bool is_singleton {
+               get {
+                       if (_is_singleton == null) {
+                               _is_singleton = get_attribute ("SingleInstance") != null;
+                       }
+                       return _is_singleton;
+               }
+               set {
+                       _is_singleton = value;
+                       set_attribute ("SingleInstance", value);
+               }
+       }
+
        /**
         * Specifies whether this class has private fields.
         */
@@ -91,6 +107,7 @@ public class Vala.Class : ObjectTypeSymbol {
 
        private bool? _is_compact;
        private bool? _is_immutable;
+       private bool? _is_singleton;
 
        private List<DataType> base_types = new ArrayList<DataType> ();
 
@@ -495,6 +512,23 @@ public class Vala.Class : ObjectTypeSymbol {
                        p.check (context);
                }
 
+               if (base_class != null && base_class.is_singleton) {
+                       error = true;
+                       Report.error (source_reference, "`%s' cannot inherit from SingleInstance class 
`%s'".printf (get_full_name (), base_class.get_full_name ()));
+               }
+
+               if (is_singleton && !is_subtype_of (context.analyzer.object_type)) {
+                       error = true;
+                       Report.error (source_reference, "SingleInstance class `%s' requires inheritance from 
`GLib.Object'".printf (get_full_name ()));
+               }
+
+               /* singleton classes require an instance construtor */
+               if (is_singleton && constructor == null) {
+                       var c = new Constructor (source_reference);
+                       c.body = new Block (source_reference);
+                       add_constructor (c);
+               }
+
                /* process enums first to avoid order problems in C code */
                foreach (Enum en in get_enums ()) {
                        en.check (context);
diff --git a/vala/valausedattr.vala b/vala/valausedattr.vala
index dab15a864..28047daf3 100644
--- a/vala/valausedattr.vala
+++ b/vala/valausedattr.vala
@@ -43,6 +43,7 @@ public class Vala.UsedAttr : CodeVisitor {
                "use_inplace", "feature_test_macro", "default_value_on_error", "",
 
                "Immutable", "",
+               "SingleInstance", "",
                "Compact", "",
                "NoWrapper", "",
                "NoThrow", "",
diff --git a/valadoc/treebuilder.vala b/valadoc/treebuilder.vala
index 5c9a8d72a..5c76aafcc 100644
--- a/valadoc/treebuilder.vala
+++ b/valadoc/treebuilder.vala
@@ -219,6 +219,7 @@ public class Valadoc.Drivers.TreeBuilder : Vala.CodeVisitor {
                                "DestroysInstance",
                                "GenericAccessors",
                                "NoAccessorMethod",
+                               "SingleInstance",
                                "NoArrayLength",
                                "Experimental",
                                "Diagnostics",


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]