Skip to content

Commit 65ca592

Browse files
committed
Implement importing of forward declared objc protocols and interfaces
This modifies the ClangImporter to introduce an opaque placeholder representation for forward declared Objective-C interfaces and protocols when imported into Swift. In the compiler, the new functionality is hidden behind a frontend flag -enable-import-objc-forward-declarations, and is on by default for language mode >6. The feature is disabled entirely in LLDB expression evaluation / Swift REPL, regardless of language version.
1 parent 7016679 commit 65ca592

File tree

51 files changed

+1285
-52
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1285
-52
lines changed

include/swift/AST/ASTContext.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ class ASTContext final {
353353
llvm::SmallPtrSet<DerivativeAttr *, 1>>
354354
DerivativeAttrs;
355355

356+
ModuleDecl *MainModule = nullptr;
357+
356358
private:
357359
/// The current generation number, which reflects the number of
358360
/// times that external modules have been loaded.

include/swift/AST/Decl.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
633633
HasAnyUnavailableValues : 1
634634
);
635635

636-
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1,
636+
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1,
637637
/// If the module is compiled as static library.
638638
StaticLibrary : 1,
639639

@@ -679,7 +679,11 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
679679

680680
/// Whether this module has been compiled with comprehensive checking for
681681
/// concurrency, e.g., Sendable checking.
682-
IsConcurrencyChecked : 1
682+
IsConcurrencyChecked : 1,
683+
684+
/// If the map from @objc provided name to top level swift::Decl in this
685+
/// module is populated
686+
ObjCNameLookupCachePopulated : 1
683687
);
684688

685689
SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2,

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,18 @@ NOTE(invoked_func_not_imported, none, "function %0 not imported", (const clang::
215215
NOTE(record_method_not_imported, none, "method %0 not imported", (const clang::NamedDecl*))
216216
NOTE(objc_property_not_imported, none, "property %0 not imported", (const clang::NamedDecl*))
217217

218-
NOTE(forward_declared_interface_label, none, "interface %0 forward declared here", (const clang::NamedDecl*))
219-
NOTE(forward_declared_protocol_label, none, "protocol %0 forward declared here", (const clang::NamedDecl*))
218+
NOTE(placeholder_for_forward_declared_interface_member_access_failure, none,
219+
"class '%0' will be imported as an opaque placeholder class and may be "
220+
"missing members; import the definition to access the complete "
221+
"interface", (StringRef))
222+
NOTE(placeholder_for_forward_declared_protocol_member_access_failure, none,
223+
"protocol '%0' will be imported as an opaque placeholder protocol "
224+
"and may be missing members; import the definition to access the "
225+
"complete protocol", (StringRef))
226+
NOTE(forward_declared_interface_label, none,
227+
"interface %0 forward declared here", (const clang::NamedDecl*))
228+
NOTE(forward_declared_protocol_label, none,
229+
"protocol %0 forward declared here", (const clang::NamedDecl*))
220230

221231
#define UNDEFINE_DIAGNOSTIC_MACROS
222232
#include "DefineDiagnosticMacros.h"

include/swift/AST/Module.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ class ModuleDecl
246246
llvm::SmallDenseMap<Identifier, SmallVector<OverlayFile *, 1>>
247247
declaredCrossImports;
248248

249+
llvm::DenseMap<Identifier, SmallVector<Decl *, 2>> ObjCNameLookupCache;
250+
249251
/// A description of what should be implicitly imported by each file of this
250252
/// module.
251253
const ImplicitImportInfo ImportInfo;
@@ -629,6 +631,14 @@ class ModuleDecl
629631
Bits.ModuleDecl.IsConcurrencyChecked = value;
630632
}
631633

634+
bool isObjCNameLookupCachePopulated() const {
635+
return Bits.ModuleDecl.ObjCNameLookupCachePopulated;
636+
}
637+
638+
void setIsObjCNameLookupCachePopulated(bool value) {
639+
Bits.ModuleDecl.ObjCNameLookupCachePopulated = value;
640+
}
641+
632642
/// For the main module, retrieves the list of primary source files being
633643
/// compiled, that is, the files we're generating code for.
634644
ArrayRef<SourceFile *> getPrimarySourceFiles() const;
@@ -666,6 +676,24 @@ class ModuleDecl
666676
VisibleDeclConsumer &Consumer,
667677
NLKind LookupKind) const;
668678

