Skip to content

[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

Merged
merged 1 commit into from
Apr 18, 2022

Conversation

zoecarver
Copy link
Contributor

No description provided.

@zoecarver zoecarver added the c++ interop Feature: Interoperability with C++ label Apr 6, 2022
@zoecarver zoecarver requested review from hyp and beccadax April 6, 2022 20:03
@zoecarver
Copy link
Contributor Author

@swift-ci please test.

@zoecarver
Copy link
Contributor Author

@swift-ci please test source compatibility.

@zoecarver
Copy link
Contributor Author

@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
Copy link
Contributor

@beccadax beccadax Apr 6, 2022

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).

@zoecarver
Copy link
Contributor Author

@swift-ci please test.

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())) {
Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor

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,

Copy link
Contributor Author

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?

Copy link
Contributor

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?

@zoecarver
Copy link
Contributor Author

@swift-ci please test.

@zoecarver
Copy link
Contributor Author

@swift-ci please test.

@zoecarver
Copy link
Contributor Author

@swift-ci Please Test Source Compatibility

Copy link
Contributor

@beccadax beccadax left a 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.

// If there's no decl name, just use the prefix we already created.
if (!decl->hasNameForLinkage()) {
constantNamePrefix = commonPrefix;
return;
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 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.

Copy link
Contributor

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.)

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())) {
Copy link
Contributor

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.

plotfi added a commit to plotfi/cxx-interop-diff that referenced this pull request Apr 15, 2022
< #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.
@zoecarver
Copy link
Contributor Author

Thank you for the review, Becca! Updated based on your comments :)

@zoecarver
Copy link
Contributor Author

@swift-ci please test.

@zoecarver
Copy link
Contributor Author

@swift-ci Please Test Source Compatibility

@zoecarver
Copy link
Contributor Author

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).

Comment on lines +23 to +26
let red: SOColorMask = .red
let green = SOColorMask.green
let blue = .blue as SOColorMask
let all: SOColorMask = .all
Copy link
Contributor Author

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in: 1d74713

@zoecarver
Copy link
Contributor Author

For the record: the source compatibility test succeeded.

@zoecarver zoecarver merged commit b8684af into swiftlang:main Apr 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants