From 7a1f377980164b36003ed334d12d43a111cbe42f Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 20 Aug 2024 08:47:23 -0700 Subject: [PATCH] Fix the type signature of async `User.functions` The async and Future versions of this were incorrectly defined as taking exactly one argument which had to be an array of BSON values rather than any number of BSON arguments. --- CHANGELOG.md | 2 + Realm/ObjectServerTests/AsyncSyncTests.swift | 2 +- .../SwiftObjectServerTests.swift | 2 +- RealmSwift/Sync.swift | 38 +++++++++++++++---- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed30a75d6d..462007f441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ x.y.z Release notes (yyyy-MM-dd) non-null return value despite always returning `nil` (since v10.29.0). * Eliminate several clang static analyzer warnings which did not report actual bugs. +* The async and Future versions of `User.functions` only worked for functions + which took exactly one argument, which had to be an array ([#8669](https://github.com/realm/realm-swift/issues/8669), since 10.16.0). ### Compatibility * Realm Studio: 15.0.0 or later. diff --git a/Realm/ObjectServerTests/AsyncSyncTests.swift b/Realm/ObjectServerTests/AsyncSyncTests.swift index c9ce6cf552..d358af5b52 100644 --- a/Realm/ObjectServerTests/AsyncSyncTests.swift +++ b/Realm/ObjectServerTests/AsyncSyncTests.swift @@ -350,7 +350,7 @@ class AsyncAwaitSyncTests: SwiftSyncTestCase { func testUserCallFunctionAsyncAwait() async throws { let user = try await self.app.login(credentials: basicCredentials()) - guard case let .int32(sum) = try await user.functions.sum([1, 2, 3, 4, 5]) else { + guard case let .int32(sum) = try await user.functions.sum(.array([1, 2, 3, 4, 5])) else { return XCTFail("Should be int32") } XCTAssertEqual(sum, 15) diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index a4491715d3..711cd66384 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -893,7 +893,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let credentials = Credentials.emailPassword(email: email, password: password) let syncUser = app.login(credentials: credentials).await(self) - let bson = syncUser.functions.sum([1, 2, 3, 4, 5]).await(self) + let bson = syncUser.functions.sum(.array([1, 2, 3, 4, 5])).await(self) guard case let .int32(sum) = bson else { XCTFail("unexpected bson type in sum: \(bson)") return diff --git a/RealmSwift/Sync.swift b/RealmSwift/Sync.swift index 417d9805e0..9818a7b76e 100644 --- a/RealmSwift/Sync.swift +++ b/RealmSwift/Sync.swift @@ -667,19 +667,33 @@ public struct FunctionCallable: Sendable { fileprivate let name: String fileprivate let user: User + @available(*, deprecated, message: "Explicitly specify .array(arg)") + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + public func dynamicallyCall(withArguments args: [[AnyBSON]]) -> Future { + return future { promise in + let objcArgs = args.first!.map(ObjectiveCSupport.convertBson) + self.user.__callFunctionNamed(name, arguments: objcArgs) { (bson: RLMBSON?, error: Error?) in + if let b = bson.map(ObjectiveCSupport.convertBson), let bson = b { + promise(.success(bson)) + } else { + promise(.failure(error ?? Realm.Error.callFailed)) + } + } + } + } + /// The implementation of @dynamicCallable that allows for `Future` callable return. /// - /// let cancellable = user.functions.sum([1, 2, 3, 4, 5]) + /// let cancellable = user.functions.sum(.array([1, 2, 3, 4, 5])) /// .sink(receiveCompletion: { result in /// }, receiveValue: { value in /// // Returned value from function /// }) /// - @preconcurrency @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - public func dynamicallyCall(withArguments args: [[AnyBSON]]) -> Future { + public func dynamicallyCall(withArguments args: [AnyBSON]) -> Future { return future { promise in - let objcArgs = args.first!.map(ObjectiveCSupport.convertBson) + let objcArgs = args.map(ObjectiveCSupport.convertBson) self.user.__callFunctionNamed(name, arguments: objcArgs) { (bson: RLMBSON?, error: Error?) in if let b = bson.map(ObjectiveCSupport.convertBson), let bson = b { promise(.success(bson)) @@ -1134,14 +1148,24 @@ public extension User { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension FunctionCallable { + @available(*, deprecated, message: "Explicitly specify .array(arg)") + public func dynamicallyCall(withArguments args: [[AnyBSON]]) async throws -> AnyBSON { + let objcArgs = args.first!.map(ObjectiveCSupport.convertBson) + let ret = try await user.__callFunctionNamed(name, arguments: objcArgs) + if let bson = ObjectiveCSupport.convertBson(object: ret) { + return bson + } + throw Realm.Error.callFailed + } + /// The implementation of @dynamicMemberLookup that allows for `async await` callable return. /// - /// guard case let .int32(sum) = try await user.functions.sum([1, 2, 3, 4, 5]) else { + /// guard case let .int32(sum) = try await user.functions.sum(.array([1, 2, 3, 4, 5])) else { /// return /// } /// - public func dynamicallyCall(withArguments args: [[AnyBSON]]) async throws -> AnyBSON { - let objcArgs = args.first!.map(ObjectiveCSupport.convertBson) + public func dynamicallyCall(withArguments args: [AnyBSON]) async throws -> AnyBSON { + let objcArgs = args.map(ObjectiveCSupport.convertBson) let ret = try await user.__callFunctionNamed(name, arguments: objcArgs) if let bson = ObjectiveCSupport.convertBson(object: ret) { return bson