From 576da7667529a61ab67917c861f1c10f6bf3281e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 19 Aug 2020 22:52:30 +0800 Subject: [PATCH 1/3] split QObject::connect lambda into own Call::Body --- .../call_builder/cpp_qobject_connect.cr | 11 ++++---- .../call_builder/cpp_to_crystal_proc.cr | 25 ++++++++++++++++--- src/bindgen/processor/qt.cr | 2 +- src/bindgen/processor/virtual_override.cr | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/bindgen/call_builder/cpp_qobject_connect.cr b/src/bindgen/call_builder/cpp_qobject_connect.cr index 5d8b17b..a984e1f 100644 --- a/src/bindgen/call_builder/cpp_qobject_connect.cr +++ b/src/bindgen/call_builder/cpp_qobject_connect.cr @@ -18,16 +18,17 @@ module Bindgen end class Body < Call::Body - def initialize(@proc : Call) + def initialize(@lambda : Call) end def to_code(call : Call, platform : Graph::Platform) : String formatter = Cpp::Format.new - ptr = formatter.function_pointer(@proc) - lambda_args = formatter.argument_list(call.arguments) - inner = @proc.body.to_code(@proc, platform) - code = %[QObject::connect(_self_, (#{ptr})&#{call.name}, [_proc_](#{lambda_args}){ #{inner}; })] + lambda_body = @lambda.body.to_code(@lambda, platform) + ptr = formatter.function_pointer(@lambda) + + code = %[QObject::connect(_self_, (#{ptr})&#{call.name}, #{lambda_body})] + call.result.apply_conversion(code) end end diff --git a/src/bindgen/call_builder/cpp_to_crystal_proc.cr b/src/bindgen/call_builder/cpp_to_crystal_proc.cr index 41f3c23..0080f69 100644 --- a/src/bindgen/call_builder/cpp_to_crystal_proc.cr +++ b/src/bindgen/call_builder/cpp_to_crystal_proc.cr @@ -7,7 +7,9 @@ module Bindgen end # Calls the *method*, using the *proc_name* to call-through to Crystal. - def build(method : Parser::Method, proc_name : String = "_proc_") : Call + # If *lambda* is true, instead of invoking the *method*, builds a C++ + # lambda expression that wraps the invocation. + def build(method : Parser::Method, *, proc_name : String = "_proc_", lambda = false) : Call pass = Cpp::Pass.new(@db) arguments = pass.arguments_from_cpp(method.arguments) @@ -18,17 +20,34 @@ module Bindgen name: proc_name, result: result, arguments: arguments, - body: Body.new, + body: lambda ? LambdaBody.new : InvokeBody.new, ) end - class Body < Call::Body + # Method invocation. + class InvokeBody < Call::Body def to_code(call : Call, platform : Graph::Platform) : String pass_args = call.arguments.map(&.call).join(", ") code = %[#{call.name}(#{pass_args})] call.result.apply_conversion(code) end end + + # Lambda expression. Captures the `CrystalProc` by value (therefore it + # is *not* convertible to a C function pointer). + class LambdaBody < Call::Body + def to_code(call : Call, platform : Graph::Platform) : String + formatter = Cpp::Format.new + + lambda_args = formatter.argument_list(call.arguments) + pass_args = call.arguments.map(&.call).join(", ") + inner = %[#{call.name}(#{pass_args})] + + prefix = "return " unless call.result.type.pure_void? + + "[#{call.name}](#{lambda_args}){ #{prefix}#{inner}; }" + end + end end end end diff --git a/src/bindgen/processor/qt.cr b/src/bindgen/processor/qt.cr index 8fb5107..9a4f337 100644 --- a/src/bindgen/processor/qt.cr +++ b/src/bindgen/processor/qt.cr @@ -90,7 +90,7 @@ module Bindgen call = CallBuilder::CppQobjectConnect.new(@db) wrapper = CallBuilder::CppWrapper.new(@db) - proc = to_proc.build(method) + proc = to_proc.build(method, lambda: true) connector.calls[Graph::Platform::Cpp] = wrapper.build( method: connector.origin, target: call.build(method, proc), diff --git a/src/bindgen/processor/virtual_override.cr b/src/bindgen/processor/virtual_override.cr index 850fa4a..adfe740 100644 --- a/src/bindgen/processor/virtual_override.cr +++ b/src/bindgen/processor/virtual_override.cr @@ -242,7 +242,7 @@ module Bindgen method: method, class_name: class_name, target: target, - virtual_target: to_crystal.build(method, proc_name), + virtual_target: to_crystal.build(method, proc_name: proc_name), in_superclass: in_superclass, ) end From 5b1d88f3fb7a6c6960a42ef9871bd341343ca89d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 20 Aug 2020 03:56:22 +0800 Subject: [PATCH 2/3] use CrystalFromCpp to generate #on_X procs --- spec/integration/qt.cpp | 20 ++++++++++++++++ spec/integration/qt.yml | 13 ++++++++++ spec/integration/qt_spec.cr | 20 ++++++++++++++++ src/bindgen/crystal/pass.cr | 47 ++++++++++++++++++++++++++++++++++++- 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/spec/integration/qt.cpp b/spec/integration/qt.cpp index 1ab7a4a..5d3b7cb 100644 --- a/spec/integration/qt.cpp +++ b/spec/integration/qt.cpp @@ -20,6 +20,21 @@ struct QObject { } }; +// Test object conversion at Proc boundaries +struct Conv { +}; + +struct ConvCpp { +}; + +ConvCpp conv_from_cpp(const Conv &) { + return { }; +} + +Conv conv_to_cpp(const ConvCpp &) { + return { }; +} + // On to the actual test classes: // Tests signal/slots connection wrapping @@ -27,6 +42,7 @@ class SomeObject { Q_OBJECT public: int normalMethod() { return 1; } + Conv convMethod() { return { }; } signals: void stuffHappened() { @@ -48,6 +64,10 @@ class SomeObject { void privateSignal(QPrivateSignal) { // Empty. } + + void convSignal(const Conv &) { + // Empty. + } }; // Tests Q_GADGET cleaning diff --git a/spec/integration/qt.yml b/spec/integration/qt.yml index 14709f3..adf22ae 100644 --- a/spec/integration/qt.yml +++ b/spec/integration/qt.yml @@ -14,9 +14,22 @@ classes: "QMetaObject::Connection": SignalConnection SomeObject: SomeObject SomeGadget: SomeGadget + Conv: Conv types: "QMetaObject::Connection": crystal_type: SignalConnection cpp_type: "QMetaObject::Connection" binding_type: QMetaObjectConnection + Conv: + crystal_type: ConvCrystal + cpp_type: ConvCpp + binding_type: ConvBinding + to_cpp: "conv_to_cpp(%)" + from_cpp: "conv_from_cpp(%)" + to_crystal: "BindgenHelper.conv_to_crystal(%)" + from_crystal: "BindgenHelper.conv_from_crystal(%)" + kind: Struct + builtin: true + pass_by: Value + wrapper_pass_by: Value diff --git a/spec/integration/qt_spec.cr b/spec/integration/qt_spec.cr index 9e1ea7f..785bcff 100644 --- a/spec/integration/qt_spec.cr +++ b/spec/integration/qt_spec.cr @@ -3,6 +3,26 @@ require "./spec_helper" describe "Qt-specific wrapper features" do it "works" do build_and_run("qt") do + module Test + lib Binding + struct ConvBinding + x : Int32 + end + end + + alias ConvBinding = Binding::ConvBinding + + module BindgenHelper + def conv_to_crystal(x : Binding::ConvBinding) : ConvCrystal + ConvCrystal.new + end + + def conv_from_crystal(x : ConvCrystal) : Binding::ConvBinding + Binding::ConvBinding.new x: 0 + end + end + end + context "signal behaviour" do it "creates a on_X method" do subject = Test::SomeObject.new diff --git a/src/bindgen/crystal/pass.cr b/src/bindgen/crystal/pass.cr index 2bcbef2..1075375 100644 --- a/src/bindgen/crystal/pass.cr +++ b/src/bindgen/crystal/pass.cr @@ -22,6 +22,18 @@ module Bindgen end end + def arguments_from_binding(list : Enumerable(Parser::Argument)) + argument = Argument.new(@db) + + list.map_with_index do |arg, idx| + if idx == list.size - 1 && arg.variadic? + self.variadic_argument + else + from_binding(arg, qualified: true).to_argument(argument.name(arg, idx)) + end + end + end + # Turns the list of arguments into a list of `Call::Argument`s. def arguments_to_wrapper(list : Enumerable(Parser::Argument)) argument = Argument.new(@db) @@ -65,13 +77,35 @@ module Bindgen is_ref, ptr = reconfigure_pass_type(rules.pass_by, is_ref, ptr) end + # HACK: Since the conversion *template* for function types cannot be a + # simple string substitution, we simply build the entire string here, + # assuming the result is oblivious to `Util.template`. Ideally, + # *template* should be lifted to a class hierarchy. + if type.kind.function? + func_arg_types = type.template.not_nil!.arguments.dup + func_ret_type = func_arg_types.shift + func_args = func_arg_types.map_with_index do |type, i| + Parser::Argument.new("arg#{i}", type) + end + builder = CallBuilder::CrystalFromCpp.new(@db) + call = builder.build(Parser::Method.build( + name: "call", + return_type: func_ret_type, + arguments: func_args, + class_name: "Proc", + ), receiver: "_proc_") + code = call.body.to_code(call, Graph::Platform::Crystal) + template = %[BindgenHelper.wrap_proc(#{code.gsub(/\{(.*)\}/, " do \\1 end ")})] + end + ptr += 1 if is_ref # Translate reference to pointer is_ref = false {is_ref, ptr, type_name, template, false} end end - # Builds the type-name of a Crystal `Proc`, from the function *type*. + # Builds the type-name of a Crystal `Proc` for Crystal wrappers, from the + # function *type*. private def proc_to_wrapper(type) args = type.template.not_nil!.arguments @@ -85,6 +119,17 @@ module Bindgen "Proc(#{names})" end + # As above, but for Crystal bindings. + private def proc_to_binding(type) + args = type.template.not_nil!.arguments + + types = args[1..-1] + types << args.first + names = types.map { |t| to_binding(t, qualified: true).type_name.as(String) }.join(", ") + + "Proc(#{names})" + end + # Computes a result for passing *type* to the wrapper. def to_wrapper(type : Parser::Type) : Call::Result to(type) do |is_ref, ptr, type_name, nilable| From 64184ab16f133e905407224404f1bac32d92425c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 29 Aug 2020 00:18:34 +0800 Subject: [PATCH 3/3] actually use Template:::ProcFromWrapper --- src/bindgen/crystal/pass.cr | 42 +------------------------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/src/bindgen/crystal/pass.cr b/src/bindgen/crystal/pass.cr index 1075375..af3681c 100644 --- a/src/bindgen/crystal/pass.cr +++ b/src/bindgen/crystal/pass.cr @@ -22,18 +22,6 @@ module Bindgen end end - def arguments_from_binding(list : Enumerable(Parser::Argument)) - argument = Argument.new(@db) - - list.map_with_index do |arg, idx| - if idx == list.size - 1 && arg.variadic? - self.variadic_argument - else - from_binding(arg, qualified: true).to_argument(argument.name(arg, idx)) - end - end - end - # Turns the list of arguments into a list of `Call::Argument`s. def arguments_to_wrapper(list : Enumerable(Parser::Argument)) argument = Argument.new(@db) @@ -77,25 +65,8 @@ module Bindgen is_ref, ptr = reconfigure_pass_type(rules.pass_by, is_ref, ptr) end - # HACK: Since the conversion *template* for function types cannot be a - # simple string substitution, we simply build the entire string here, - # assuming the result is oblivious to `Util.template`. Ideally, - # *template* should be lifted to a class hierarchy. if type.kind.function? - func_arg_types = type.template.not_nil!.arguments.dup - func_ret_type = func_arg_types.shift - func_args = func_arg_types.map_with_index do |type, i| - Parser::Argument.new("arg#{i}", type) - end - builder = CallBuilder::CrystalFromCpp.new(@db) - call = builder.build(Parser::Method.build( - name: "call", - return_type: func_ret_type, - arguments: func_args, - class_name: "Proc", - ), receiver: "_proc_") - code = call.body.to_code(call, Graph::Platform::Crystal) - template = %[BindgenHelper.wrap_proc(#{code.gsub(/\{(.*)\}/, " do \\1 end ")})] + template = Template::ProcFromWrapper.new(type, @db).followed_by(template) end ptr += 1 if is_ref # Translate reference to pointer @@ -119,17 +90,6 @@ module Bindgen "Proc(#{names})" end - # As above, but for Crystal bindings. - private def proc_to_binding(type) - args = type.template.not_nil!.arguments - - types = args[1..-1] - types << args.first - names = types.map { |t| to_binding(t, qualified: true).type_name.as(String) }.join(", ") - - "Proc(#{names})" - end - # Computes a result for passing *type* to the wrapper. def to_wrapper(type : Parser::Type) : Call::Result to(type) do |is_ref, ptr, type_name, nilable|