Skip to content

Commit 6faffd2

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 cfad960 commit 6faffd2

File tree

53 files changed

+1620
-57
lines changed

Some content is hidden

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

53 files changed

+1620
-57
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: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,27 @@ 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*))
230+
231+
NOTE(forward_declared_interface_clashes_with_imported_objc_Swift_interface, none,
232+
"interface %0 is incomplete and cannot be imported as a stub; "
233+
"its name conflicts with a %1 in module %2",
234+
(const clang::NamedDecl*, StringRef, StringRef))
235+
NOTE(forward_declared_protocol_clashes_with_imported_objc_Swift_protocol, none,
236+
"protocol %0 is incomplete and cannot be imported as a stub; "
237+
"its name conflicts with a %1 in module %2",
238+
(const clang::NamedDecl*, StringRef, StringRef))
220239

221240
#define UNDEFINE_DIAGNOSTIC_MACROS
222241
#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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx,
491491
Bits.ModuleDecl.HasIncrementalInfo = 0;
492492
Bits.ModuleDecl.HasHermeticSealAtLink = 0;
493493
Bits.ModuleDecl.IsConcurrencyChecked = 0;
494+
Bits.ModuleDecl.ObjCNameLookupCachePopulated = 0;
494495
}
495496

496497
ImplicitImportList ModuleDecl::getImplicitImports() const {
@@ -1055,6 +1056,62 @@ void ModuleDecl::getTopLevelDeclsWhereAttributesMatch(
10551056
FORWARD(getTopLevelDeclsWhereAttributesMatch, (Results, matchAttributes));
10561057
}
10571058

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

3248+
setIsObjCNameLookupCachePopulated(false);
3249+
ObjCNameLookupCache.clear();
3250+
31913251
if (!Cache)
31923252
return;
31933253

lib/ClangImporter/ClangImporter.cpp

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

62016201
void ClangImporter::diagnoseMemberValue(const DeclName &name,
62026202
const Type &baseType) {
6203-
if (!baseType->getAnyNominal())
6203+
6204+
// Return early for any type that namelookup::extractDirectlyReferencedNominalTypes
6205+
// does not know how to handle.
6206+
if (!(baseType->getAnyNominal() ||
6207+
baseType->is<ExistentialType>() ||
6208+
baseType->is<UnboundGenericType>() ||
6209+
baseType->is<ArchetypeType>() ||
6210+
baseType->is<ProtocolCompositionType>() ||
6211+
baseType->is<TupleType>()))
62046212
return;
62056213

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

0 commit comments

Comments
 (0)