679+
private:
680+
void populateObjCNameLookupCache();
681+
682+
public:
683+
/// Finds top-levels decls of this module by @objc provided name.
684+
/// Decls that have no @objc attribute are not considered.
685+
///
686+
/// This does a simple local lookup, not recursively looking through imports.
687+
/// The order of the results is not guaranteed to be meaningful.
688+
///
689+
/// \param Results Vector collecting the decls.
690+
///
691+
/// \param name The @objc simple name to look for. Declarations with matching
692+
/// name and "anonymous" @objc attribute, as well a matching named @objc
693+
/// attribute will be added to Results.
694+
void lookupTopLevelDeclsByObjCName(SmallVectorImpl<Decl *> &Results,
695+
DeclName name);
696+
669697
/// This is a hack for 'main' file parsing and the integrated REPL.
670698
///
671699
/// FIXME: Refactor main file parsing to not pump the parser incrementally.

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ def enable_experimental_eager_clang_module_diagnostics :
304304
Flag<["-"], "enable-experimental-eager-clang-module-diagnostics">,
305305
HelpText<"Enable experimental eager diagnostics reporting on the importability of all referenced C, C++, and Objective-C libraries">;
306306

307+
def enable_import_objc_forward_declarations :
308+
Flag<["-"], "enable-import-objc-forward-declarations">,
309+
HelpText<"Attempt to import Objective-C forward declarations">;
310+
307311
def enable_experimental_pairwise_build_block :
308312
Flag<["-"], "enable-experimental-pairwise-build-block">,
309313
HelpText<"Enable experimental pairwise 'buildBlock' for result builders">;

lib/AST/Module.cpp

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "swift/Basic/Compiler.h"
4444
#include "swift/Basic/SourceManager.h"
4545
#include "swift/Basic/Statistic.h"
46+
#include "swift/ClangImporter/ClangModule.h"
4647
#include "swift/Demangling/ManglingMacros.h"
4748
#include "swift/Parse/Token.h"
4849
#include "swift/Strings.h"
@@ -57,8 +58,8 @@
5758
#include "llvm/Support/MemoryBuffer.h"
5859
#include "llvm/Support/Path.h"
5960
#include "llvm/Support/SaveAndRestore.h"
60-
#include "llvm/Support/raw_ostream.h"
6161
#include "llvm/Support/YAMLTraits.h"
62+
#include "llvm/Support/raw_ostream.h"
6263

6364
using namespace swift;
6465

@@ -1052,6 +1053,62 @@ void ModuleDecl::getTopLevelDeclsWhereAttributesMatch(
10521053
FORWARD(getTopLevelDeclsWhereAttributesMatch, (Results, matchAttributes));
10531054
}
10541055

