PrintAsClang: Print @cdecl enums in the compatibility header

Print @cdecl enums in the C section of the compatibility header. Use and
extend the macros to support C compiler clients.

The macro is adapted to the features supported by the client compiler.
It uses an Objective-C style macro with raw type when available and
fallbacks to a simple typedef for C compatibility.
This commit is contained in:
Alexis Laferrière
2025-06-02 13:19:03 -07:00
parent 6d091123f9
commit 138e2daa3e
5 changed files with 217 additions and 11 deletions

View File

@@ -176,20 +176,37 @@ CLANG_MACRO_CONDITIONAL("SWIFT_ENUM_ATTR", "(_extensibility)", \
"__attribute__((enum_extensibility(_extensibility)))")
CLANG_MACRO_BODY("SWIFT_ENUM", \
"# define SWIFT_ENUM(_type, _name, _extensibility) " \
"# if (defined(__cplusplus) && __cplusplus >= 201103L) || " \
" (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || " \
" __has_feature(objc_fixed_enum)\n" \
"# define SWIFT_ENUM(_type, _name, _extensibility) " \
"enum _name : _type _name; " \
"enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type\n" \
"# if __has_feature(generalized_swift_name)\n" \
"# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) " \
"enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); " \
"enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) " \
"SWIFT_ENUM_EXTRA _name : _type\n" \
"# if __has_feature(generalized_swift_name)\n" \
"# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) " \
"enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); " \
"enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) " \
"SWIFT_ENUM_EXTRA _name : _type\n" \
"# else\n" \
"# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) " \
"SWIFT_ENUM(_type, _name, _extensibility)\n" \
"# endif\n" \
"# else\n" \
"# define SWIFT_ENUM(_type, _name, _extensibility) _type _name; enum \n" \
"# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) " \
"SWIFT_ENUM(_type, _name, _extensibility)\n" \
"# endif")
CLANG_MACRO_DEFINED("SWIFT_ENUM_NAMED")
CLANG_MACRO_BODY("SWIFT_ENUM_TAG", \
"# if (defined(__cplusplus) && __cplusplus >= 201103L) || " \
" (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || " \
" __has_feature(objc_fixed_enum)\n" \
"# define SWIFT_ENUM_TAG enum\n" \
"# else\n" \
"# define SWIFT_ENUM_TAG\n" \
"# endif")
CLANG_MACRO("SWIFT_UNAVAILABLE", , "__attribute__((unavailable))")
CLANG_MACRO("SWIFT_UNAVAILABLE_MSG", "(msg)", "__attribute__((unavailable(msg)))")

View File

