Skip to content

Commit bd110a0

Browse files
committed
PrintAsClang: Forward reference enums when used transitively
There are two main scenarios when printing a compatibility header that references a @cdecl enum defined in Swift code. (1) When defined in the same module as it's used we can print the definition normally and then reference it. (2) When used in a different mode we need to print a forward declaration before we can reference it. This change adds printing the forward declaration and fix an issue where the compiler would instead print an @include of the Swift module. The import of the Swift module would work only in a local scenario where a compatibility header and module would be generated under the same name. However for a distributed frameworks we do not distribute the compatibility header so this strategy doesn't work. Relying on a forward declaration should be more reliable in all cases but clients may need to import the other compatibility header explicitly.
1 parent 138e2da commit bd110a0

File tree

3 files changed

+75
-8
lines changed

3 files changed

+75
-8
lines changed

include/swift/PrintAsClang/ClangMacros.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ CLANG_MACRO_BODY("SWIFT_ENUM_TAG", \
207207
"# define SWIFT_ENUM_TAG\n" \
208208
"# endif")
209209

210+
CLANG_MACRO_BODY("SWIFT_ENUM_FWD_DECL", \
211+
"# if (defined(__cplusplus) && __cplusplus >= 201103L) || " \
212+
" (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || " \
213+
" __has_feature(objc_fixed_enum)\n" \
214+
"# define SWIFT_ENUM_FWD_DECL(_type, _name) enum _name : _type _name;\n" \
215+
"# else\n" \
216+
"# define SWIFT_ENUM_FWD_DECL(_type, _name) _type _name;\n" \
217+
"# endif")
218+
210219
CLANG_MACRO("SWIFT_UNAVAILABLE", , "__attribute__((unavailable))")
211220
CLANG_MACRO("SWIFT_UNAVAILABLE_MSG", "(msg)", "__attribute__((unavailable(msg)))")
212221

lib/PrintAsClang/ModuleContentsWriter.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,14 @@ class ModuleWriter {
457457
}
458458
}
459459

460+
if (isa<EnumDecl>(D) && !D->hasClangNode() &&
461+
outputLangMode != OutputLanguageMode::Cxx) {
462+
// We don't want to add an import for a @cdecl or @objc enum declared
463+
// in Swift. We either do nothing for special enums like Optional as
464+
// done in the prologue here, or we forward declare them.
465+
return false;
466+
}
467+
460468
imports.insert(otherModule);
461469
return true;
462470
}
@@ -530,16 +538,20 @@ class ModuleWriter {
530538
}
531539

532540
void forwardDeclare(const EnumDecl *ED) {
533-
// Don't forward declare C enums.
534-
if (ED->getAttrs().getAttribute<CDeclAttr>())
535-
return;
536-
537-
assert(ED->isObjC() || ED->hasClangNode());
541+
assert(ED->isObjC() || ED->getAttrs().getAttribute<CDeclAttr>() ||
542+
ED->hasClangNode());
538543

539544
forwardDeclare(ED, [&]{
540-
os << "enum " << getNameForObjC(ED) << " : ";
541-
printer.print(ED->getRawType());
542-
os << ";\n";
545+
if (ED->getASTContext().LangOpts.hasFeature(Feature::CDecl)) {
546+
// Forward declare in a way to be compatible with older C standards.
547+
os << "typedef SWIFT_ENUM_FWD_DECL(";
548+
printer.print(ED->getRawType());
549+
os << ", " << getNameForObjC(ED) << ")\n";
550+
} else {
551+
os << "enum " << getNameForObjC(ED) << " : ";
552+
printer.print(ED->getRawType());
553+
os << ";\n";
554+
}
543555
});
544556
}
545557

@@ -608,6 +620,7 @@ class ModuleWriter {
608620
} else if (addImport(TD)) {
609621
return;
610622
} else if (auto ED = dyn_cast<EnumDecl>(TD)) {
623+
// Treat this after addImport to filter out special enums from the stdlib.
611624
forwardDeclare(ED);
612625
} else if (isa<GenericTypeParamDecl>(TD)) {
613626
llvm_unreachable("should not see generic parameters here");
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
/// Build CoreLib defining a @cdecl enum.
5+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) \
6+
// RUN: %t/CoreLib.swift -emit-module -verify -o %t \
7+
// RUN: -emit-clang-header-path %t/CoreLib.h \
8+
// RUN: -enable-experimental-feature CDecl
9+
// RUN: %check-in-clang-c %t/CoreLib.h -I %t
10+
11+
/// Build MiddleLib using the @cdecl enum in API.
12+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) \
13+
// RUN: %t/MiddleLib.swift -emit-module -verify -o %t -I %t \
14+
// RUN: -emit-clang-header-path %t/MiddleLib.h \
15+
// RUN: -enable-experimental-feature CDecl
16+
// RUN: %FileCheck %s --input-file %t/MiddleLib.h
17+
// RUN: %check-in-clang-c %t/MiddleLib.h -I %t
18+
19+
/// Build a client.
20+
// RUN: %clang-no-modules -c %t/Client.c -I %t \
21+
// RUN: -F %S/../Inputs/clang-importer-sdk-path/frameworks \
22+
// RUN: -I %clang-include-dir -Werror \
23+
// RUN: -isysroot %S/../Inputs/clang-importer-sdk
24+
25+
// REQUIRES: swift_feature_CDecl
26+
27+
//--- CoreLib.swift
28+
@cdecl("CEnum")
29+
public enum CEnum: CInt { case A, B }
30+
31+
//--- MiddleLib.swift
32+
import CoreLib
33+
34+
@cdecl("CFunc")
35+
public func CFunc(e: CEnum) {}
36+
// CHECK: typedef SWIFT_ENUM_FWD_DECL(int, CEnum)
37+
// CHECK: SWIFT_EXTERN void CFunc(SWIFT_ENUM_TAG CEnum e) SWIFT_NOEXCEPT;
38+
39+
//--- Client.c
40+
#include "CoreLib.h"
41+
#include "MiddleLib.h"
42+
43+
int main() {
44+
CFunc(CEnumA);
45+
}

0 commit comments

Comments
 (0)