Skip to content

[interop][SwiftToCxx] avoid emitting ambiguous C++ overloads and emit unavailable type stubs for top level types that could not be emitted in the C++ section of the generated header #65728

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
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
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,14 @@ ERROR(expose_enum_case_type_to_cxx,none,
"enum %0 can not be represented in C++ as one of its cases has an associated value with type that can't be represented in C++", (ValueDecl *))
ERROR(expose_enum_case_tuple_to_cxx,none,
"enum %0 can not yet be represented in C++ as one of its cases has multiple associated values", (ValueDecl *))
ERROR(expose_protocol_to_cxx_unsupported,none,
"protocol %0 can not yet be represented in C++", (ValueDecl *))
ERROR(expose_move_only_to_cxx,none,
"move-only %0 %1 can not yet be represented in C++", (DescriptiveDeclKind, ValueDecl *))
ERROR(unexposed_other_decl_in_cxx,none,
"%0 %1 is not yet exposed to C++", (DescriptiveDeclKind, ValueDecl *))
ERROR(unsupported_other_decl_in_cxx,none,
"Swift %0 %1 cannot be represented in C++", (DescriptiveDeclKind, ValueDecl *))

ERROR(attr_methods_only,none,
"only methods can be declared %0", (DeclAttribute))
Expand Down
8 changes: 7 additions & 1 deletion include/swift/AST/SwiftNameTranslation.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
#ifndef SWIFT_NAME_TRANSLATION_H
#define SWIFT_NAME_TRANSLATION_H

#include "swift/AST/Identifier.h"
#include "swift/AST/AttrKind.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/Identifier.h"

