From d4d6cc2abbfff21dd8978c50c2a98ce7cf11515e Mon Sep 17 00:00:00 2001 From: Rico Tzschichholz Date: Sun, 10 Sep 2017 15:49:56 +0200 Subject: [PATCH] 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(-) create mode 100644 tests/objects/signals-delegate.vala diff --git a/codegen/valagsignalmodule.vala b/codegen/valagsignalmodule.vala index 306c374cd..5e3424531 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 6f1874739..d8b7c8ff3 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 000000000..1e94603b8 --- /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 83b918dbd..d34055434 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 2238b9814..8e62d750b 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 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; } -- 2.11.4.GIT