Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @_objcImplementation #60630

Merged
merged 14 commits into from
Oct 21, 2022
Merged

Add @_objcImplementation #60630

merged 14 commits into from
Oct 21, 2022

Conversation

beccadax
Copy link
Contributor

@beccadax beccadax commented Aug 18, 2022

This PR adds a mechanism to implement "pure" Objective-C classes in Swift. Unlike the existing @objc interop mechanism, these classes use traditional handwritten ObjC header files and can be subclassed, swizzled, and otherwise manipulated from ObjC code like any other ObjC class.

To do this, you arrange to import the header file via your bridging or umbrella header, as though it were implemented in Objective-C, and then you apply the @_objcImplementation attribute to an extension and implement the methods there. Each @_objcImplementation extension corresponds to one @interface in the header; the attribute can specify a category name in parentheses to implement a category, or it can leave the category name out to implement the main body of the class. The extension that implements the main body of the class can declare stored properties.

This initial PR includes fairly minimal support sufficient to allow you to implement initializers, methods, stored properties, and accessors/observers for ObjC classes without leaking memory. Sema-level diagnostics are very incomplete and stored resilient properties have not yet been tested. The vast majority of Swift-specific metadata has been eliminated, but there may still be a couple of things that shouldn't be there.

Parser reviewers: There is a matching pull request, swiftlang/swift-syntax#798, which includes a test for the new attribute.

Fixes rdar://70730077.

@beccadax beccadax force-pushed the at-implementation branch 3 times, most recently from 86a8951 to eaf12b4 Compare August 25, 2022 17:44
docs/ReferenceGuides/UnderscoredAttributes.md Outdated Show resolved Hide resolved
docs/ReferenceGuides/UnderscoredAttributes.md Outdated Show resolved Hide resolved
include/swift/IRGen/Linking.h Show resolved Hide resolved
include/swift/AST/Decl.h Outdated Show resolved Hide resolved
include/swift/AST/Decl.h Outdated Show resolved Hide resolved
lib/IRGen/GenClass.cpp Outdated Show resolved Hide resolved
lib/IRGen/GenDecl.cpp Outdated Show resolved Hide resolved
lib/IRGen/GenDecl.cpp Show resolved Hide resolved
@beccadax beccadax force-pushed the at-implementation branch 2 times, most recently from aec03c1 to 451f2e9 Compare September 15, 2022 00:01
beccadax added a commit to beccadax/swift-syntax that referenced this pull request Sep 15, 2022
Part of swiftlang/swift#60630, but safe to merge before the compiler-side implementation.
@beccadax beccadax force-pushed the at-implementation branch 3 times, most recently from 41770b0 to 76beb1c Compare September 30, 2022 00:39
beccadax added a commit to beccadax/swift-syntax that referenced this pull request Sep 30, 2022
Part of swiftlang/swift#60630, but safe to merge before the compiler-side implementation.
@beccadax beccadax force-pushed the at-implementation branch 2 times, most recently from c5953fc to cd72652 Compare October 5, 2022 03:23
@beccadax
Copy link
Contributor Author

beccadax commented Oct 5, 2022

@swift-ci please test

@beccadax beccadax force-pushed the at-implementation branch 2 times, most recently from 5557ebd to 6c28360 Compare October 5, 2022 17:39
beccadax added a commit to beccadax/swift-syntax that referenced this pull request Oct 5, 2022
Part of swiftlang/swift#60630, but safe to merge before the compiler-side implementation.
@beccadax
Copy link
Contributor Author

beccadax commented Oct 5, 2022

With swiftlang/swift-syntax#798

@swift-ci please test

@beccadax
Copy link
Contributor Author

beccadax commented Oct 5, 2022

With swiftlang/swift-syntax#798

@swift-ci please test

@beccadax beccadax changed the title @_objcImplementation Add @_objcImplementation Oct 5, 2022
@beccadax beccadax marked this pull request as ready for review October 5, 2022 20:37
@beccadax
Copy link
Contributor Author

beccadax commented Oct 8, 2022

With swiftlang/swift-syntax#798

@swift-ci please test

@beccadax
Copy link
Contributor Author

@swift-ci build toolchain

@beccadax
Copy link
Contributor Author

With swiftlang/swift-syntax#798

@swift-ci please build toolchain macOS platform

Does not validate members yet; nor does it emit different metadata.
They either must be @objc dynamic (the dynamic is implicit) or final.
Stored properties are only allowed in the extension implementing the class's main interface, not its categories. This also means banning `@objc final`, which is unenforceable anyway when ObjC subclasses are allowed, and therefore allowing `@objc let` and `@objc static` properties to be overridden if they're declared in objcImplementations.
This commit begins to generate correct metadata for @_objcImplementation extensions:

• Swift-specific metadata and symbols are not generated.
• For main-class @_objcImpls, we visit the class to emit metadata, but visit the extension’s members.
• Includes both IR tests and executable tests, including coverage of same-module @objc subclasses, different-module @objc subclasses, and clang subclasses.