namespace swift {
class ValueDecl;
Expand Down Expand Up @@ -76,8 +77,13 @@ enum RepresentationError {
UnrepresentableIndirectEnum,
UnrepresentableEnumCaseType,
UnrepresentableEnumCaseTuple,
UnrepresentableProtocol,
UnrepresentableMoveOnly,
};

/// Constructs a diagnostic that describes the given C++ representation error.
Diagnostic diagnoseRepresenationError(RepresentationError error, ValueDecl *vd);

struct DeclRepresentation {
RepresentationKind kind;
llvm::Optional<RepresentationError> error;
Expand Down
48 changes: 48 additions & 0 deletions lib/AST/SwiftNameTranslation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "swift/AST/SwiftNameTranslation.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/LazyResolver.h"
#include "swift/AST/Module.h"
#include "swift/AST/ParameterList.h"
Expand Down Expand Up @@ -163,6 +164,9 @@ swift::cxx_translation::getNameForCxx(const ValueDecl *VD,
if (customNamesOnly)
return StringRef();

if (isa<ConstructorDecl>(VD))
return "init";

if (auto *mod = dyn_cast<ModuleDecl>(VD)) {
if (mod->isStdlibModule())
return "swift";
Expand Down Expand Up @@ -220,6 +224,11 @@ swift::cxx_translation::getDeclRepresentation(const ValueDecl *VD) {
genericSignature = AFD->getGenericSignature().getCanonicalSignature();
}
if (const auto *typeDecl = dyn_cast<NominalTypeDecl>(VD)) {
if (isa<ProtocolDecl>(typeDecl))
return {Unsupported, UnrepresentableProtocol};
// Swift's consume semantics are not yet supported in C++.
if (typeDecl->isMoveOnly())
return {Unsupported, UnrepresentableMoveOnly};
if (typeDecl->isGeneric()) {
if (isa<ClassDecl>(VD))
return {Unsupported, UnrepresentableGeneric};
Expand Down Expand Up @@ -283,3 +292,42 @@ bool swift::cxx_translation::isVisibleToCxx(const ValueDecl *VD,
}
return false;
}

Diagnostic
swift::cxx_translation::diagnoseRepresenationError(RepresentationError error,
ValueDecl *vd) {
switch (error) {
case UnrepresentableObjC:
return Diagnostic(diag::expose_unsupported_objc_decl_to_cxx,
vd->getDescriptiveKind(), vd);
case UnrepresentableAsync:
return Diagnostic(diag::expose_unsupported_async_decl_to_cxx,
vd->getDescriptiveKind(), vd);
case UnrepresentableIsolatedInActor:
return Diagnostic(diag::expose_unsupported_actor_isolated_to_cxx,
vd->getDescriptiveKind(), vd);
case UnrepresentableRequiresClientEmission:
return Diagnostic(diag::expose_unsupported_client_emission_to_cxx,
vd->getDescriptiveKind(), vd);
case UnrepresentableGeneric:
return Diagnostic(diag::expose_generic_decl_to_cxx,
vd->getDescriptiveKind(), vd);
case UnrepresentableGenericRequirements:
return Diagnostic(diag::expose_generic_requirement_to_cxx,
vd->getDescriptiveKind(), vd);
case UnrepresentableThrows:
return Diagnostic(diag::expose_throwing_to_cxx, vd->getDescriptiveKind(),
vd);
case UnrepresentableIndirectEnum:
return Diagnostic(diag::expose_indirect_enum_cxx, vd);
case UnrepresentableEnumCaseType:
return Diagnostic(diag::expose_enum_case_type_to_cxx, vd);
case UnrepresentableEnumCaseTuple:
return Diagnostic(diag::expose_enum_case_tuple_to_cxx, vd);
case UnrepresentableProtocol:
return Diagnostic(diag::expose_protocol_to_cxx_unsupported, vd);
case UnrepresentableMoveOnly:
return Diagnostic(diag::expose_move_only_to_cxx, vd->getDescriptiveKind(),
vd);
}
}
81 changes: 80 additions & 1 deletion lib/PrintAsClang/DeclAndTypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ static bool looksLikeInitMethod(ObjCSelector selector) {
return !(firstPiece.size() > 4 && clang::isLowercase(firstPiece[4]));
}

// Enters and leaves a new lexical scope when emitting
// members of a Swift type.
struct CxxEmissionScopeRAII {
DeclAndTypePrinter &printer;
CxxDeclEmissionScope &prevScope;
CxxDeclEmissionScope scope;

CxxEmissionScopeRAII(DeclAndTypePrinter &printer)
: printer(printer), prevScope(printer.getCxxDeclEmissionScope()) {
printer.setCxxDeclEmissionScope(scope);
}
~CxxEmissionScopeRAII() { printer.setCxxDeclEmissionScope(prevScope); }
};

class DeclAndTypePrinter::Implementation
: private DeclVisitor<DeclAndTypePrinter::Implementation>,
private TypeVisitor<DeclAndTypePrinter::Implementation, void,
Expand Down Expand Up @@ -188,6 +202,12 @@ class DeclAndTypePrinter::Implementation
}

private:
void recordEmittedDeclInCurrentCxxLexicalScope(const ValueDecl *vd) {
assert(outputLang == OutputLanguageMode::Cxx);
owningPrinter.getCxxDeclEmissionScope().emittedDeclarationNames.insert(
cxx_translation::getNameForCxx(vd));
}

/// Prints a protocol adoption list: <code>&lt;NSCoding, NSCopying&gt;</code>
///
/// This method filters out non-ObjC protocols.
Expand Down Expand Up @@ -215,6 +235,11 @@ class DeclAndTypePrinter::Implementation
/// Prints the members of a class, extension, or protocol.
template <bool AllowDelayed = false, typename R>
void printMembers(R &&members) {
CxxEmissionScopeRAII cxxScopeRAII(owningPrinter);
// FIXME: Actually track emitted members in nested
// lexical scopes.
// FIXME: Emit unavailable C++ decls for not emitted
// nested members.
bool protocolMembersOptional = false;
for (const Decl *member : members) {
auto VD = dyn_cast<ValueDecl>(member);
Expand Down Expand Up @@ -301,6 +326,7 @@ class DeclAndTypePrinter::Implementation
ClangValueTypePrinter::forwardDeclType(os, CD, owningPrinter);
ClangClassTypePrinter(os).printClassTypeDecl(
CD, [&]() { printMembers(CD->getMembers()); }, owningPrinter);
recordEmittedDeclInCurrentCxxLexicalScope(CD);
return;
}

Expand Down Expand Up @@ -363,6 +389,7 @@ class DeclAndTypePrinter::Implementation
}
},
owningPrinter);
recordEmittedDeclInCurrentCxxLexicalScope(SD);
}

void visitExtensionDecl(ExtensionDecl *ED) {
Expand Down Expand Up @@ -843,6 +870,7 @@ class DeclAndTypePrinter::Implementation
printMembers(ED->getMembers());
},
owningPrinter);
recordEmittedDeclInCurrentCxxLexicalScope(ED);
}

