Swift 6.3: typed 'throws' instance method cannot be represented in Objective-C

This appears to be a new error in Swift 6.3 (Xcode 26.4 beta) for any Swift methods marked @objc using typed throws. Is this an intentional restriction? Why can't the type simply be erased as if it's using throws?

1 Like

The obvious solution, an untyped overload, doesn't seem to work.

/// - Parameters:
///   - object: A Data value to be inserted into the keychain.
///   - key: A key that can be used to retrieve the `object` from the keychain.
/// - Throws: An error of type `KeychainError`.
/// - Important: Inserted data should be no larger than 4kb.
public func setObject(_ object: Data, forKey key: String) throws(KeychainError) {
    lock.lock()
    defer {
        lock.unlock()
    }
    return try SecureEnclave.setObject(object, forKey: key, options: baseKeychainQuery)
}

@objc
public func setObject(_ object: Data, forKey key: String) throws {
    try setObject(object, forKey: key)
}

This produces:

Valet/Sources/Valet/SecureEnclaveValet.swift:133:17: error: invalid redeclaration of 'setObject(_:forKey:)'
    public func setObject(_ object: Data, forKey key: String) throws {
                ^
Valet/Sources/Valet/SecureEnclaveValet.swift:124:17: note: 'setObject(_:forKey:)' previously declared here
    public func setObject(_ object: Data, forKey key: String) throws(KeychainError) {

So what's the intended solution here?

1 Like

Without answering your original question, a workaround is to name the second method something different and then explicitly specify the ObjC selector.

2 Likes

Ah yes, true, I'll give that a shot.

It seems that this was specifically add in [Sema] @objc functions shall not have typed throw by DataCorrupted · Pull Request #81054 · swiftlang/swift · GitHub which was started almost a year ago, due to the original bug noted here [SILGenFunction] emitBridgeErrorForForeignError hits assertion error when an `@objc` function `throws(ObjCError)` · Issue #80974 · swiftlang/swift · GitHub. However, I don't see any discussion around why typed throws can't be supported with @objc. throws(SomeError) -> throws(any Error) -> throws are all automatic transforms, why couldn't that be done before translating to Obj-C?

1 Like

From the original bug reports, it seems the main issue was that @objc async throws(SomeType) methods were crashing the compiler, so all @objc typed throws where banned, even though they were working fine. Is there any chance we can walk back the restriction on synchronous methods?

Fundamentally, the issue seems to be what Doug said here:

I think we need to ban this in the type checker, because the type has to be NSError* on the Objective-C side, and there's no good way (short of some odd as! casting) to turn that NSError* into a properly-typed error.

Though I have no idea why Swift code bridged into Obj-C would ever need to turn an NSError back into a Swift typed error.

You wouldn’t be able to put it in an @objc protocol, for example, because an ObjC implementer wouldn’t be able to see the correct requirement. But in a class it would be okay, because ObjC can’t subclass Swift classes. Maybe that’s just too subtle though.

2 Likes

So perhaps that's the issue? These are @objc(CustomName) classes that inherit from NSObject. I can't recall whether those can actually be subclassed in Obj-C.

@objc @implementation classes can be subclassed in Objective-C, so they'd have to be banned there as well for the same reason as @objc protocol.

2 Likes

Disregarding any potential compiler architectural difficulties, is there any fundamental reason this shouldn't be allowed?

// Source code
@objc class MyError: NSError { ... }

@objc protocol Foo {
  func frobnicate() throws(MyError)
}
// What Objective-C sees
@protocol Foo

-(BOOL)frobnicateWithError:(MyError**)error;

@end
1 Like