The test cases do not yet cover stored properties.
Only works for trivial types right now because features related to initialization and deinitialization are seriously busted.
Accessing a stored property from a normal method should send a selector, but accessing it from an initializer or accessor should use the field offset.
This is failing in the iOS simulator, but *only* in Swift CI, not locally (even with the same tools). Disable the test ouside of macOS while we investigate the failure.
beccadax added a commit to beccadax/swift-syntax that referenced this pull request Oct 19, 2022
Part of swiftlang/swift#60630, but safe to merge before the compiler-side implementation.
@beccadax
Copy link
Contributor Author

With swiftlang/swift-syntax#798

@swift-ci please test

@beccadax
Copy link
Contributor Author

With swiftlang/swift-syntax#798

@swift-ci please clean test

@beccadax
Copy link
Contributor Author

With swiftlang/swift-syntax#798

@swift-ci please clean test

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great. I appreciate the testing discipline for this tricky feature.

@tbkka
Copy link
Contributor

tbkka commented Oct 22, 2022

I'm seeing a failure on a local build of main on Ubuntu 22.10:

.../swift/include/swift/AST/Attr.h:2289:21: error: use of undeclared identifier 'DAK_ObjCImplementation'
    : DeclAttribute(DAK_ObjCImplementation, AtLoc, Range, Implicit),
.../swift/include/swift/AST/Attr.h:2303:29: error: use of undeclared identifier 'DAK_ObjCImplementation'
    return DA->getKind() == DAK_ObjCImplementation;

Any ideas? Is DAK_ObjCImplementation supposed to be declared on Linux?

@tbkka
Copy link
Contributor

tbkka commented Oct 22, 2022

Ignore that. I just updated all my sources and now it seems to build fine, so whatever this issue was you must have already fixed it. Thanks!!

@ahoppen
Copy link
Member

ahoppen commented Oct 22, 2022

This PR was paired with swiftlang/swift-syntax#798 and you needed to pull that PR’s commits to fix your build.


Declares an extension that defines an implementation for the Objective-C
category `CategoryName` on the class in question, or for the main `@interface`
if the argument list is omitted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is just internal documentation, but I think I would phrase this as:

## `@_objcImplementation` and `@_objcImplementation(CategoryName)`

Must be written on an `extension` of a non-generic imported Objective-C class.
When written without an argument list, emits an Objective-C class implementation
for the class.  When written with a category name argument, emits an Objective-C
category implementation for that category of the class, which must be declared
in the imported headers.  In both cases, the emitted implementation will look
exactly like what an Objective-C compiler would have emitted for a corresponding
`@implementation`.

Some part of me really hates that the first use case is done with an extension.
I don't know that I have a better option, though. Having two ClassDecls, or
gutting one for the other, both seem worse.


bool isCategoryNameInvalid() const {
return Bits.ObjCImplementationAttr.isCategoryNameInvalid;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that a category name was given but doesn't match anything from the class?

///
/// If \c this (an otherwise nonsensical value), the value has not yet been
/// computed.
Decl *CachedObjCImplementationDecl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to pay for this on literally every declaration we make in the compiler. I doubt this is even hot. Can you use out-of-line storage for it?

@@ -1553,6 +1556,16 @@ class LinkEntity {
bool isAlwaysSharedLinkage() const;
#undef LINKENTITY_GET_FIELD
#undef LINKENTITY_SET_FIELD

private:
static bool isObjCImplementation(NominalTypeDecl *NTD) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasObjCImplementation, maybe? But really I think this assertion can be broader — we should never have a type metadata or descriptor symbol for any imported class. Unless we emit a lazy candidate with the normal symbol name, but I don't think we do? And if we do, it would trigger for your case, too.

@@ -84,7 +84,14 @@ AccessLevelRequest::evaluate(Evaluator &evaluator, ValueDecl *D) const {
if (D->hasInterfaceType() && D->isInvalid()) {
return AccessLevel::Private;
} else {
auto container = cast<NominalTypeDecl>(D->getDeclContext());
auto container = dyn_cast<NominalTypeDecl>(DC);
if (D->getKind() == DeclKind::Destructor && !container) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isa<DestructorDecl>(D)?

/// \li It's \c \@objc
/// \li Its access level is not \c private or \c fileprivate
static bool
isObjCMemberImplementation(const ValueDecl *VD,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this name, or some of the similar ones. "Implementation" is not a word that ambiguously refers to an ObjC @implementation declaration, and there are a lot of places where ObjC means e.g. the @objc attribute. Is there a more distinctive term we can use within the compiler for these things?

@@ -35,7 +35,8 @@ FormalLinkage swift::getDeclLinkage(const ValueDecl *D) {

// Clang declarations are public and can't be assured of having a
// unique defining location.
if (isa<ClangModuleUnit>(fileContext))
if (isa<ClangModuleUnit>(fileContext) &&
!D->getObjCImplementationDecl())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you want to change this. Do you remember why you found it necessary?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants