summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
blob: aec8ded930ef6a88184c3f3d82bb126e23e744f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
{%- let cbi = ci|get_callback_interface_definition(name) %}
{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %}

// Declaration and FfiConverters for {{ type_name }} Callback Interface

public protocol {{ type_name }} : AnyObject {
    {% for meth in cbi.methods() -%}
    func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%}
    {%- match meth.return_type() -%}
    {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
    {%- else -%}
    {%- endmatch %}
    {% endfor %}
}

// The ForeignCallback that is passed to Rust.
fileprivate let {{ foreign_callback }} : ForeignCallback =
    { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer<UInt8>, argsLen: Int32, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in
    {% for meth in cbi.methods() -%}
    {%- let method_name = format!("invoke_{}", meth.name())|fn_name %}

    func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer<UInt8>, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer<RustBuffer>) throws -> Int32 {
        {%- if meth.arguments().len() > 0 %}
        var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen)))
        {%- endif %}

        {%- match meth.return_type() %}
        {%- when Some(return_type) %}
        func makeCall() throws -> Int32 {
            let result = {% if meth.throws() %} try{% endif %} swiftCallbackInterface.{{ meth.name()|fn_name }}(
                    {% for arg in meth.arguments() -%}
                    {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader)
                    {%- if !loop.last %}, {% endif %}
                    {% endfor -%}
                )
            var writer = [UInt8]()
            {{ return_type|write_fn }}(result, into: &writer)
            out_buf.pointee = RustBuffer(bytes: writer)
            return UNIFFI_CALLBACK_SUCCESS
        }
        {%- when None %}
        func makeCall() throws -> Int32 {
            try swiftCallbackInterface.{{ meth.name()|fn_name }}(
                    {% for arg in meth.arguments() -%}
                    {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader)
                    {%- if !loop.last %}, {% endif %}
                    {% endfor -%}
                )
            return UNIFFI_CALLBACK_SUCCESS
        }
        {%- endmatch %}

        {%- match meth.throws_type() %}
        {%- when None %}
        return try makeCall()
        {%- when Some(error_type) %}
        do {
            return try makeCall()
        } catch let error as {{ error_type|type_name }} {
            out_buf.pointee = {{ error_type|lower_fn }}(error)
            return UNIFFI_CALLBACK_ERROR
        }
        {%- endmatch %}
    }
    {%- endfor %}


    switch method {
        case IDX_CALLBACK_FREE:
            {{ ffi_converter_name }}.drop(handle: handle)
            // Sucessful return
            // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
            return UNIFFI_CALLBACK_SUCCESS
        {% for meth in cbi.methods() -%}
        {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
        case {{ loop.index }}:
            let cb: {{ cbi|type_name }}
            do {
                cb = try {{ ffi_converter_name }}.lift(handle)
            } catch {
                out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle")
                return UNIFFI_CALLBACK_UNEXPECTED_ERROR
            }
            do {
                return try {{ method_name }}(cb, argsData, argsLen, out_buf)
            } catch let error {
                out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
                return UNIFFI_CALLBACK_UNEXPECTED_ERROR
            }
        {% endfor %}
        // This should never happen, because an out of bounds method index won't
        // ever be used. Once we can catch errors, we should return an InternalError.
        // https://github.com/mozilla/uniffi-rs/issues/351
        default:
            // An unexpected error happened.
            // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
            return UNIFFI_CALLBACK_UNEXPECTED_ERROR
    }
}

// FfiConverter protocol for callback interfaces
fileprivate struct {{ ffi_converter_name }} {
    private static let initCallbackOnce: () = {
        // Swift ensures this initializer code will once run once, even when accessed by multiple threads.
        try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in
            {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)
        }
    }()

    private static func ensureCallbackinitialized() {
        _ = initCallbackOnce
    }

    static func drop(handle: UniFFICallbackHandle) {
        handleMap.remove(handle: handle)
    }

    private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>()
}

extension {{ ffi_converter_name }} : FfiConverter {
    typealias SwiftType = {{ type_name }}
    // We can use Handle as the FfiType because it's a typealias to UInt64
    typealias FfiType = UniFFICallbackHandle

    public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType {
        ensureCallbackinitialized();
        guard let callback = handleMap.get(handle: handle) else {
            throw UniffiInternalError.unexpectedStaleHandle
        }
        return callback
    }

    public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
        ensureCallbackinitialized();
        let handle: UniFFICallbackHandle = try readInt(&buf)
        return try lift(handle)
    }

    public static func lower(_ v: SwiftType) -> UniFFICallbackHandle {
        ensureCallbackinitialized();
        return handleMap.insert(obj: v)
    }

    public static func write(_ v: SwiftType, into buf: inout [UInt8]) {
        ensureCallbackinitialized();
        writeInt(&buf, lower(v))
    }
}