swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.61k stars 10.37k forks source link

[SR-14381] @objc & @objcMembers attributes break compilation on Windows #56739

Open swift-ci opened 3 years ago

swift-ci commented 3 years ago
Previous ID SR-14381
Radar None
Original Reporter Eugene Gubin (JIRA User)
Type Bug

Attachment: Download

Environment Windows 10, swift-DEVELOPMENT-SNAPSHOT-2021-03-02-a-windows10
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler | |Labels | Bug, Windows | |Assignee | None | |Priority | Medium | md5: 3bc38c5bc812b76bec20dd2017e83d57

is blocked by:

Issue Description:

Both @objc & @objcMembers attributes break compilation on Windows

Steps to reproduce: download the attachment, unpack and build.

Result:

ObjCAttributeBug.swift:3:2: error: Objective-C interoperability is disabled
@objc @objcMembers class MyClass: NSObject {
~^~~~~
typesanitizer commented 3 years ago

You can pass the compiler flags -Xfrontend -enable-objc-interop to get this to work. @compnerd points out that using ObjC interop adds some overhead, which is why it is off by default on non-Darwin platforms.

compnerd commented 3 years ago

This is expected, and dare I say, desired behavior. The default on non-Apple targets is that the ObjC interop is disabled. You would need to provide your own build of an ABI compatible ObjC runtime if you wish to use the ObjC interop. You can enable it with the -enable-objc-interop flag to the frontend. There are runtime overheads for the interop as well, due to the bridging of types. Note that you cannot combine build with different flags, so you would also need to do a custom runtime build as the object layout is impacted.

swift-ci commented 3 years ago

Comment by Eugene Gubin (JIRA)

I was told it's a bug 🙂 Details: https://forums.swift.org/t/objc-attribute-windows/46787

compnerd commented 3 years ago

I don't consider this a bug, it is intentional. Objective-C interop is non-trivial, and a massive undertaking.

swift-ci commented 3 years ago

Comment by Eugene Gubin (JIRA)

@compnerd Sorry, when I said `bug` I mean what `@objc` should be just ignored on Windows since Objective-C runtime is not supported there. One can not use conditional defines to turn these annotations off. It breaks declaration.

```

#if os(Darwin)

@objc

#end

class MyClass: NSObject { }

```

compnerd commented 3 years ago

Eugene Gubin (JIRA User) - I think that is the confusion. The runtime is not unsupported, it is unavailable by default. If you have a build of a compatible ObjC runtime, you can enable the ObjC interop via the -enable-objc-interop flag. The original bring up work used a fork of the ObjC runtime that I cannot provide to actually test the ObjC interop 🙂

swift-ci commented 3 years ago

Comment by Eugene Gubin (JIRA)

@compnerd, thanks for clarification! But what to do with these @objc annotations in cross-platform code? As shown in the example above it could force you to repeat an entire declaration. I think it would be good to have a way to ignore these annotations on platforms there ObjC runtime is not available.

compnerd commented 3 years ago

Changes like that require an evolution proposal, along with an implementation. For @objc sure you could control that via -objc-interop but not all attributes have a corresponding flag. I would suggest that the way to handle this is to extend the support for `#if` to guard the attributes.

smallscript commented 1 year ago

It is a PITA to when trying to cross check same code on Windows and OSX. :-)

HeMet commented 1 year ago

SE-0367 should fix the issue.

oliviermartin commented 7 months ago

I have the issue I tried:

#if hasAttribute(objc)
@objc
#endif

... but as I would expect, I have still got the same error.

I had to use:

#if canImport(ObjectiveC)
@objc
#endif

which is quite verbose in the code when you go from this easily readable code:

import Foundation

@objc public class Hello: NSObject {
    @objc public func say() {
        print("Hello from Swift!")
    }
}

to this one:

import Foundation

#if canImport(ObjectiveC)
@objc
#endif
public class Hello: NSObject {
    #if canImport(ObjectiveC)
    @objc
    #endif
    public func say() {
        print("Hello from Swift!")
    }
}

An alternative would be to use @objcMembers - but there is a cost to it https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/#objcMembers. Another alternative would be to be able to define our own custom attribute a bit like Python decorator: https://realpython.com/primer-on-python-decorators/ - but its another topic.

tbkka commented 7 months ago

I can see where people would want to be able to write Swift frameworks in such a way that on ObjC-supporting platforms, the types would be available from ObjC. And yes, having @objc be ignored on non-ObjC-supporting platforms would make this easier.

As @compnerd pointed out above, however, this would be a change to the existing language behavior. As such, we cannot make such a change without first discussing it on the Swift forums and having a Swift Evolution proposal reviewed by the Swift Language Steering Group. For a change like this, the proposal would not need to be very involved, but we cannot make such a change without going through this process, which allows people who may have other use cases than yours to debate what the full impact would be.

If you wish to pursue doing this, the process is open to anyone who wants to participate. I'd be happy to give you pointers on how to proceed.

compnerd commented 7 months ago

Just to mention, #if canImport(ObjectiveC) would be incorrect as if the module is available, it will always return true. You should use #if _runtime(_ObjC) instead to ensure that ObjC interop is enabled rather than whether the ObjectiveC module is available.