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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions lib/ClangImporter/ClangAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,8 @@ bool importer::isObjCId(const clang::Decl *decl) {
}

bool importer::isUnavailableInSwift(
const clang::Decl *decl, const PlatformAvailability &platformAvailability,
const clang::Decl *decl,
const PlatformAvailability *platformAvailability,
bool enableObjCInterop) {
// 'id' is always unavailable in Swift.
if (enableObjCInterop && isObjCId(decl))
Expand All @@ -724,7 +725,10 @@ bool importer::isUnavailableInSwift(
if (attr->getPlatform()->getName() == "swift")
return true;

if (!platformAvailability.isPlatformRelevant(
if (!platformAvailability)
continue;

if (!platformAvailability->isPlatformRelevant(
attr->getPlatform()->getName())) {
continue;
}
Expand All @@ -733,7 +737,7 @@ bool importer::isUnavailableInSwift(
llvm::VersionTuple version = attr->getDeprecated();
if (version.empty())
continue;
if (platformAvailability.treatDeprecatedAsUnavailable(
if (platformAvailability->treatDeprecatedAsUnavailable(
decl, version, /*isAsync=*/false)) {
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/ClangImporter/ClangAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ bool isObjCId(const clang::Decl *decl);

/// Determine whether the given declaration is considered
/// 'unavailable' in Swift.
bool isUnavailableInSwift(const clang::Decl *decl, const PlatformAvailability &,
bool isUnavailableInSwift(const clang::Decl *decl, const PlatformAvailability *,
bool enableObjCInterop);

/// Determine the optionality of the given Clang parameter.
Expand Down
23 changes: 20 additions & 3 deletions lib/ClangImporter/ImportEnumInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,16 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
// underlying type of the enum, because there is no way to conjure up a
// name for the Swift type.
if (!decl->hasNameForLinkage()) {
kind = EnumKind::Constants;
return;
// If this enum comes from a typedef, we can find a name.
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.
!importer::isUnavailableInSwift(
cast<clang::TypedefType>(decl->getIntegerType().getTypePtr())->getDecl(),
nullptr, true)) {
kind = EnumKind::Constants;
return;
}
}

// First, check for attributes that denote the classification.
Expand Down Expand Up @@ -339,7 +347,16 @@ void EnumInfo::determineConstantNamePrefix(const clang::EnumDecl *decl) {

// Don't use importFullName() here, we want to ignore the swift_name
// and swift_private attributes.
StringRef enumNameStr = decl->getName();
StringRef enumNameStr;
// If there's no name, this must be typedef. So use the typedef's name.
if (!decl->hasNameForLinkage()) {
auto typedefDecl = cast<clang::TypedefType>(
decl->getIntegerType().getTypePtr())->getDecl();
enumNameStr = typedefDecl->getName();
} else {
enumNameStr = decl->getName();
}

if (enumNameStr.empty())
enumNameStr = decl->getTypedefNameForAnonDecl()->getName();
assert(!enumNameStr.empty() && "should have been classified as Constants");
Expand Down
17 changes: 16 additions & 1 deletion lib/ClangImporter/ImportName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,7 @@ bool NameImporter::hasErrorMethodNameCollision(
// been marked NS_SWIFT_UNAVAILABLE, because it's actually marked unavailable,
// or because it was deprecated before our API sunset. We can handle
// "conflicts" where one form is unavailable.
return !isUnavailableInSwift(conflict, availability,
return !isUnavailableInSwift(conflict, &availability,
enableObjCInterop());
}

Expand Down Expand Up @@ -1742,6 +1742,21 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
}
}

// If this enum inherits from a typedef we can compute the name from the
// typedef (even if it's an anonymous enum).
if (auto enumDecl = dyn_cast<clang::EnumDecl>(D)) {
// Intentionally don't get the cannonical type here.
if (auto typedefType = dyn_cast<clang::TypedefType>(enumDecl->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.
if (importer::isUnavailableInSwift(typedefType->getDecl(), nullptr, true)) {
result.setDeclName(swiftCtx.getIdentifier(typedefType->getDecl()->getName()));
result.setEffectiveContext(D->getDeclContext());
return result;
}
}
}

// Otherwise, for empty names, there is nothing to do.
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/ClangImporter/ImporterImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
/// 'unavailable' in Swift.
bool isUnavailableInSwift(const clang::Decl *decl) {
return importer::isUnavailableInSwift(
decl, platformAvailability, SwiftContext.LangOpts.EnableObjCInterop);
decl, &platformAvailability, SwiftContext.LangOpts.EnableObjCInterop);
}

/// Add "Unavailable" annotation to the swift declaration.
Expand Down
22 changes: 22 additions & 0 deletions test/Interop/Cxx/enum/Inputs/anonymous-with-swift-name.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#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__((availability(swift, unavailable))) _type _name; enum __attribute__((flag_enum,enum_extensibility(open))) : _name
#define CF_OPTIONS(_type, _name) __attribute__((availability(swift, unavailable))) _type _name; enum : _name

typedef SOME_OPTIONS(unsigned, SOColorMask) {
kSOColorMaskRed = (1 << 1),
kSOColorMaskGreen = (1 << 2),
kSOColorMaskBlue = (1 << 3),
kSOColorMaskAll = ~0U
};


typedef CF_OPTIONS(unsigned, CFColorMask) {
kCFColorMaskRed = (1 << 1),
kCFColorMaskGreen = (1 << 2),
kCFColorMaskBlue = (1 << 3),
kCFColorMaskAll = ~0U
};

#endif // TEST_INTEROP_CXX_ENUM_INPUTS_ANONYMOUS_WITH_SWIFT_NAME_H
5 changes: 5 additions & 0 deletions test/Interop/Cxx/enum/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ module ScopedEnums {
header "scoped-enums.h"
requires cplusplus
}

module AnonymousWithSwiftName {
header "anonymous-with-swift-name.h"
requires cplusplus
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: %target-swift-ide-test -print-module -module-to-print=AnonymousWithSwiftName -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s

// CHECK: @available(*, unavailable, message: "Not available in Swift")
// CHECK: typealias SOColorMask = UInt32

// CHECK: struct SOColorMask : OptionSet, @unchecked Sendable {
// CHECK: init(rawValue: UInt32)
// CHECK: let rawValue: UInt32
// CHECK: typealias RawValue = UInt32
// CHECK: typealias Element = SOColorMask
// CHECK: typealias ArrayLiteralElement = SOColorMask

// CHECK: static var red: SOColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "red")
// CHECK: static var Red: SOColorMask { get }

// CHECK: static var green: SOColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "green")
// CHECK: static var Green: SOColorMask { get }

// CHECK: static var blue: SOColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "blue")
// CHECK: static var Blue: SOColorMask { get }

// CHECK: static var all: SOColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "all")
// CHECK: static var All: SOColorMask { get }
// CHECK: }

// CHECK: @available(*, unavailable, message: "Not available in Swift")
// CHECK: typealias CFColorMask = UInt32

// CHECK: struct CFColorMask : OptionSet {
// CHECK: init(rawValue: UInt32)
// CHECK: let rawValue: UInt32
// CHECK: typealias RawValue = UInt32
// CHECK: typealias Element = CFColorMask
// CHECK: typealias ArrayLiteralElement = CFColorMask

// CHECK: static var red: CFColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "red")
// CHECK: static var Red: CFColorMask { get }

// CHECK: static var green: CFColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "green")
// CHECK: static var Green: CFColorMask { get }

// CHECK: static var blue: CFColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "blue")
// CHECK: static var Blue: CFColorMask { get }

// CHECK: static var all: CFColorMask { get }
// CHECK: @available(swift, obsoleted: 3, renamed: "all")
// CHECK: static var All: CFColorMask { get }
// CHECK: }
34 changes: 34 additions & 0 deletions test/Interop/Cxx/enum/anonymous-with-swift-name.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop)

// REQUIRES: executable_test

import AnonymousWithSwiftName
import StdlibUnittest

var AnonymousEnumsTestSuite = TestSuite("Anonymous Enums With Swift Name")

AnonymousEnumsTestSuite.test("SOME_OPTIONS") {
let red: SOColorMask = .red
let green = SOColorMask.green
let blue = .blue as SOColorMask
let all: SOColorMask = .all

expectEqual(red.rawValue, 2)
expectEqual(green.rawValue, 4)
expectEqual(blue.rawValue, 8)
expectEqual(all.rawValue, ~CUnsignedInt(0))
}

AnonymousEnumsTestSuite.test("CF_OPTIONS") {
let red: SOColorMask = .red
let green = SOColorMask.green
let blue = .blue as SOColorMask
let all: SOColorMask = .all
Comment on lines +23 to +26
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


expectEqual(red.rawValue, 2)
expectEqual(green.rawValue, 4)
expectEqual(blue.rawValue, 8)
expectEqual(all.rawValue, ~CUnsignedInt(0))
}

runAllTests()