1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 /**
4  Type-erased protocol for synchronous functions.
5  */
6 internal protocol AnySyncFunctionComponent: AnyFunction {
7   /**
8    Calls the function synchronously with given arguments.
9    - Parameters:
10      - owner: An object that calls this function. If the `takesOwner` property is true
11        and type of the first argument matches the owner type, it's being passed as the argument.
12      - args: An array of arguments to pass to the function. The arguments must be of the same type as in the underlying closure.
13      - appContext: An app context where the function is executed.
14    - Returns: A value returned by the called function when succeeded or an error when it failed.
15    */
16   func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext) throws -> Any
17 }
18 
19 /**
20  Represents a function that can only be called synchronously.
21  */
22 public final class SyncFunctionComponent<Args, FirstArgType, ReturnType>: AnySyncFunctionComponent {
23   typealias ClosureType = (Args) throws -> ReturnType
24 
25   /**
26    The underlying closure to run when the function is called.
27    */
28   let body: ClosureType
29 
30   init(
31     _ name: String,
32     firstArgType: FirstArgType.Type,
33     dynamicArgumentTypes: [AnyDynamicType],
34     _ body: @escaping ClosureType
35   ) {
36     self.name = name
37     self.dynamicArgumentTypes = dynamicArgumentTypes
38     self.body = body
39   }
40 
41   // MARK: - AnyFunction
42 
43   let name: String
44 
45   let dynamicArgumentTypes: [AnyDynamicType]
46 
47   var argumentsCount: Int {
48     return dynamicArgumentTypes.count - (takesOwner ? 1 : 0)
49   }
50 
51   var takesOwner: Bool = false
52 
53   func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> ()) {
54     do {
55       let result = try call(by: owner, withArguments: args, appContext: appContext)
56       callback(.success(Conversions.convertFunctionResult(result)))
57     } catch let error as Exception {
58       callback(.failure(error))
59     } catch {
60       callback(.failure(UnexpectedException(error)))
61     }
62   }
63 
64   // MARK: - AnySyncFunctionComponent
65 
66   func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext) throws -> Any {
67     do {
68       try validateArgumentsNumber(function: self, received: args.count)
69 
70       var arguments = concat(
71         arguments: args,
72         withOwner: owner,
73         withPromise: nil,
74         forFunction: self,
75         appContext: appContext
76       )
77 
78       // Convert JS values to non-JS native types.
79       arguments = try cast(jsValues: arguments, forFunction: self, appContext: appContext)
80 
81       // Convert arguments to the types desired by the function.
82       arguments = try cast(arguments: arguments, forFunction: self, appContext: appContext)
83 
84       let argumentsTuple = try Conversions.toTuple(arguments) as! Args
85       let result = try body(argumentsTuple)
86       return Conversions.convertFunctionResult(result, appContext: appContext)
87     } catch let error as Exception {
88       throw FunctionCallException(name).causedBy(error)
89     } catch {
90       throw UnexpectedException(error)
91     }
92   }
93 
94   // MARK: - JavaScriptObjectBuilder
95 
96   func build(appContext: AppContext) throws -> JavaScriptObject {
97     // We intentionally capture a strong reference to `self`, otherwise the "detached" objects would
98     // immediately lose the reference to the definition and thus the underlying native function.
99     // It may potentially cause memory leaks, but at the time of writing this comment,
100     // the native definition instance deallocates correctly when the JS VM triggers the garbage collector.
101     return try appContext.runtime.createSyncFunction(name, argsCount: argumentsCount) { [weak appContext, self] this, args in
102       guard let appContext else {
103         throw Exceptions.AppContextLost()
104       }
105       let result = try self.call(by: this, withArguments: args, appContext: appContext)
106       return Conversions.convertFunctionResult(result, appContext: appContext)
107     }
108   }
109 }
110 
111 /**
112  Synchronous function without arguments.
113  */
114 public func Function<R>(
115   _ name: String,
116   @_implicitSelfCapture _ closure: @escaping () throws -> R
117 ) -> SyncFunctionComponent<(), Void, R> {
118   return SyncFunctionComponent(
119     name,
120     firstArgType: Void.self,
121     dynamicArgumentTypes: [],
122     closure
123   )
124 }
125 
126 /**
127  Synchronous function with one argument.
128  */
129 public func Function<R, A0: AnyArgument>(
130   _ name: String,
131   @_implicitSelfCapture _ closure: @escaping (A0) throws -> R
132 ) -> SyncFunctionComponent<(A0), A0, R> {
133   return SyncFunctionComponent(
134     name,
135     firstArgType: A0.self,
136     dynamicArgumentTypes: [~A0.self],
137     closure
138   )
139 }
140 
141 /**
142  Synchronous function with two arguments.
143  */
144 public func Function<R, A0: AnyArgument, A1: AnyArgument>(
145   _ name: String,
146   @_implicitSelfCapture _ closure: @escaping (A0, A1) throws -> R
147 ) -> SyncFunctionComponent<(A0, A1), A0, R> {
148   return SyncFunctionComponent(
149     name,
150     firstArgType: A0.self,
151     dynamicArgumentTypes: [~A0.self, ~A1.self],
152     closure
153   )
154 }
155 
156 /**
157  Synchronous function with three arguments.
158  */
159 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>(
160   _ name: String,
161   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) throws -> R
162 ) -> SyncFunctionComponent<(A0, A1, A2), A0, R> {
163   return SyncFunctionComponent(
164     name,
165     firstArgType: A0.self,
166     dynamicArgumentTypes: [
167       ~A0.self,
168       ~A1.self,
169       ~A2.self
170     ],
171     closure
172   )
173 }
174 
175 /**
176  Synchronous function with four arguments.
177  */
178 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>(
179   _ name: String,
180   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) throws -> R
181 ) -> SyncFunctionComponent<(A0, A1, A2, A3), A0, R> {
182   return SyncFunctionComponent(
183     name,
184     firstArgType: A0.self,
185     dynamicArgumentTypes: [
186       ~A0.self,
187       ~A1.self,
188       ~A2.self,
189       ~A3.self
190     ],
191     closure
192   )
193 }
194 
195 /**
196  Synchronous function with five arguments.
197  */
198 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>(
199   _ name: String,
200   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) throws -> R
201 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4), A0, R> {
202   return SyncFunctionComponent(
203     name,
204     firstArgType: A0.self,
205     dynamicArgumentTypes: [
206       ~A0.self,
207       ~A1.self,
208       ~A2.self,
209       ~A3.self,
210       ~A4.self
211     ],
212     closure
213   )
214 }
215 
216 /**
217  Synchronous function with six arguments.
218  */
219 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>(
220   _ name: String,
221   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) throws -> R
222 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5), A0, R> {
223   return SyncFunctionComponent(
224     name,
225     firstArgType: A0.self,
226     dynamicArgumentTypes: [
227       ~A0.self,
228       ~A1.self,
229       ~A2.self,
230       ~A3.self,
231       ~A4.self,
232       ~A5.self
233     ],
234     closure
235   )
236 }
237 
238 /**
239  Synchronous function with seven arguments.
240  */
241 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>(
242   _ name: String,
243   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) throws -> R
244 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6), A0, R> {
245   return SyncFunctionComponent(
246     name,
247     firstArgType: A0.self,
248     dynamicArgumentTypes: [
249       ~A0.self,
250       ~A1.self,
251       ~A2.self,
252       ~A3.self,
253       ~A4.self,
254       ~A5.self,
255       ~A6.self
256     ],
257     closure
258   )
259 }
260 
261 /**
262  Synchronous function with eight arguments.
263  */
264 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument, A7: AnyArgument>(
265   _ name: String,
266   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) throws -> R
267 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> {
268   return SyncFunctionComponent(
269     name,
270     firstArgType: A0.self,
271     dynamicArgumentTypes: [
272       ~A0.self,
273       ~A1.self,
274       ~A2.self,
275       ~A3.self,
276       ~A4.self,
277       ~A5.self,
278       ~A6.self,
279       ~A7.self
280     ],
281     closure
282   )
283 }
284