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    */
callnull16   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       return try body(argumentsTuple)
86     } catch let error as Exception {
87       throw FunctionCallException(name).causedBy(error)
88     } catch {
89       throw UnexpectedException(error)
90     }
91   }
92 
93   // MARK: - JavaScriptObjectBuilder
94 
95   func build(appContext: AppContext) throws -> JavaScriptObject {
96     // We intentionally capture a strong reference to `self`, otherwise the "detached" objects would
97     // immediately lose the reference to the definition and thus the underlying native function.
98     // It may potentially cause memory leaks, but at the time of writing this comment,
99     // the native definition instance deallocates correctly when the JS VM triggers the garbage collector.
100     return try appContext.runtime.createSyncFunction(name, argsCount: argumentsCount) { [weak appContext, self] this, args in
101       guard let appContext else {
102         throw Exceptions.AppContextLost()
103       }
104       let result = try self.call(by: this, withArguments: args, appContext: appContext)
105       return Conversions.convertFunctionResult(result, appContext: appContext, dynamicType: ~ReturnType.self)
106     }
107   }
108 }
109 
110 /**
111  Synchronous function without arguments.
112  */
113 public func Function<R>(
114   _ name: String,
115   @_implicitSelfCapture _ closure: @escaping () throws -> R
116 ) -> SyncFunctionComponent<(), Void, R> {
117   return SyncFunctionComponent(
118     name,
119     firstArgType: Void.self,
120     dynamicArgumentTypes: [],
121     closure
122   )
123 }
124 
125 /**
126  Synchronous function with one argument.
127  */
128 public func Function<R, A0: AnyArgument>(
129   _ name: String,
130   @_implicitSelfCapture _ closure: @escaping (A0) throws -> R
131 ) -> SyncFunctionComponent<(A0), A0, R> {
132   return SyncFunctionComponent(
133     name,
134     firstArgType: A0.self,
135     dynamicArgumentTypes: [~A0.self],
136     closure
137   )
138 }
139 
140 /**
141  Synchronous function with two arguments.
142  */
143 public func Function<R, A0: AnyArgument, A1: AnyArgument>(
144   _ name: String,
145   @_implicitSelfCapture _ closure: @escaping (A0, A1) throws -> R
146 ) -> SyncFunctionComponent<(A0, A1), A0, R> {
147   return SyncFunctionComponent(
148     name,
149     firstArgType: A0.self,
150     dynamicArgumentTypes: [~A0.self, ~A1.self],
151     closure
152   )
153 }
154 
155 /**
156  Synchronous function with three arguments.
157  */
158 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>(
159   _ name: String,
160   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) throws -> R
161 ) -> SyncFunctionComponent<(A0, A1, A2), A0, R> {
162   return SyncFunctionComponent(
163     name,
164     firstArgType: A0.self,
165     dynamicArgumentTypes: [
166       ~A0.self,
167       ~A1.self,
168       ~A2.self
169     ],
170     closure
171   )
172 }
173 
174 /**
175  Synchronous function with four arguments.
176  */
177 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>(
178   _ name: String,
179   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) throws -> R
180 ) -> SyncFunctionComponent<(A0, A1, A2, A3), A0, R> {
181   return SyncFunctionComponent(
182     name,
183     firstArgType: A0.self,
184     dynamicArgumentTypes: [
185       ~A0.self,
186       ~A1.self,
187       ~A2.self,
188       ~A3.self
189     ],
190     closure
191   )
192 }
193 
194 /**
195  Synchronous function with five arguments.
196  */
197 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>(
198   _ name: String,
199   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) throws -> R
200 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4), A0, R> {
201   return SyncFunctionComponent(
202     name,
203     firstArgType: A0.self,
204     dynamicArgumentTypes: [
205       ~A0.self,
206       ~A1.self,
207       ~A2.self,
208       ~A3.self,
209       ~A4.self
210     ],
211     closure
212   )
213 }
214 
215 /**
216  Synchronous function with six arguments.
217  */
218 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>(
219   _ name: String,
220   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) throws -> R
221 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5), A0, R> {
222   return SyncFunctionComponent(
223     name,
224     firstArgType: A0.self,
225     dynamicArgumentTypes: [
226       ~A0.self,
227       ~A1.self,
228       ~A2.self,
229       ~A3.self,
230       ~A4.self,
231       ~A5.self
232     ],
233     closure
234   )
235 }
236 
237 /**
238  Synchronous function with seven arguments.
239  */
240 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>(
241   _ name: String,
242   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) throws -> R
243 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6), A0, R> {
244   return SyncFunctionComponent(
245     name,
246     firstArgType: A0.self,
247     dynamicArgumentTypes: [
248       ~A0.self,
249       ~A1.self,
250       ~A2.self,
251       ~A3.self,
252       ~A4.self,
253       ~A5.self,
254       ~A6.self
255     ],
256     closure
257   )
258 }
259 
260 /**
261  Synchronous function with eight arguments.
262  */
263 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument, A7: AnyArgument>(
264   _ name: String,
265   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) throws -> R
266 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> {
267   return SyncFunctionComponent(
268     name,
269     firstArgType: A0.self,
270     dynamicArgumentTypes: [
271       ~A0.self,
272       ~A1.self,
273       ~A2.self,
274       ~A3.self,
275       ~A4.self,
276       ~A5.self,
277       ~A6.self,
278       ~A7.self
279     ],
280     closure
281   )
282 }
283