1056+
void ModuleDecl::lookupTopLevelDeclsByObjCName(SmallVectorImpl<Decl *> &Results,
1057+
DeclName name) {
1058+
if (!isObjCNameLookupCachePopulated())
1059+
populateObjCNameLookupCache();
1060+
1061+
// A top level decl can't be special anyways
1062+
if (name.isSpecial())
1063+
return;
1064+
1065+
auto resultsForFileUnit = ObjCNameLookupCache.find(name.getBaseIdentifier());
1066+
if (resultsForFileUnit == ObjCNameLookupCache.end())
1067+
return;
1068+
1069+
Results.append(resultsForFileUnit->second.begin(),
1070+
resultsForFileUnit->second.end());
1071+
}
1072+
1073+
void ModuleDecl::populateObjCNameLookupCache() {
1074+
SmallVector<Decl *> topLevelObjCExposedDeclsInFileUnit;
1075+
auto hasObjCAttrNamePredicate = [](const DeclAttributes &attrs) -> bool {
1076+
return attrs.hasAttribute<ObjCAttr>();
1077+
};
1078+
1079+
for (FileUnit *file : getFiles()) {
1080+
file->getTopLevelDeclsWhereAttributesMatch(
1081+
topLevelObjCExposedDeclsInFileUnit, hasObjCAttrNamePredicate);
1082+
if (auto *synth = file->getSynthesizedFile()) {
1083+
synth->getTopLevelDeclsWhereAttributesMatch(
1084+
topLevelObjCExposedDeclsInFileUnit, hasObjCAttrNamePredicate);
1085+
}
1086+
}
1087+
1088+
for (Decl *decl : topLevelObjCExposedDeclsInFileUnit) {
1089+
if (ValueDecl *VD = dyn_cast<ValueDecl>(decl)) {
1090+
if (VD->hasName()) {
1091+
const auto &declObjCAttribute = VD->getAttrs().getAttribute<ObjCAttr>();
1092+
// No top level decl (class, protocol, extension etc.) is allowed to have a
1093+
// compound name, @objc provided or otherwise. Global functions are allowed to
1094+
// have compound names, but not allowed to have @objc attributes. Thus we
1095+
// are sure to not hit asserts getting the simple name.
1096+
//
1097+
// Similarly, init, dealloc and subscript (the special names) can't be top
1098+
// level decls, so we won't hit asserts getting the base identifier out of the
1099+
// value decl.
1100+
const Identifier &declObjCName =
1101+
declObjCAttribute->hasName()
1102+
? declObjCAttribute->getName()->getSimpleName()
1103+
: VD->getName().getBaseIdentifier();
1104+
ObjCNameLookupCache[declObjCName].push_back(decl);
1105+
}
1106+
}
1107+
}
1108+
1109+
setIsObjCNameLookupCachePopulated(true);
1110+
}
1111+
10551112
void SourceFile::getTopLevelDecls(SmallVectorImpl<Decl*> &Results) const {
10561113
auto decls = getTopLevelDecls();
10571114
Results.append(decls.begin(), decls.end());

lib/ClangImporter/ClangImporter.cpp

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6206,7 +6206,15 @@ void ClangImporter::diagnoseTopLevelValue(const DeclName &name) {
62066206

62076207
void ClangImporter::diagnoseMemberValue(const DeclName &name,
62086208
const Type &baseType) {
6209-
if (!baseType->getAnyNominal())
6209+
6210+
// Return early for any type that namelookup::extractDirectlyReferencedNominalTypes
6211+
// does not know how to handle.
6212+
if (!(baseType->getAnyNominal() ||
6213+
baseType->is<ExistentialType>() ||
6214+
baseType->is<UnboundGenericType>() ||
6215+
baseType->is<ArchetypeType>() ||
6216+
baseType->is<ProtocolCompositionType>() ||
6217+
baseType->is<TupleType>()))
62106218
return;
62116219

62126220
SmallVector<NominalTypeDecl *, 4> nominalTypesToLookInto;
@@ -6218,6 +6226,45 @@ void ClangImporter::diagnoseMemberValue(const DeclName &name,
62186226
Impl.diagnoseMemberValue(name,
62196227
cast<clang::DeclContext>(clangContainerDecl));
62206228
}
6229+
6230+
if (Impl.ImportForwardDeclarations) {
6231+
const clang::Decl *clangContainerDecl = containerDecl->getClangDecl();
6232+
if (const clang::ObjCInterfaceDecl *objCInterfaceDecl =
6233+
llvm::dyn_cast_or_null<clang::ObjCInterfaceDecl>(
6234+
clangContainerDecl)) {
6235+
if (!objCInterfaceDecl->hasDefinition()) {
6236+
// Emit a diagnostic about how the base type represents a forward
6237+
// declared ObjC interface and is in all likelihood missing members.
6238+
// We only attach this diagnostic in diagnoseMemberValue rather than
6239+
// in SwiftDeclConverter because it is only relevant when the user
6240+
// tries to access an unavailable member.
6241+
Impl.addImportDiagnostic(
6242+
objCInterfaceDecl,
6243+
Diagnostic(
6244+
diag::
6245+
placeholder_for_forward_declared_interface_member_access_failure,
6246+
objCInterfaceDecl->getName()),
6247+
objCInterfaceDecl->getSourceRange().getBegin());
6248+
// Emit any diagnostics attached to the source Clang node (ie. forward
6249+
// declaration here note)
6250+
Impl.diagnoseTargetDirectly(clangContainerDecl);
6251+
}
6252+
} else if (const clang::ObjCProtocolDecl *objCProtocolDecl =
6253+
llvm::dyn_cast_or_null<clang::ObjCProtocolDecl>(
6254+
clangContainerDecl)) {
6255+
if (!objCProtocolDecl->hasDefinition()) {
6256+
// Same as above but for protocols
6257+
Impl.addImportDiagnostic(
6258+
objCProtocolDecl,
6259+
Diagnostic(
6260+
diag::
6261+
placeholder_for_forward_declared_protocol_member_access_failure,
6262+
objCProtocolDecl->getName()),
6263+
objCProtocolDecl->getSourceRange().getBegin());
6264+
Impl.diagnoseTargetDirectly(clangContainerDecl);
6265+
}
6266+
}
6267+
}
62216268
}
62226269
}
62236270

0 commit comments

Comments
 (0)