Skip to content

Commit 9fbf8bd

Browse files
committed
[cxx-interop] Import custom NS_OPTIONS correctly
This changes the heuristic that we use to detect `CF_OPTIONS`/`NS_OPTIONS` instantiations in C++ language mode. Since libraries sometimes declare custom option types that break the assumptions about the names of such types, this lifts the requirements on the type name. rdar://113524090 rdar://113279214
1 parent f02ca3b commit 9fbf8bd

File tree

4 files changed

+53
-33
lines changed

4 files changed

+53
-33
lines changed

lib/ClangImporter/ImportType.cpp

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2647,43 +2647,46 @@ ArgumentAttrs ClangImporter::Implementation::inferDefaultArgument(
26472647
} else if (const clang::TypedefType *typedefType =
26482648
type->getAs<clang::TypedefType>()) {
26492649
// Get the AvailabilityAttr that would be set from CF/NS_OPTIONS
2650-
if (importer::isUnavailableInSwift(typedefType->getDecl(), nullptr, true)) {
2650+
clang::TypedefNameDecl *typedefDecl = typedefType->getDecl();
2651+
if (importer::isUnavailableInSwift(typedefDecl, nullptr, true)) {
26512652
// If we've taken this branch it means we have an enum type, and it is
26522653
// likely an integer or NSInteger that is being used by NS/CF_OPTIONS to
26532654
// behave like a C enum in the presence of C++.
2654-
auto enumName = typedefType->getDecl()->getName();
2655-
ArgumentAttrs argumentAttrs(DefaultArgumentKind::None, true, enumName);
2656-
auto camelCaseWords = camel_case::getWords(enumName);
2657-
for (auto it = camelCaseWords.rbegin(); it != camelCaseWords.rend();
2658-
++it) {
2659-
auto word = *it;
2660-
auto next = std::next(it);
2661-
if (camel_case::sameWordIgnoreFirstCase(word, "options")) {
2662-
argumentAttrs.argumentKind = DefaultArgumentKind::EmptyArray;
2663-
return argumentAttrs;
2655+
auto enumName = typedefDecl->getName();
2656+
// Find the next decl in the same context. If this typedef is a part of an
2657+
// NS/CF_OPTIONS declaration, the next decl will be an enum.
2658+
auto declsInContext = typedefDecl->getDeclContext()->decls();
2659+
auto declIter = llvm::find(declsInContext, typedefDecl);
2660+
if (declIter != declsInContext.end())
2661+
declIter++;
2662+
if (declIter != declsInContext.end()) {
2663+
if (auto enumDecl = dyn_cast<clang::EnumDecl>(*declIter)) {
2664+
// If this is a NS/CF_OPTIONS declaration, the enum will have no name,
2665+
// and all enum cases will likely be prefixed with the name of the
2666+
// enum.
2667+
bool looksLikeCFOptions =
2668+
!enumDecl->getIdentifier() &&
2669+
enumDecl->hasAttr<clang::FlagEnumAttr>() &&
2670+
enumDecl->hasAttr<clang::EnumExtensibilityAttr>();
2671+
if (looksLikeCFOptions) {
2672+
for (auto caseDecl : enumDecl->enumerators()) {
2673+
if (!caseDecl->getName().starts_with(enumName)) {
2674+
looksLikeCFOptions = false;
2675+
break;
2676+
}
2677+
}
2678+
}
2679+
if (looksLikeCFOptions) {
2680+
ArgumentAttrs argumentAttrs(DefaultArgumentKind::None, true,
2681+
enumName);
2682+
for (auto word : llvm::reverse(camel_case::getWords(enumName))) {
2683+
if (camel_case::sameWordIgnoreFirstCase(word, "options")) {
2684+
argumentAttrs.argumentKind = DefaultArgumentKind::EmptyArray;
2685+
}
2686+
}
2687+
return argumentAttrs;
2688+
}
26642689
}
2665-
if (camel_case::sameWordIgnoreFirstCase(word, "units"))
2666-
return argumentAttrs;
2667-
if (camel_case::sameWordIgnoreFirstCase(word, "domain"))
2668-
return argumentAttrs;
2669-
if (camel_case::sameWordIgnoreFirstCase(word, "action"))
2670-
return argumentAttrs;
2671-
if (camel_case::sameWordIgnoreFirstCase(word, "event"))
2672-
return argumentAttrs;
2673-
if (camel_case::sameWordIgnoreFirstCase(word, "events") &&
2674-
next != camelCaseWords.rend() &&
2675-
camel_case::sameWordIgnoreFirstCase(*next, "control"))
2676-
return argumentAttrs;
2677-
if (camel_case::sameWordIgnoreFirstCase(word, "state"))
2678-
return argumentAttrs;
2679-
if (camel_case::sameWordIgnoreFirstCase(word, "unit"))
2680-
return argumentAttrs;
2681-
if (camel_case::sameWordIgnoreFirstCase(word, "position") &&
2682-
next != camelCaseWords.rend() &&
2683-
camel_case::sameWordIgnoreFirstCase(*next, "scroll"))
2684-
return argumentAttrs;
2685-
if (camel_case::sameWordIgnoreFirstCase(word, "edge"))
2686-
return argumentAttrs;
26872690
}
26882691
}
26892692
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include "CFOptions.h"
2+
3+
typedef CF_OPTIONS(unsigned, MyControlFlags) {
4+
MyControlFlagsNone = 0,
5+
MyControlFlagsFirst
6+
};

test/Interop/Cxx/objc-correctness/Inputs/module.modulemap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ module CxxClassWithNSStringInit {
99
requires cplusplus
1010
}
1111

12+
module CustomNSOptions {
13+
header "customNSOptions.h"
14+
}
15+
1216
module NSOptionsMangling {
1317
header "NSOptionsMangling.h"
1418
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -enable-objc-interop -enable-experimental-cxx-interop
2+
// REQUIRES: objc_interop
3+
4+
import CustomNSOptions
5+
6+
let flags1: MyControlFlags = []
7+
let flags2: MyControlFlags = [.first]

0 commit comments

Comments
 (0)