void visitEnumDecl(EnumDecl *ED) {
Expand Down Expand Up @@ -984,6 +1012,39 @@ class DeclAndTypePrinter::Implementation
sel.getSelectorPieces().front().str() == "init";
}

/// Returns true if the given function overload is safe to emit in the current
/// C++ lexical scope.
bool canPrintOverloadOfFunction(const AbstractFunctionDecl *funcDecl) const {
assert(outputLang == OutputLanguageMode::Cxx);
auto &overloads =
owningPrinter.getCxxDeclEmissionScope().emittedFunctionOverloads;
auto cxxName = cxx_translation::getNameForCxx(funcDecl);
auto overloadIt = overloads.find(cxxName);
if (overloadIt == overloads.end()) {
overloads.insert(std::make_pair(
cxxName,
llvm::SmallVector<const AbstractFunctionDecl *>({funcDecl})));
return true;
}
auto selfArity =
funcDecl->getParameters() ? funcDecl->getParameters()->size() : 0;
for (const auto *overload : overloadIt->second) {
auto arity =
overload->getParameters() ? overload->getParameters()->size() : 0;
// Avoid printing out an overload with the same and arity, as that might
// be an ambiguous overload on the C++ side.
// FIXME: we should take types into account, not all overloads with the
// same arity are ambiguous in C++.
if (selfArity == arity) {
owningPrinter.getCxxDeclEmissionScope()
.additionalUnrepresentableDeclarations.push_back(funcDecl);
return false;
}
}
overloadIt->second.push_back(funcDecl);
return true;
}

void printAbstractFunctionAsMethod(AbstractFunctionDecl *AFD,
bool isClassMethod,
bool isNSUIntegerSubscript = false,
Expand Down Expand Up @@ -1023,6 +1084,12 @@ class DeclAndTypePrinter::Implementation
if (!dispatchInfo)
return;
}
// FIXME: handle getters/setters ambiguities here too.
if (!isa<AccessorDecl>(AFD)) {
if (!canPrintOverloadOfFunction(AFD))
return;
}

owningPrinter.prologueOS << cFuncPrologueOS.str();

printDocumentationComment(AFD);
Expand Down Expand Up @@ -1746,10 +1813,16 @@ class DeclAndTypePrinter::Implementation
llvm::raw_string_ostream cFuncPrologueOS(cFuncDecl);
auto funcABI = Implementation(cFuncPrologueOS, owningPrinter, outputLang)
.printSwiftABIFunctionSignatureAsCxxFunction(FD);
if (!funcABI)
if (!funcABI) {
owningPrinter.getCxxDeclEmissionScope()
.additionalUnrepresentableDeclarations.push_back(FD);
return;
}
if (!canPrintOverloadOfFunction(FD))
return;
owningPrinter.prologueOS << cFuncPrologueOS.str();
printAbstractFunctionAsCxxFunctionThunk(FD, *funcABI);
recordEmittedDeclInCurrentCxxLexicalScope(FD);
return;
}
if (FD->getDeclContext()->isTypeContext())
Expand Down Expand Up @@ -2835,6 +2908,12 @@ bool DeclAndTypePrinter::shouldInclude(const ValueDecl *VD) {
!excludeForObjCImplementation(VD);
}

