-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[cxx-interop] Allow anonymous enums to be imported as non-constants. #42223
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
Conversation
3487ef6
to
3057d0d
Compare
@swift-ci please test. |
@swift-ci please test source compatibility. |
@swift-ci Please Test Source Compatibility |
#ifndef TEST_INTEROP_CXX_ENUM_INPUTS_ANONYMOUS_WITH_SWIFT_NAME_H | ||
#define TEST_INTEROP_CXX_ENUM_INPUTS_ANONYMOUS_WITH_SWIFT_NAME_H | ||
|
||
#define SOME_OPTIONS(_type, _name) __attribute__((swift_name("_TYPEDEF_" #_name))) _type _name; enum __attribute__((swift_name(#_name))) __attribute__((flag_enum,enum_extensibility(open))) : _type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this pattern will work for us, if only because users may want to use swift_name()
to give their option sets custom names:
typedef SOME_OPTIONS(unsigned, SOColorMask) {
kSOColorMaskRed = (1 << 1),
kSOColorMaskGreen = (1 << 2),
kSOColorMaskBlue = (1 << 3),
kSOColorMaskAll = ~0U
} __attribute__((swift_name("SOColorOptions")));
// Oops! Now the name relationship between typedef and enum is totally busted.
However, I think your general approach of modifying the macro to link the typedef and enum together by name is a good one. It just needs to be done by the clang name, not the Swift name. For instance, suppose we extended __attribute__((flag_enum))
to allow an optional (C++) type name; we could then link the two by their clang names, leaving the swift_name
attribute to do its current job:
#define SOME_OPTIONS(_type, _name) _type _name; \
enum __attribute__((flag_enum(_name),enum_extensibility(open))) : _type
(You could instead introduce a new attribute—say, anonymous_typedef(TypeName)
—to represent this link; it wouldn't be much different.)
Another way to do it might be to require the typedef to be used as the underlying type of the anonymous enum, like so:
#define SOME_OPTIONS(_type, _name) _type _name; \
enum __attribute__((flag_enum,enum_extensibility(open))) : _name
// Note that we're using `: _name` instead of `: _type`.
I believe these are equivalent to C++ (right?), but we can give them different meanings to Swift.
(There is a danger that someone might accidentally turn, say, NSInteger
into an options enum by using the old macro with the new compiler. Perhaps you'd need to also mark the typedef with flag_enum
—this might require an extension to clang so that it would allow the attribute to be applied to typedefs.)
In any case, we're going to want attributes applied to the enum to also be respected when we import the typedef. Actually, what we probably want is for ImportDecl.cpp:canSkipOverTypedef()
to tell us to import the enum in the typedef's place, but also fall back to the typedef's name if there isn't a swift_name
attribute (which I assume would require changes to ImportName.cpp).
3057d0d
to
588fa53
Compare
@swift-ci please test. |
588fa53
to
8286f10
Compare
lib/ClangImporter/ImportEnumInfo.cpp
Outdated
if (!isa<clang::TypedefType>(decl->getIntegerType().getTypePtr()) || | ||
// If the typedef is available in Swift, the user will get ambiguity. | ||
// It also means they may not have intended this API to be imported like this. | ||
!unavailableOnSwift(cast<clang::TypedefType>(decl->getIntegerType().getTypePtr())->getDecl())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this is an acceptable heuristic. We may have to add another attr to make sure this is actually what the user wants. CC @beccadax
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked this over and we think this will work.
The deal here is that:
- Swift will import both the typedef and the enum.
- The typedef will be unavailable from Swift.
- Name lookup sets aside unavailable-from-Swift declarations and only uses them in diagnostics when there are no available decls, so the typedef import should be harmless.
So from the user's perspective, marking the typedef as unavailable-in-Swift means that the typedef should be set aside so the enum can be imported instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@beccadax I am a little confused. If the typedef is ignored how does swift know for instance that NSEnumerationOptions=Int ?
I applied this patch and modified CFAvailability.h on my local machine and updated these module dumps:
https://github.dev/plotfi/cxx-interop-diff/pull/1
Taking a look at Foundation.NSSet it seems without the typedef we get func objects(withOptions opts: Int,
instead of func objects(withOptions opts: NSEnumerationOptions,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the typedef is ignored how does swift know for instance that NSEnumerationOptions=Int ?
We set the enum's name as the typdef's name and the base type as the typedef's type. We are able to derive this information from the enum's base type (which is the typedef, now).
Taking a look at Foundation.NSSet it seems without the typedef we get func objects(withOptions opts: Int, instead of func objects(withOptions opts: NSEnumerationOptions,
Yeah, I think that's the behavior we want. opts
should be an enum (where the raw value is an Int
), not a plain Int
. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with the parameter being a raw Int, but could there be anyway that we insert a typealias NSEnumerationOptions = Int
and use that as the parameter type? Is NSEnumerationOptions not a raw Int here?
@swift-ci please test. |
8286f10
to
150061a
Compare
@swift-ci please test. |
@swift-ci Please Test Source Compatibility |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some changes to request, but the basic approach looks workable to me.
lib/ClangImporter/ImportEnumInfo.cpp
Outdated
// If there's no decl name, just use the prefix we already created. | ||
if (!decl->hasNameForLinkage()) { | ||
constantNamePrefix = commonPrefix; | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this—the type name contributes to the prefix computation for a reason. We should probably continue to the code below with enumNameStr
containing the underlying typedef's name instead of the enum's lack-of-name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Code structuring note: there might be a case for a "get C name of type" helper which encompasses checking the decl's name, checking its linkage name, and then this special check for an underlying typedef. There might even be such a helper already that you could add to—I'm not sure.)
lib/ClangImporter/ImportEnumInfo.cpp
Outdated
if (!isa<clang::TypedefType>(decl->getIntegerType().getTypePtr()) || | ||
// If the typedef is available in Swift, the user will get ambiguity. | ||
// It also means they may not have intended this API to be imported like this. | ||
!unavailableOnSwift(cast<clang::TypedefType>(decl->getIntegerType().getTypePtr())->getDecl())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked this over and we think this will work.
The deal here is that:
- Swift will import both the typedef and the enum.
- The typedef will be unavailable from Swift.
- Name lookup sets aside unavailable-from-Swift declarations and only uses them in diagnostics when there are no available decls, so the typedef import should be harmless.
So from the user's perspective, marking the typedef as unavailable-in-Swift means that the typedef should be set aside so the enum can be imported instead.
< #define CF_OPTIONS(_type, _name) _type __attribute__((availability(swift, unavailable))) _name; enum __CF_OPTIONS_ATTRIBUTES : _name --- > #define CF_OPTIONS(_type, _name) _type _name; enum __CF_OPTIONS_ATTRIBUTES : _type along with Zoe's swiftlang/swift#42223
If an anonymous enum inherits from a typedef, it will have the typedef's name.
150061a
to
965c7ca
Compare
Thank you for the review, Becca! Updated based on your comments :) |
@swift-ci please test. |
@swift-ci Please Test Source Compatibility |
There were actually not logic changes between this patch and the last one, and the source compatibility passed last time (technically it failed because of a UPass, but only with one project, and that project is known to pass on main). |
let red: SOColorMask = .red | ||
let green = SOColorMask.green | ||
let blue = .blue as SOColorMask | ||
let all: SOColorMask = .all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need to be CF
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in: 1d74713
For the record: the source compatibility test succeeded. |
No description provided.