@@ -2385,8 +2385,14 @@ private:
}
void maybePrintTagKeyword(const TypeDecl *NTD) {
if (isa<EnumDecl>(NTD) && !NTD->hasClangNode()) {
os << "enum ";
if (auto *ED = dyn_cast<EnumDecl>(NTD); !NTD->hasClangNode()) {
if (ED->getAttrs().hasAttribute<CDeclAttr>()) {
// We should be able to use the tag macro for all printed enums but
// for now restrict it to @cdecl to guard it behind the feature flag.
os << "SWIFT_ENUM_TAG ";
} else {
os << "enum ";
}
return;
}
@@ -3016,9 +3022,17 @@ bool DeclAndTypePrinter::shouldInclude(const ValueDecl *VD) {
(outputLang == OutputLanguageMode::C))
return false;
// C output mode only accepts @cdecl functions.
// C output mode only prints @cdecl functions and enums.
if (outputLang == OutputLanguageMode::C &&
!cdeclKind) {
!cdeclKind && !isa<EnumDecl>(VD)) {
return false;
}
// The C mode prints @cdecl enums and reject other enums,
// while other modes accept other enums and reject @cdecl ones.
if (isa<EnumDecl>(VD) &&
VD->getAttrs().hasAttribute<CDeclAttr>() !=
(outputLang == OutputLanguageMode::C)) {
return false;
}

View File

@@ -530,6 +530,10 @@ public:
}
void forwardDeclare(const EnumDecl *ED) {
// Don't forward declare C enums.
if (ED->getAttrs().getAttribute<CDeclAttr>())
return;
assert(ED->isObjC() || ED->hasClangNode());
forwardDeclare(ED, [&]{
@@ -864,7 +868,7 @@ public:
SmallVector<ProtocolConformance *, 1> conformances;
auto errorTypeProto = ctx.getProtocol(KnownProtocolKind::Error);
if (outputLangMode != OutputLanguageMode::Cxx
if (outputLangMode == OutputLanguageMode::ObjC
&& ED->lookupConformance(errorTypeProto, conformances)) {
bool hasDomainCase = std::any_of(ED->getAllElements().begin(),
ED->getAllElements().end(),

View File

@@ -0,0 +1,126 @@
/// Variant of PrintAsObjC/enums.swift for @cdecl enums.
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-source-import \
// RUN: -emit-module -emit-module-doc -o %t %s \
// RUN: -import-objc-header %S/Inputs/enums.h \
// RUN: -emit-objc-header-path %t/enums.h \
// RUN: -disable-objc-attr-requires-foundation-module \
// RUN: -enable-experimental-feature CDecl
// RUN: %FileCheck %s --input-file %t/enums.h
// RUN: %FileCheck -check-prefix=NEGATIVE %s --input-file %t/enums.h
// RUN: %check-in-clang %t/enums.h
// REQUIRES: swift_feature_CDecl
// REQUIRES: objc_interop
import Foundation
// NEGATIVE-NOT: enum EnumNamed
/// No error domains in C mode.
// NEGATIVE-NOT: @"main.
// CHECK-LABEL: typedef SWIFT_ENUM_NAMED(ptrdiff_t, ObjcEnumNamed, "EnumNamed", closed) {
// CHECK-NEXT: ObjcEnumNamedA = 0,
// CHECK-NEXT: ObjcEnumNamedB = 1,
// CHECK-NEXT: ObjcEnumNamedC = 2,
// CHECK-NEXT: ObjcEnumNamedD = 3,
// CHECK-NEXT: ObjcEnumNamedHelloDolly = 4,
// CHECK-NEXT: };
@cdecl("ObjcEnumNamed") enum EnumNamed: Int {
case A, B, C, d, helloDolly
}
// CHECK-LABEL: typedef SWIFT_ENUM_NAMED(unsigned int, ExplicitValues, "ExplicitValues", closed) {
// CHECK-NEXT: ExplicitValuesZim = 0,
// CHECK-NEXT: ExplicitValuesZang = 219,
// CHECK-NEXT: ExplicitValuesZung = 220,
// CHECK-NEXT: };
// NEGATIVE-NOT: ExplicitValuesDomain
@cdecl("ExplicitValues") enum ExplicitValues: CUnsignedInt {
case Zim, Zang = 219, Zung
func methodNotExportedToC() {}
}
// CHECK: /// Foo: A feer, a female feer.
// CHECK-NEXT: typedef SWIFT_ENUM_NAMED(int, FooComments, "FooComments", closed) {
// CHECK: /// Zim: A zeer, a female zeer.
// CHECK-NEXT: FooCommentsZim = 0,
// CHECK-NEXT: FooCommentsZang = 1,
// CHECK-NEXT: FooCommentsZung = 2,
// CHECK-NEXT: }
/// Foo: A feer, a female feer.
@cdecl("FooComments") public enum FooComments: CInt {
/// Zim: A zeer, a female zeer.
case Zim
case Zang, Zung
}
// CHECK-LABEL: typedef SWIFT_ENUM_NAMED(int16_t, NegativeValues, "NegativeValues", closed) {
// CHECK-NEXT: Zang = -219,
// CHECK-NEXT: Zung = -218,
// CHECK-NEXT: };
@cdecl("NegativeValues") enum NegativeValues: Int16 {
case Zang = -219, Zung
func methodNotExportedToC() {}
}
// CHECK-LABEL: typedef SWIFT_ENUM_NAMED(ptrdiff_t, SomeError, "SomeError", closed) {
// CHECK-NEXT: SomeErrorBadness = 9001,
// CHECK-NEXT: SomeErrorWorseness = 9002,
// CHECK-NEXT: };
@cdecl("SomeError") enum SomeError: Int, Error {
case Badness = 9001
case Worseness
}
// CHECK-LABEL: typedef SWIFT_ENUM_NAMED(ptrdiff_t, SomeOtherError, "SomeOtherError", closed) {
// CHECK-NEXT: SomeOtherErrorDomain = 0,
// CHECK-NEXT: };
@cdecl("SomeOtherError") enum SomeOtherError: Int, Error {
case Domain
}
// CHECK-LABEL: typedef SWIFT_ENUM_NAMED(ptrdiff_t, ObjcErrorType, "SomeRenamedErrorType", closed) {
// CHECK-NEXT: ObjcErrorTypeBadStuff = 0,
// CHECK-NEXT: };
@cdecl("ObjcErrorType") enum SomeRenamedErrorType: Int, Error {
case BadStuff
}
@cdecl("acceptMemberImported") func acceptMemberImported(a: Wrapper.Raw, b: Wrapper.Enum, c: Wrapper.Options, d: Wrapper.Typedef, e: Wrapper.Anon, ee: Wrapper.Anon2) {}
// CHECK-LABEL: SWIFT_EXTERN void acceptMemberImported(enum MemberRaw a, enum MemberEnum b, MemberOptions c, enum MemberTypedef d, MemberAnon e, MemberAnon2 ee) SWIFT_NOEXCEPT;
@cdecl("acceptPlainEnum") func acceptPlainEnum(_: NSMalformedEnumMissingTypedef) {}
// CHECK-LABEL: SWIFT_EXTERN void acceptPlainEnum(enum NSMalformedEnumMissingTypedef) SWIFT_NOEXCEPT;
@cdecl("acceptTopLevelImported") func acceptTopLevelImported(a: TopLevelRaw, b: TopLevelEnum, c: TopLevelOptions, d: TopLevelTypedef, e: TopLevelAnon) {}
// CHECK-LABEL: SWIFT_EXTERN void acceptTopLevelImported(enum TopLevelRaw a, TopLevelEnum b, TopLevelOptions c, TopLevelTypedef d, TopLevelAnon e) SWIFT_NOEXCEPT;
@cdecl("takeAndReturnEnumC") func takeAndReturnEnumC(_ foo: FooComments) -> NegativeValues {
return .Zung
}
// CHECK-LABEL: SWIFT_EXTERN SWIFT_ENUM_TAG NegativeValues takeAndReturnEnumC(SWIFT_ENUM_TAG FooComments foo) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
@cdecl("takeAndReturnRenamedEnum") func takeAndReturnRenamedEnum(_ foo: EnumNamed) -> EnumNamed {
return .A
}
// CHECK-LABEL: SWIFT_EXTERN SWIFT_ENUM_TAG ObjcEnumNamed takeAndReturnRenamedEnum(SWIFT_ENUM_TAG ObjcEnumNamed foo) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
/// Objective-C user.
// CHECK-LABEL: SWIFT_ENUM_FWD_DECL(int, FooComments)
// CHECK-LABEL: SWIFT_ENUM_FWD_DECL(int16_t, NegativeValues)
// CHECK-LABEL: SWIFT_EXTERN SWIFT_ENUM_TAG NegativeValues takeAndReturnEnumObjC(SWIFT_ENUM_TAG FooComments foo) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
@_cdecl("takeAndReturnEnumObjC") func takeAndReturnEnumObjC(_ foo: FooComments) -> NegativeValues {
return .Zung
}

View File

@@ -27,6 +27,22 @@
// CHECK: extern "C" {
// CHECK: #endif
// CHECK: /// Enums
// CHECK: typedef SWIFT_ENUM_NAMED(int, CEnum, "CEnum", closed) {
// CHECK: CEnumA = 0,
// CHECK: CEnumB = 1,
// CHECK: };
// CHECK: typedef SWIFT_ENUM_NAMED(long, CEnumRenamed_CName, "CEnumRenamed", closed) {
// CHECK: CEnumRenamed_CNameA = 0,
// CHECK: CEnumRenamed_CNameB = 1,
// CHECK: };
// CHECK: typedef SWIFT_ENUM_NAMED(char, zCEnumDefinedLate, "zCEnumDefinedLate", closed) {
// CHECK: CEnumDefinedLateA = 0,
// CHECK: CEnumDefinedLateB = 1,
// CHECK: };
/// My documentation
@cdecl("simple")
func a_simple(x: Int, bar y: Int) -> Int { return x }
@@ -62,6 +78,30 @@ func g_nullablePointers(_ x: UnsafeMutableRawPointer,
z: UnsafeMutableRawPointer!) {}
// CHECK: SWIFT_EXTERN void nullable_pointers(void * _Nonnull x, void * _Nullable y, void * _Null_unspecified z) SWIFT_NOEXCEPT;
/// Enums
@cdecl("CEnum")
enum CEnum: CInt { case A, B }
@cdecl("CEnumRenamed_CName")
enum CEnumRenamed: CLong { case A, B }
@cdecl("use_enum")
func h_useCEnum(e: CEnum) -> CEnum { return e }
// CHECK: SWIFT_EXTERN SWIFT_ENUM_TAG CEnum use_enum(SWIFT_ENUM_TAG CEnum e) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
@cdecl("use_enum_renamed")
func i_useCEnumLong(e: CEnumRenamed) -> CEnumRenamed { return e }
// CHECK: SWIFT_EXTERN SWIFT_ENUM_TAG CEnumRenamed_CName use_enum_renamed(SWIFT_ENUM_TAG CEnumRenamed_CName e) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
@cdecl("use_enum_late")
func j_useCEnumChar(e: zCEnumDefinedLate) -> zCEnumDefinedLate { return e }
// CHECK: SWIFT_EXTERN SWIFT_ENUM_TAG zCEnumDefinedLate use_enum_late(SWIFT_ENUM_TAG zCEnumDefinedLate e) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
/// Declare this enum late in the source file and in alphabetical order.
@cdecl("zCEnumDefinedLate")
enum zCEnumDefinedLate: CChar { case A, B }
// CHECK: #if defined(__cplusplus)
// CHECK-NEXT: }
// CHECK-NEXT: #endif
@@ -74,5 +114,10 @@ int main() {
ptrdiff_t x = simple(42, 43);
primitiveTypes(1, 2, 3, 'a', 1.0f, 2.0, true);
has_keyword_arg_names(1, 2);
(void)use_enum(CEnumA);
(void)use_enum_renamed(CEnumRenamed_CNameB);
(void)use_enum_late(zCEnumDefinedLateA);
return_never();
}