Skip to content

Commit 30d09de

Browse files
authored
Merge pull request #60715 from hyp/eng/expose-cxx
[interop][SwiftToCxx] add @_expose(Cxx) attribute suppport
2 parents 1a7146f + 8de7939 commit 30d09de

22 files changed

+307
-28
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,22 @@ func g() {
358358
}
359359
```
360360

361+
## `@_expose(<language>)`
362+
363+
Indicates that a particular declaration should be included
364+
in the emitted specified foreign language binding.
365+
366+
When applied to a nominal type declaration, all of the
367+
public declarations inside this type become exposed as well.
368+
369+
### `_expose(Cxx[, <"cxxName">])`
370+
371+
Indicates that a particular declaration should be included
372+
in the generated C++ binding header.
373+
374+
The optional "cxxName" string will be used as the name of
375+
the generated C++ declaration.
376+
361377
## `@_fixed_layout`
362378

363379
Same as `@frozen` but also works for classes.

include/swift/AST/Attr.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,12 @@ SIMPLE_DECL_ATTR(_alwaysEmitConformanceMetadata, AlwaysEmitConformanceMetadata,
757757
OnProtocol | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
758758
132)
759759

760+
DECL_ATTR(_expose, Expose,
761+
OnFunc | OnNominalType | OnVar |
762+
LongAttribute | UserInaccessible |
763+
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
764+
133)
765+
760766
// If you're adding a new underscored attribute here, please document it in
761767
// docs/ReferenceGuides/UnderscoredAttributes.md.
762768

include/swift/AST/Attr.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,23 @@ class BackDeployAttr: public DeclAttribute {
22262226
}
22272227
};
22282228

2229+
/// Defines the `@_expose` attribute, used to expose declarations in the
2230+
/// header used by C/C++ to interoperate with Swift.
2231+
class ExposeAttr : public DeclAttribute {
2232+
public:
2233+
ExposeAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit)
2234+
: DeclAttribute(DAK_Expose, AtLoc, Range, Implicit), Name(Name) {}
2235+
2236+
ExposeAttr(StringRef Name, bool Implicit)
2237+
: ExposeAttr(Name, SourceLoc(), SourceRange(), Implicit) {}
2238+
2239+
/// The exposed declaration name.
2240+
const StringRef Name;
2241+
2242+
static bool classof(const DeclAttribute *DA) {
2243+
return DA->getKind() == DAK_Expose;
2244+
}
2245+
};
22292246

22302247
/// Attributes that may be applied to declarations.
22312248
class DeclAttributes {

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,11 @@ ERROR(cdecl_empty_name,none,
14811481
ERROR(cdecl_throws,none,
14821482
"raising errors from @_cdecl functions is not supported", ())
14831483

1484+
ERROR(expose_only_non_other_attr,none,
1485+
"@_expose attribute cannot be applied to an '%0' declaration", (StringRef))
1486+
ERROR(expose_inside_unexposed_decl,none,
1487+
"@_expose attribute cannot be applied inside of unexposed declaration %0", (DeclName))
1488+
14841489
ERROR(attr_methods_only,none,
14851490
"only methods can be declared %0", (DeclAttribute))
14861491
ERROR(attr_decl_async,none,

include/swift/AST/SwiftNameTranslation.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ namespace objc_translation {
5757

5858
namespace cxx_translation {
5959

60+
using objc_translation::CustomNamesOnly_t;
61+
62+
StringRef
63+
getNameForCxx(const ValueDecl *VD,
64+
CustomNamesOnly_t customNamesOnly = objc_translation::Normal);
65+
6066
/// Returns true if the given value decl D is visible to C++ of its
6167
/// own accord (i.e. without considering its context)
6268
bool isVisibleToCxx(const ValueDecl *VD, AccessLevel minRequiredAccess,

lib/AST/Attr.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,14 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
10461046
Printer << "@_cdecl(\"" << cast<CDeclAttr>(this)->Name << "\")";
10471047
break;
10481048

1049+
case DAK_Expose:
1050+
Printer.printAttrName("@_expose");
1051+
Printer << "(Cxx";
1052+
if (!cast<ExposeAttr>(this)->Name.empty())
1053+
Printer << ", \"" << cast<ExposeAttr>(this)->Name << "\"";
1054+
Printer << ")";
1055+
break;
1056+
10491057
case DAK_ObjC: {
10501058
Printer.printAttrName("@objc");
10511059
llvm::SmallString<32> scratch;
@@ -1440,6 +1448,8 @@ StringRef DeclAttribute::getAttrName() const {
14401448
return "_unavailableFromAsync";
14411449
case DAK_BackDeploy:
14421450
return "_backDeploy";
1451+
case DAK_Expose:
1452+
return "_expose";
14431453
}
14441454
llvm_unreachable("bad DeclAttrKind");
14451455
}

lib/AST/SwiftNameTranslation.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,20 @@ isVisibleToObjC(const ValueDecl *VD, AccessLevel minRequiredAccess,
151151
return false;
152152
}
153153

154+
StringRef
155+
swift::cxx_translation::getNameForCxx(const ValueDecl *VD,
156+
CustomNamesOnly_t customNamesOnly) {
157+
if (const auto *Expose = VD->getAttrs().getAttribute<ExposeAttr>()) {
158+
if (!Expose->Name.empty())
159+
return Expose->Name;
160+
}
161+
162+
if (customNamesOnly)
163+
return StringRef();
164+
165+
return VD->getBaseIdentifier().str();
166+
}
167+
154168
bool swift::cxx_translation::isVisibleToCxx(const ValueDecl *VD,
155169
AccessLevel minRequiredAccess,
156170
bool checkParent) {

lib/Parse/ParseDecl.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,27 +2376,45 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
23762376
}
23772377

23782378
case DAK_CDecl:
2379+
case DAK_Expose:
23792380
case DAK_SILGenName: {
23802381
if (!consumeIf(tok::l_paren)) {
23812382
diagnose(Loc, diag::attr_expected_lparen, AttrName,
23822383
DeclAttribute::isDeclModifier(DK));
23832384
return false;
23842385
}
23852386

2386-
if (Tok.isNot(tok::string_literal)) {
2387-
diagnose(Loc, diag::attr_expected_string_literal, AttrName);
2388-
return false;
2387+
bool ParseSymbolName = true;
2388+
if (DK == DAK_Expose) {
2389+
if (Tok.isNot(tok::identifier) || Tok.getText() != "Cxx") {
2390+
diagnose(Tok.getLoc(), diag::attr_expected_option_such_as, AttrName,
2391+
"Cxx");
2392+
if (Tok.isNot(tok::identifier))
2393+
return false;
2394+
DiscardAttribute = true;
2395+
}
2396+
consumeToken(tok::identifier);
2397+
ParseSymbolName = consumeIf(tok::comma);
23892398
}
23902399

2391-
Optional<StringRef> AsmName = getStringLiteralIfNotInterpolated(
2392-
Loc, ("'" + AttrName + "'").str());
2400+
Optional<StringRef> AsmName;
2401+
if (ParseSymbolName) {
2402+
if (Tok.isNot(tok::string_literal)) {
2403+
diagnose(Loc, diag::attr_expected_string_literal, AttrName);
2404+
return false;
2405+
}
23932406

2394-
consumeToken(tok::string_literal);
2407+
AsmName =
2408+
getStringLiteralIfNotInterpolated(Loc, ("'" + AttrName + "'").str());
23952409

2396-
if (AsmName.hasValue())
2410+
consumeToken(tok::string_literal);
2411+
2412+
if (AsmName.hasValue())
2413+
AttrRange = SourceRange(Loc, Tok.getRange().getStart());
2414+
else
2415+
DiscardAttribute = true;
2416+
} else
23972417
AttrRange = SourceRange(Loc, Tok.getRange().getStart());
2398-
else
2399-
DiscardAttribute = true;
24002418

24012419
if (!consumeIf(tok::r_paren)) {
24022420
diagnose(Loc, diag::attr_expected_rparen, AttrName,
@@ -2419,6 +2437,10 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
24192437
else if (DK == DAK_CDecl)
24202438
Attributes.add(new (Context) CDeclAttr(AsmName.getValue(), AtLoc,
24212439
AttrRange, /*Implicit=*/false));
2440+
else if (DK == DAK_Expose)
2441+
Attributes.add(new (Context) ExposeAttr(
2442+
AsmName ? AsmName.getValue() : StringRef(""), AtLoc, AttrRange,
2443+
/*Implicit=*/false));
24222444
else
24232445
llvm_unreachable("out of sync with switch");
24242446
}

lib/PrintAsClang/ClangSyntaxPrinter.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "swift/ABI/MetadataValues.h"
1515
#include "swift/AST/Decl.h"
1616
#include "swift/AST/Module.h"
17+
#include "swift/AST/SwiftNameTranslation.h"
1718

1819
using namespace swift;
1920
using namespace cxx_synthesis;
@@ -52,7 +53,7 @@ void ClangSyntaxPrinter::printIdentifier(StringRef name) {
5253

5354
void ClangSyntaxPrinter::printBaseName(const ValueDecl *decl) {
5455
assert(decl->getName().isSimpleName());
55-
printIdentifier(decl->getBaseIdentifier().str());
56+
printIdentifier(cxx_translation::getNameForCxx(decl));
5657
}
5758

5859
void ClangSyntaxPrinter::printModuleNameCPrefix(const ModuleDecl &mod) {

lib/PrintAsClang/DeclAndTypePrinter.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,7 +1203,7 @@ class DeclAndTypePrinter::Implementation
12031203
DeclAndTypeClangFunctionPrinter::FunctionSignatureModifiers modifiers;
12041204
modifiers.isInline = true;
12051205
funcPrinter.printFunctionSignature(
1206-
FD, FD->getName().getBaseIdentifier().get(), resultTy,
1206+
FD, cxx_translation::getNameForCxx(FD), resultTy,
12071207
DeclAndTypeClangFunctionPrinter::FunctionSignatureKind::CxxInlineThunk,
12081208
{}, modifiers);
12091209
// FIXME: Support throwing exceptions for Swift errors.
@@ -2393,8 +2393,16 @@ static bool isAsyncAlternativeOfOtherDecl(const ValueDecl *VD) {
23932393
return false;
23942394
}
23952395

2396+
static bool hasExposeAttr(const ValueDecl *VD) {
2397+
if (VD->getAttrs().hasAttribute<ExposeAttr>())
2398+
return true;
2399+
if (const auto *NMT = dyn_cast<NominalTypeDecl>(VD->getDeclContext()))
2400+
return hasExposeAttr(NMT);
2401+
return false;
2402+
}
2403+
23962404
bool DeclAndTypePrinter::shouldInclude(const ValueDecl *VD) {
2397-
return !VD->isInvalid() &&
2405+
return !VD->isInvalid() && (!requiresExposedAttribute || hasExposeAttr(VD)) &&
23982406
(outputLang == OutputLanguageMode::Cxx
23992407
? cxx_translation::isVisibleToCxx(VD, minRequiredAccess)
24002408
: isVisibleToObjC(VD, minRequiredAccess)) &&

lib/PrintAsClang/DeclAndTypePrinter.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class DeclAndTypePrinter {
4747
PrimitiveTypeMapping &typeMapping;
4848
SwiftToClangInteropContext &interopContext;
4949
AccessLevel minRequiredAccess;
50+
bool requiresExposedAttribute;
5051
OutputLanguageMode outputLang;
5152

5253
/// The name 'CFTypeRef'.
@@ -62,11 +63,14 @@ class DeclAndTypePrinter {
6263
DelayedMemberSet &delayed,
6364
PrimitiveTypeMapping &typeMapping,
6465
SwiftToClangInteropContext &interopContext,
65-
AccessLevel access, OutputLanguageMode outputLang)
66+
AccessLevel access, bool requiresExposedAttribute,
67+
OutputLanguageMode outputLang)
6668
: M(mod), os(out), prologueOS(prologueOS),
6769
outOfLineDefinitionsOS(outOfLineDefinitionsOS), delayedMembers(delayed),
6870
typeMapping(typeMapping), interopContext(interopContext),
69-
minRequiredAccess(access), outputLang(outputLang) {}
71+
minRequiredAccess(access),
72+
requiresExposedAttribute(requiresExposedAttribute),
73+
outputLang(outputLang) {}
7074

7175
/// Returns true if \p VD should be included in a compatibility header for
7276
/// the options the printer was constructed with.

lib/PrintAsClang/ModuleContentsWriter.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,12 @@ class ModuleWriter {
136136
ModuleWriter(raw_ostream &os, raw_ostream &prologueOS,
137137
llvm::SmallPtrSetImpl<ImportModuleTy> &imports, ModuleDecl &mod,
138138
SwiftToClangInteropContext &interopContext, AccessLevel access,
139-
OutputLanguageMode outputLang)
139+
bool requiresExposedAttribute, OutputLanguageMode outputLang)
140140
: os(os), imports(imports), M(mod),
141141
outOfLineDefinitionsOS(outOfLineDefinitions),
142142
printer(M, os, prologueOS, outOfLineDefinitionsOS, delayedMembers,
143-
typeMapping, interopContext, access, outputLang),
143+
typeMapping, interopContext, access, requiresExposedAttribute,
144+
outputLang),
144145
outputLangMode(outputLang) {}
145146

146147
PrimitiveTypeMapping &getTypeMapping() { return typeMapping; }
@@ -672,21 +673,23 @@ void swift::printModuleContentsAsObjC(
672673
ModuleDecl &M, SwiftToClangInteropContext &interopContext) {
673674
llvm::raw_null_ostream prologueOS;
674675
ModuleWriter(os, prologueOS, imports, M, interopContext, getRequiredAccess(M),
675-
OutputLanguageMode::ObjC)
676+
/*requiresExposedAttribute=*/false, OutputLanguageMode::ObjC)
676677
.write();
677678
}
678679

679680
void swift::printModuleContentsAsCxx(
680681
raw_ostream &os, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
681-
ModuleDecl &M, SwiftToClangInteropContext &interopContext) {
682+
ModuleDecl &M, SwiftToClangInteropContext &interopContext,
683+
bool requiresExposedAttribute) {
682684
std::string moduleContentsBuf;
683685
llvm::raw_string_ostream moduleOS{moduleContentsBuf};
684686
std::string modulePrologueBuf;
685687
llvm::raw_string_ostream prologueOS{modulePrologueBuf};
686688

687689
// FIXME: Use getRequiredAccess once @expose is supported.
688690
ModuleWriter writer(moduleOS, prologueOS, imports, M, interopContext,
689-
AccessLevel::Public, OutputLanguageMode::Cxx);
691+
AccessLevel::Public, requiresExposedAttribute,
692+
OutputLanguageMode::Cxx);
690693
writer.write();
691694

692695
os << "#ifndef SWIFT_PRINTED_CORE\n";

lib/PrintAsClang/ModuleContentsWriter.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace clang {
2323
}
2424

2525
namespace swift {
26+
class Decl;
2627
class ModuleDecl;
2728
class SwiftToClangInteropContext;
2829

@@ -40,7 +41,8 @@ void printModuleContentsAsObjC(raw_ostream &os,
4041
void printModuleContentsAsCxx(raw_ostream &os,
4142
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
4243
ModuleDecl &M,
43-
SwiftToClangInteropContext &interopContext);
44+
SwiftToClangInteropContext &interopContext,
45+
bool requiresExposedAttribute);
4446

4547
} // end namespace swift
4648

lib/PrintAsClang/PrintAsClang.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,13 @@ static std::string computeMacroGuard(const ModuleDecl *M) {
491491

492492
static std::string
493493
getModuleContentsCxxString(ModuleDecl &M,
494-
SwiftToClangInteropContext &interopContext) {
494+
SwiftToClangInteropContext &interopContext,
495+
bool requiresExposedAttribute) {
495496
SmallPtrSet<ImportModuleTy, 8> imports;
496497
std::string moduleContentsBuf;
497498
llvm::raw_string_ostream moduleContents{moduleContentsBuf};
498-
printModuleContentsAsCxx(moduleContents, imports, M, interopContext);
499+
printModuleContentsAsCxx(moduleContents, imports, M, interopContext,
500+
requiresExposedAttribute);
499501
return moduleContents.str();
500502
}
501503

@@ -518,8 +520,11 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
518520
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
519521
emitCxxConditional(os, [&] {
520522
// FIXME: Expose Swift with @expose by default.
521-
if (ExposePublicDeclsInClangHeader) {
522-
os << getModuleContentsCxxString(*M, interopContext);
523+
if (ExposePublicDeclsInClangHeader ||
524+
M->DeclContext::getASTContext().LangOpts.EnableCXXInterop) {
525+
os << getModuleContentsCxxString(
526+
*M, interopContext,
527+
/*requiresExposedAttribute=*/!ExposePublicDeclsInClangHeader);
523528
}
524529
});
525530
writeEpilogue(os);

lib/PrintAsClang/PrintClangFunction.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/AST/Decl.h"
2222
#include "swift/AST/GenericParamList.h"
2323
#include "swift/AST/ParameterList.h"
24+
#include "swift/AST/SwiftNameTranslation.h"
2425
#include "swift/AST/Type.h"
2526
#include "swift/AST/TypeVisitor.h"
2627
#include "swift/ClangImporter/ClangImporter.h"
@@ -701,8 +702,8 @@ void DeclAndTypeClangFunctionPrinter::printCxxMethod(
701702
modifiers.isConst =
702703
!isa<ClassDecl>(typeDeclContext) && !isMutating && !isConstructor;
703704
printFunctionSignature(
704-
FD, isConstructor ? "init" : FD->getName().getBaseIdentifier().get(),
705-
resultTy, FunctionSignatureKind::CxxInlineThunk, {}, modifiers);
705+
FD, isConstructor ? "init" : cxx_translation::getNameForCxx(FD), resultTy,
706+
FunctionSignatureKind::CxxInlineThunk, {}, modifiers);
706707
if (!isDefinition) {
707708
os << ";\n";
708709
return;
@@ -734,7 +735,8 @@ static bool canRemapBoolPropertyNameDirectly(StringRef name) {
734735
static std::string remapPropertyName(const AccessorDecl *accessor,
735736
Type resultTy) {
736737
// For a getter or setter, go through the variable or subscript decl.
737-
StringRef propertyName = accessor->getStorage()->getBaseIdentifier().str();
738+
StringRef propertyName =
739+
cxx_translation::getNameForCxx(accessor->getStorage());
738740

739741
// Boolean property getters can be remapped directly in certain cases.
740742
if (accessor->isGetter() && resultTy->isBool() &&

0 commit comments

Comments
 (0)