Skip to content

🍒[cxx-interop] Import custom NS_OPTIONS correctly #68046

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 3 commits into from
Aug 23, 2023
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
62 changes: 27 additions & 35 deletions lib/ClangImporter/ImportType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2609,6 +2609,9 @@ ArgumentAttrs ClangImporter::Implementation::inferDefaultArgument(
if (isFirstParameter && camel_case::getFirstWord(baseNameStr) == "set")
return DefaultArgumentKind::None;

if (auto elaboratedTy = type->getAs<clang::ElaboratedType>())
type = elaboratedTy->desugar();

// Some nullable parameters default to 'nil'.
if (clangOptionality == OTK_Optional) {
// Nullable trailing closure parameters default to 'nil'.
Expand Down Expand Up @@ -2639,42 +2642,31 @@ ArgumentAttrs ClangImporter::Implementation::inferDefaultArgument(
}
} else if (const clang::TypedefType *typedefType =
type->getAs<clang::TypedefType>()) {
// Get the AvailabilityAttr that would be set from CF/NS_OPTIONS
if (importer::isUnavailableInSwift(typedefType->getDecl(), nullptr, true)) {
// If we've taken this branch it means we have an enum type, and it is
// likely an integer or NSInteger that is being used by NS/CF_OPTIONS to
// behave like a C enum in the presence of C++.
auto enumName = typedefType->getDecl()->getName();
ArgumentAttrs argumentAttrs(DefaultArgumentKind::None, true, enumName);
auto camelCaseWords = camel_case::getWords(enumName);
for (auto it = camelCaseWords.rbegin(); it != camelCaseWords.rend();
++it) {
auto word = *it;
auto next = std::next(it);
if (camel_case::sameWordIgnoreFirstCase(word, "options")) {
argumentAttrs.argumentKind = DefaultArgumentKind::EmptyArray;
return argumentAttrs;
clang::TypedefNameDecl *typedefDecl = typedefType->getDecl();
// Find the next decl in the same context. If this typedef is a part of an
// NS/CF_OPTIONS declaration, the next decl will be an enum.
auto declsInContext = typedefDecl->getDeclContext()->decls();
auto declIter = llvm::find(declsInContext, typedefDecl);
if (declIter != declsInContext.end())
declIter++;
if (declIter != declsInContext.end()) {
if (auto enumDecl = dyn_cast<clang::EnumDecl>(*declIter)) {
if (auto cfOptionsTy =
nameImporter.getContext()
.getClangModuleLoader()
->getTypeDefForCXXCFOptionsDefinition(enumDecl)) {
if (cfOptionsTy->getDecl() == typedefDecl) {
auto enumName = typedefDecl->getName();
ArgumentAttrs argumentAttrs(DefaultArgumentKind::None, true,
enumName);
for (auto word : llvm::reverse(camel_case::getWords(enumName))) {
if (camel_case::sameWordIgnoreFirstCase(word, "options")) {
argumentAttrs.argumentKind = DefaultArgumentKind::EmptyArray;
}
}
return argumentAttrs;
}
}
if (camel_case::sameWordIgnoreFirstCase(word, "units"))
return argumentAttrs;
if (camel_case::sameWordIgnoreFirstCase(word, "domain"))
return argumentAttrs;
if (camel_case::sameWordIgnoreFirstCase(word, "action"))
return argumentAttrs;
if (camel_case::sameWordIgnoreFirstCase(word, "events") &&
next != camelCaseWords.rend() &&
camel_case::sameWordIgnoreFirstCase(*next, "control"))
return argumentAttrs;
if (camel_case::sameWordIgnoreFirstCase(word, "state"))
return argumentAttrs;
if (camel_case::sameWordIgnoreFirstCase(word, "unit"))
return argumentAttrs;
if (camel_case::sameWordIgnoreFirstCase(word, "position") &&
next != camelCaseWords.rend() &&
camel_case::sameWordIgnoreFirstCase(*next, "scroll"))
return argumentAttrs;
if (camel_case::sameWordIgnoreFirstCase(word, "edge"))
return argumentAttrs;
}
}
}
Expand Down
60 changes: 39 additions & 21 deletions test/Interop/Cxx/enum/Inputs/c-enums-withOptions-omit.h
Original file line number Diff line number Diff line change
@@ -1,39 +1,57 @@
// Enum usage that is bitwise-able and assignable in C++, aka how CF_OPTIONS
// does things.
typedef int __attribute__((availability(swift, unavailable))) NSEnumerationOptions;
enum : NSEnumerationOptions { NSEnumerationConcurrent, NSEnumerationReverse };
typedef unsigned NSUInteger;

#define __CF_OPTIONS_ATTRIBUTES __attribute__((flag_enum,enum_extensibility(open)))
#if (__cplusplus)
#define CF_OPTIONS(_type, _name) __attribute__((availability(swift,unavailable))) _type _name; enum __CF_OPTIONS_ATTRIBUTES : _name
#else
#define CF_OPTIONS(_type, _name) enum __CF_OPTIONS_ATTRIBUTES _name : _type _name; enum _name : _type
#endif

typedef CF_OPTIONS(NSUInteger, NSEnumerationOptions) {
NSEnumerationConcurrent = (1UL << 0),
NSEnumerationReverse = (1UL << 1),
};

@interface NSSet
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts ;
@end

typedef int __attribute__((availability(swift, unavailable))) NSOrderedCollectionDifferenceCalculationOptions;
enum : NSOrderedCollectionDifferenceCalculationOptions {
typedef CF_OPTIONS(NSUInteger, NSOrderedCollectionDifferenceCalculationOptions) {
NSOrderedCollectionDifferenceCalculationOptions1,
NSOrderedCollectionDifferenceCalculationOptions2
};

typedef int __attribute__((availability(swift, unavailable))) NSCalendarUnit;
enum : NSCalendarUnit { NSCalendarUnit1, NSCalendarUnit2 };
typedef CF_OPTIONS(NSUInteger, NSCalendarUnit) {
NSCalendarUnit1,
NSCalendarUnit2
};

typedef int __attribute__((availability(swift, unavailable))) NSSearchPathDomainMask;
enum : NSSearchPathDomainMask { NSSearchPathDomainMask1, NSSearchPathDomainMask2 };
typedef CF_OPTIONS(NSUInteger, NSSearchPathDomainMask) {
NSSearchPathDomainMask1,
NSSearchPathDomainMask2
};

typedef int __attribute__((availability(swift, unavailable))) NSControlCharacterAction;
enum : NSControlCharacterAction { NSControlCharacterAction1, NSControlCharacterAction2 };
typedef CF_OPTIONS(NSUInteger, NSControlCharacterAction) {
NSControlCharacterAction1,
NSControlCharacterAction2
};

typedef int __attribute__((availability(swift, unavailable))) UIControlState;
enum : UIControlState { UIControlState1, UIControlState2 };
typedef CF_OPTIONS(NSUInteger, UIControlState) {
UIControlState1,
UIControlState2
};

typedef int __attribute__((availability(swift, unavailable))) UITableViewCellStateMask;
enum : UITableViewCellStateMask { UITableViewCellStateMask1, UITableViewCellStateMask2 };
typedef CF_OPTIONS(NSUInteger, UITableViewCellStateMask) {
UITableViewCellStateMask1,
UITableViewCellStateMask2
};

typedef int __attribute__((availability(swift, unavailable))) UIControlEvents;
enum : UIControlEvents { UIControlEvents1, UIControlEvents2 };
typedef CF_OPTIONS(NSUInteger, UIControlEvents) {
UIControlEvents1,
UIControlEvents2
};

typedef int __attribute__((availability(swift, unavailable)))
UITableViewScrollPosition;
enum : UITableViewScrollPosition {
typedef CF_OPTIONS(NSUInteger, UITableViewScrollPosition) {
UITableViewScrollPosition1,
UITableViewScrollPosition2
};
Expand Down
6 changes: 6 additions & 0 deletions test/Interop/Cxx/objc-correctness/Inputs/CFOptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#define __CF_OPTIONS_ATTRIBUTES __attribute__((flag_enum,enum_extensibility(open)))
#if (__cplusplus)
#define CF_OPTIONS(_type, _name) __attribute__((availability(swift,unavailable))) _type _name; enum __CF_OPTIONS_ATTRIBUTES : _name
#else
#define CF_OPTIONS(_type, _name) enum __CF_OPTIONS_ATTRIBUTES _name : _type _name; enum _name : _type
#endif
7 changes: 1 addition & 6 deletions test/Interop/Cxx/objc-correctness/Inputs/NSOptionsMangling.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
#define __CF_OPTIONS_ATTRIBUTES __attribute__((flag_enum,enum_extensibility(open)))
#if (__cplusplus)
#define CF_OPTIONS(_type, _name) __attribute__((availability(swift,unavailable))) _type _name; enum __CF_OPTIONS_ATTRIBUTES : _name
#else
#define CF_OPTIONS(_type, _name) enum __CF_OPTIONS_ATTRIBUTES _name : _type _name; enum _name : _type
#endif
#include "CFOptions.h"

typedef CF_OPTIONS(unsigned, UIControlState) { UIControlStateNormal = 0 };

Expand Down
25 changes: 25 additions & 0 deletions test/Interop/Cxx/objc-correctness/Inputs/NSStreamDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include "CFOptions.h"

typedef CF_OPTIONS(int, NSStreamEvent) {
NSStreamEventNone = 0,
NSStreamEventOpenCompleted = 1UL << 0,
NSStreamEventHasBytesAvailable = 1UL << 1,
NSStreamEventHasSpaceAvailable = 1UL << 2,
NSStreamEventErrorOccurred = 1UL << 3,
NSStreamEventEndEncountered = 1UL << 4
};

@protocol NSObject
@end

__attribute__((objc_root_class))
@interface NSObject <NSObject>
@end

@interface NSStream : NSObject
@end

@protocol NSStreamDelegate <NSObject>
@optional
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
@end
6 changes: 6 additions & 0 deletions test/Interop/Cxx/objc-correctness/Inputs/customNSOptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "CFOptions.h"

typedef CF_OPTIONS(unsigned, MyControlFlags) {
MyControlFlagsNone = 0,
MyControlFlagsFirst
};
9 changes: 9 additions & 0 deletions test/Interop/Cxx/objc-correctness/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ module CxxClassWithNSStringInit {
requires cplusplus
}

module CustomNSOptions {
header "customNSOptions.h"
}

module NSOptionsMangling {
header "NSOptionsMangling.h"
}
Expand All @@ -19,6 +23,11 @@ module NSNofiticationBridging {
requires cplusplus
}

module NSStreamDelegate {
header "NSStreamDelegate.h"
requires objc
}

module OSObject {
header "os-object.h"
requires objc
Expand Down
7 changes: 7 additions & 0 deletions test/Interop/Cxx/objc-correctness/custom-nsoptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -enable-objc-interop -enable-experimental-cxx-interop
// REQUIRES: objc_interop

import CustomNSOptions

let flags1: MyControlFlags = []
let flags2: MyControlFlags = [.first]
8 changes: 8 additions & 0 deletions test/Interop/Cxx/objc-correctness/nsstreamdelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -enable-objc-interop -enable-experimental-cxx-interop
// REQUIRES: objc_interop

import NSStreamDelegate

func foo<T: NSStreamDelegate>(_ delegate: T, stream: NSStream) {
delegate.stream!(stream, handle: NSStreamEvent.openCompleted)
}