[vala/staging: 6/6] Allow to pass compatible delegates to signal.connect()



commit d4d6cc2abbfff21dd8978c50c2a98ce7cf11515e
Author: Rico Tzschichholz <ricotz ubuntu com>
Date:   Sun Sep 10 15:49:56 2017 +0200

    Allow to pass compatible delegates to signal.connect()
    
    https://bugzilla.gnome.org/show_bug.cgi?id=787521

 codegen/valagsignalmodule.vala      |   32 +++++++++--
 tests/Makefile.am                   |    1 +
 tests/objects/signals-delegate.vala |  108 +++++++++++++++++++++++++++++++++++
 vala/valadatatype.vala              |    4 -
 vala/valadelegatetype.vala          |   66 +++++++++++++++++++++
 5 files changed, 202 insertions(+), 9 deletions(-)
---
diff --git a/codegen/valagsignalmodule.vala b/codegen/valagsignalmodule.vala
index 306c374..5e34245 100644
--- a/codegen/valagsignalmodule.vala
+++ b/codegen/valagsignalmodule.vala
@@ -603,7 +603,15 @@ public class Vala.GSignalModule : GObjectModule {
        CCodeExpression? connect_signal (Signal sig, Expression signal_access, Expression handler, bool 
disconnect, bool after, CodeNode expr) {
                string connect_func;
 
-               var m = (Method) handler.symbol_reference;
+               DelegateType? dt = null;
+               var p = handler.symbol_reference as Parameter;
+               if (p != null) {
+                       dt = p.variable_type as DelegateType;
+                       if (dt != null && !context.experimental) {
+                               Report.warning (dt.source_reference, "Connecting delegates to signals is 
experimental");
+                       }
+               }
+               var m = handler.symbol_reference as Method;
 
                if (!disconnect) {
                        // connect
@@ -613,9 +621,9 @@ public class Vala.GSignalModule : GObjectModule {
                                else
                                        connect_func = get_dynamic_signal_connect_after_wrapper_name 
((DynamicSignal) sig);
                        } else {
-                               if (m.closure) {
+                               if ((m != null && m.closure) || (dt != null && dt.value_owned)) {
                                        connect_func = "g_signal_connect_data";
-                               } else if (in_gobject_instance (m)) {
+                               } else if (m != null && in_gobject_instance (m)) {
                                        connect_func = "g_signal_connect_object";
                                } else if (!after) {
                                        connect_func = "g_signal_connect";
@@ -714,7 +722,7 @@ public class Vala.GSignalModule : GObjectModule {
                // third resp. sixth argument: handler
                ccall.add_argument (new CCodeCastExpression (get_cvalue (handler), "GCallback"));
 
-               if (m.closure) {
+               if (m != null && m.closure) {
                        // g_signal_connect_data
 
                        // fourth argument: user_data
@@ -729,7 +737,7 @@ public class Vala.GSignalModule : GObjectModule {
                                ccall.add_argument (new CCodeConstant ("0"));
                        else
                                ccall.add_argument (new CCodeConstant ("G_CONNECT_AFTER"));
-               } else if (m.binding == MemberBinding.INSTANCE) {
+               } else if (m != null && m.binding == MemberBinding.INSTANCE) {
                        // g_signal_connect_object or g_signal_handlers_disconnect_matched
                        // or dynamic_signal_connect or dynamic_signal_disconnect
 
@@ -754,6 +762,20 @@ public class Vala.GSignalModule : GObjectModule {
                                else
                                        ccall.add_argument (new CCodeConstant ("G_CONNECT_AFTER"));
                        }
+               } else if (dt != null && dt.delegate_symbol.has_target) {
+                       // fourth argument: user_data
+                       CCodeExpression handler_destroy_notify;
+                       ccall.add_argument (get_delegate_target_cexpression (handler, out 
handler_destroy_notify));
+                       if (!disconnect && dt.value_owned) {
+                               // fifth argument: destroy_notify
+                               //FIXME handler_destroy_notify is NULL
+                               ccall.add_argument (new CCodeCastExpression (handler_destroy_notify, 
"GClosureNotify"));
+                               // sixth argument: connect_flags
+                               if (!after)
+                                       ccall.add_argument (new CCodeConstant ("0"));
+                               else
+                                       ccall.add_argument (new CCodeConstant ("G_CONNECT_AFTER"));
+                       }
                } else {
                        // g_signal_connect or g_signal_connect_after or g_signal_handlers_disconnect_matched
                        // or dynamic_signal_connect or dynamic_signal_disconnect
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6f18747..d8b7c8f 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -180,6 +180,7 @@ TESTS = \
        objects/properties.vala \
        objects/regex.vala \
        objects/signals.vala \
+       objects/signals-delegate.vala \
        objects/test-025.vala \
        objects/test-026.vala \
        objects/test-029.vala \
diff --git a/tests/objects/signals-delegate.vala b/tests/objects/signals-delegate.vala
new file mode 100644
index 0000000..1e94603
--- /dev/null
+++ b/tests/objects/signals-delegate.vala
@@ -0,0 +1,108 @@
+public delegate string FooFunc (Foo foo, string s);
+
+public class Foo : Object {
+       public signal string test (string s);
+
+       public void add (FooFunc func) {
+               test.connect (func);
+       }
+
+       public void add_owned (owned FooFunc func) {
+               test.connect (func);
+       }
+
+       public void add_remove (FooFunc func) {
+               test.connect (func);
+               test.disconnect (func);
+       }
+
+       public void add_remove_owned (owned FooFunc func) {
+               test.connect (func);
+               test.disconnect (func);
+       }
+
+       public void invoke_test () {
+               assert (test ("bar") == "foo");
+       }
+
+       public void invoke_test_empty () {
+               assert (test ("bar") == null);
+       }
+}
+
+public class Bar : Object {
+       public signal string test (string s);
+
+       int i;
+
+       public Bar (Foo foo) {
+               i = 42;
+               foo.add (instance_callback);
+       }
+
+       public Bar.owned (Foo foo) {
+               i = 42;
+               foo.add_owned (instance_callback);
+       }
+
+       public Bar.remove (Foo foo) {
+               i = 42;
+               foo.add_remove (instance_callback);
+       }
+
+       public Bar.remove_owned (Foo foo) {
+               i = 42;
+               foo.add_remove_owned (instance_callback);
+       }
+
+       string instance_callback (Foo foo, string s) {
+               assert (foo is Foo);
+               assert (this is Bar);
+               assert (s == "bar");
+               assert (i == 42);
+               return "foo";
+       }
+}
+
+string callback_static (Foo foo, string s) {
+       assert (foo is Foo);
+       assert (s == "bar");
+       return "foo";
+}
+
+void main () {
+       Foo foo;
+       Bar bar;
+
+       foo = new Foo ();
+       foo.add ((FooFunc) callback_static);
+       foo.invoke_test ();
+
+       foo = new Foo ();
+       foo.add_owned ((FooFunc) callback_static);
+       foo.invoke_test ();
+
+       foo = new Foo ();
+       foo.add_remove ((FooFunc) callback_static);
+       foo.invoke_test_empty ();
+
+       foo = new Foo ();
+       foo.add_remove_owned ((FooFunc) callback_static);
+       foo.invoke_test_empty ();
+
+       foo = new Foo ();
+       bar = new Bar (foo);
+       foo.invoke_test ();
+
+       foo = new Foo ();
+       bar = new Bar.owned (foo);
+       foo.invoke_test ();
+
+       foo = new Foo ();
+       bar = new Bar.remove (foo);
+       foo.invoke_test_empty ();
+
+       foo = new Foo ();
+       bar = new Bar.remove_owned (foo);
+       foo.invoke_test_empty ();
+}
diff --git a/vala/valadatatype.vala b/vala/valadatatype.vala
index 83b918d..d340554 100644
--- a/vala/valadatatype.vala
+++ b/vala/valadatatype.vala
@@ -285,10 +285,6 @@ public abstract class Vala.DataType : CodeNode {
                        }
                }
 
-               if (target_type is DelegateType && this is DelegateType) {
-                       return ((DelegateType) target_type).delegate_symbol == ((DelegateType) 
this).delegate_symbol;
-               }
-
                if (target_type is PointerType) {
                        /* any reference or array type or pointer type can be cast to a generic pointer */
                        if (type_parameter != null ||
diff --git a/vala/valadelegatetype.vala b/vala/valadelegatetype.vala
index 2238b98..8e62d75 100644
--- a/vala/valadelegatetype.vala
+++ b/vala/valadelegatetype.vala
@@ -140,6 +140,72 @@ public class Vala.DelegateType : CallableType {
                return true;
        }
 
+       public override bool compatible (DataType target_type) {
+               var dt_target = target_type as DelegateType;
+               if (dt_target == null) {
+                       return false;
+               }
+
+               // trivial case
+               if (delegate_symbol == dt_target.delegate_symbol) {
+                       return true;
+               }
+
+               // target-delegate is allowed to ensure stricter return type (stronger postcondition)
+               if (!get_return_type ().stricter (dt_target.get_return_type ().get_actual_type (dt_target, 
null, this))) {
+                       return false;
+               }
+
+               var parameters = get_parameters ();
+               Iterator<Parameter> params_it = parameters.iterator ();
+
+               if (dt_target.delegate_symbol.parent_symbol is Signal && 
dt_target.delegate_symbol.sender_type != null && parameters.size == dt_target.get_parameters ().size + 1) {
+                       // target-delegate has sender parameter
+                       params_it.next ();
+
+                       // target-delegate is allowed to accept arguments of looser types (weaker 
precondition)
+                       var p = params_it.get ();
+                       if (!dt_target.delegate_symbol.sender_type.stricter (p.variable_type)) {
+                               return false;
+                       }
+               }
+
+               foreach (Parameter param in dt_target.get_parameters ()) {
+                       /* target-delegate is allowed to accept less arguments */
+                       if (!params_it.next ()) {
+                               break;
+                       }
+
+                       // target-delegate is allowed to accept arguments of looser types (weaker 
precondition)
+                       var p = params_it.get ();
+                       if (!param.variable_type.get_actual_type (this, null, this).stricter 
(p.variable_type)) {
+                               return false;
+                       }
+               }
+
+               /* target-delegate may not expect more arguments */
+               if (params_it.next ()) {
+                       return false;
+               }
+
+               // target-delegate may throw less but not more errors than the delegate
+               foreach (DataType error_type in get_error_types ()) {
+                       bool match = false;
+                       foreach (DataType delegate_error_type in dt_target.get_error_types ()) {
+                               if (error_type.compatible (delegate_error_type)) {
+                                       match = true;
+                                       break;
+                               }
+                       }
+
+                       if (!match) {
+                               return false;
+                       }
+               }
+
+               return true;
+       }
+
        public override bool is_disposable () {
                return delegate_symbol.has_target && value_owned && !is_called_once;
        }


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