bool DeclAndTypePrinter::isVisible(const ValueDecl *vd) const {
return outputLang == OutputLanguageMode::Cxx
? cxx_translation::isVisibleToCxx(vd, minRequiredAccess)
: isVisibleToObjC(vd, minRequiredAccess);
}

void DeclAndTypePrinter::print(const Decl *D) {
getImpl().print(D);
}
Expand Down
31 changes: 29 additions & 2 deletions lib/PrintAsClang/DeclAndTypePrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ class PrimitiveTypeMapping;
class ValueDecl;
class SwiftToClangInteropContext;

/// Tracks which C++ declarations have been emitted in a lexical
/// C++ scope.
struct CxxDeclEmissionScope {
/// Additional Swift declarations that are unrepresentable in C++.
std::vector<const ValueDecl *> additionalUnrepresentableDeclarations;
/// Records the C++ declaration names already emitted in this lexical scope.
llvm::StringSet<> emittedDeclarationNames;
/// Records the names of the function overloads already emitted in this
/// lexical scope.
llvm::StringMap<llvm::SmallVector<const AbstractFunctionDecl *, 2>>
emittedFunctionOverloads;
};

/// Responsible for printing a Swift Decl or Type in Objective-C, to be
/// included in a Swift module's ObjC compatibility header.
class DeclAndTypePrinter {
Expand All @@ -45,6 +58,7 @@ class DeclAndTypePrinter {
raw_ostream &prologueOS;
raw_ostream &outOfLineDefinitionsOS;
const DelayedMemberSet &delayedMembers;
CxxDeclEmissionScope *cxxDeclEmissionScope;
PrimitiveTypeMapping &typeMapping;
SwiftToClangInteropContext &interopContext;
AccessLevel minRequiredAccess;
Expand All @@ -63,26 +77,39 @@ class DeclAndTypePrinter {
DeclAndTypePrinter(ModuleDecl &mod, raw_ostream &out, raw_ostream &prologueOS,
raw_ostream &outOfLineDefinitionsOS,
DelayedMemberSet &delayed,
CxxDeclEmissionScope &topLevelEmissionScope,
PrimitiveTypeMapping &typeMapping,
SwiftToClangInteropContext &interopContext,
AccessLevel access, bool requiresExposedAttribute,
llvm::StringSet<> &exposedModules,
OutputLanguageMode outputLang)
: M(mod), os(out), prologueOS(prologueOS),
outOfLineDefinitionsOS(outOfLineDefinitionsOS), delayedMembers(delayed),
typeMapping(typeMapping), interopContext(interopContext),
minRequiredAccess(access),
cxxDeclEmissionScope(&topLevelEmissionScope), typeMapping(typeMapping),
interopContext(interopContext), minRequiredAccess(access),
requiresExposedAttribute(requiresExposedAttribute),
exposedModules(exposedModules), outputLang(outputLang) {}

PrimitiveTypeMapping &getTypeMapping() { return typeMapping; }

SwiftToClangInteropContext &getInteropContext() { return interopContext; }

CxxDeclEmissionScope &getCxxDeclEmissionScope() {
return *cxxDeclEmissionScope;
}

void setCxxDeclEmissionScope(CxxDeclEmissionScope &scope) {
cxxDeclEmissionScope = &scope;
}

/// Returns true if \p VD should be included in a compatibility header for
/// the options the printer was constructed with.
bool shouldInclude(const ValueDecl *VD);

/// Returns true if \p vd is visible given the current access level and thus
/// can be included in the generated header.
bool isVisible(const ValueDecl *vd) const;

void print(const Decl *D);
void print(Type ty);

Expand Down
Loading