Skip to content

Commit cf7cbae

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 cad2c7b commit cf7cbae

File tree

53 files changed

+1615
-56
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

+1615
-56
lines changed

include/swift/AST/ASTContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ class ASTContext final {
355355
llvm::SmallPtrSet<DerivativeAttr *, 1>>
356356
DerivativeAttrs;
357357

358+
/// The Swift module currently being compiled.
359+
ModuleDecl *MainModule = nullptr;
360+
358361
private:
359362
/// The current generation number, which reflects the number of
360363
/// 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
@@ -630,7 +630,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
630630
HasAnyUnavailableValues : 1
631631
);
632632

633-
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1,
633+
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1,
634634
/// If the module is compiled as static library.
635635
StaticLibrary : 1,
636636

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

677677
/// Whether this module has been compiled with comprehensive checking for
678678
/// concurrency, e.g., Sendable checking.
679-
IsConcurrencyChecked : 1
679+
IsConcurrencyChecked : 1,
680+
681+
/// If the map from @objc provided name to top level swift::Decl in this
682+
/// module is populated
683+
ObjCNameLookupCachePopulated : 1
680684
);
681685

682686
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
@@ -246,8 +246,27 @@ NOTE(invoked_func_not_imported, none, "function %0 unavailable (cannot import)",
246246
NOTE(record_method_not_imported, none, "method %0 unavailable (cannot import)", (const clang::NamedDecl*))
247247
NOTE(objc_property_not_imported, none, "property %0 unavailable (cannot import)", (const clang::NamedDecl*))
248248

249-
NOTE(forward_declared_interface_label, none, "interface %0 forward declared here", (const clang::NamedDecl*))
250-
NOTE(forward_declared_protocol_label, none, "protocol %0 forward declared here", (const clang::NamedDecl*))
249+
NOTE(placeholder_for_forward_declared_interface_member_access_failure, none,
250+
"class '%0' will be imported as an opaque placeholder class and may be "
251+
"missing members; import the definition to access the complete "
252+
"interface", (StringRef))
253+
NOTE(placeholder_for_forward_declared_protocol_member_access_failure, none,
254+
"protocol '%0' will be imported as an opaque placeholder protocol "
255+
"and may be missing members; import the definition to access the "
256+
"complete protocol", (StringRef))
257+
NOTE(forward_declared_interface_label, none,
258+
"interface %0 forward declared here", (const clang::NamedDecl*))
259+
NOTE(forward_declared_protocol_label, none,
260+
"protocol %0 forward declared here", (const clang::NamedDecl*))
261+
262+
NOTE(forward_declared_interface_clashes_with_imported_objc_Swift_interface, none,
263+
"interface %0 is incomplete and cannot be imported as a stub; "
264+
"its name conflicts with a %1 in module %2",
265+
(const clang::NamedDecl*, StringRef, StringRef))
266+
NOTE(forward_declared_protocol_clashes_with_imported_objc_Swift_protocol, none,
267+
"protocol %0 is incomplete and cannot be imported as a stub; "
268+
"its name conflicts with a %1 in module %2",
269+
(const clang::NamedDecl*, StringRef, StringRef))
251270

252271
#define UNDEFINE_DIAGNOSTIC_MACROS
253272
#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;
@@ -633,6 +635,14 @@ class ModuleDecl
633635
Bits.ModuleDecl.IsConcurrencyChecked = value;
634636
}
635637

638+
bool isObjCNameLookupCachePopulated() const {
639+
return Bits.ModuleDecl.ObjCNameLookupCachePopulated;
640+
}
641+
642+
void setIsObjCNameLookupCachePopulated(bool value) {
643+
Bits.ModuleDecl.ObjCNameLookupCachePopulated = value;
644+
}
645+
636646
/// For the main module, retrieves the list of primary source files being
637647
/// compiled, that is, the files we're generating code for.
638648
ArrayRef<SourceFile *> getPrimarySourceFiles() const;
@@ -670,6 +680,24 @@ class ModuleDecl
670680
VisibleDeclConsumer &Consumer,
671681
NLKind LookupKind) const;
672682

683+
private:
684+
void populateObjCNameLookupCache();
685+
686+
public:
687+
/// Finds top-levels decls of this module by @objc provided name.
688+
/// Decls that have no @objc attribute are not considered.
689+
///
690+
/// This does a simple local lookup, not recursively looking through imports.
691+
/// The order of the results is not guaranteed to be meaningful.
692+
///
693+
/// \param Results Vector collecting the decls.
694+
///
695+
/// \param name The @objc simple name to look for. Declarations with matching
696+
/// name and "anonymous" @objc attribute, as well a matching named @objc
697+
/// attribute will be added to Results.
698+
void lookupTopLevelDeclsByObjCName(SmallVectorImpl<Decl *> &Results,
699+
DeclName name);
700+
673701
/// This is a hack for 'main' file parsing and the integrated REPL.
674702
///
675703
/// 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
@@ -308,6 +308,10 @@ def enable_experimental_eager_clang_module_diagnostics :
308308
Flag<["-"], "enable-experimental-eager-clang-module-diagnostics">,
309309
HelpText<"Enable experimental eager diagnostics reporting on the importability of all referenced C, C++, and Objective-C libraries">;
310310

311+
def enable_import_objc_forward_declarations :
312+
Flag<["-"], "enable-import-objc-forward-declarations">,
313+
HelpText<"Attempt to import Objective-C forward declarations">;
314+
311315
def enable_experimental_pairwise_build_block :
312316
Flag<["-"], "enable-experimental-pairwise-build-block">,
313317
HelpText<"Enable experimental pairwise 'buildBlock' for result builders">;

lib/AST/Module.cpp

Lines changed: 58 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 {
@@ -1068,6 +1069,60 @@ void ModuleDecl::getTopLevelDeclsWhereAttributesMatch(
10681069
FORWARD(getTopLevelDeclsWhereAttributesMatch, (Results, matchAttributes));
10691070
}
10701071

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

3283+
setIsObjCNameLookupCachePopulated(false);
3284+
ObjCNameLookupCache.clear();
3285+
32283286
if (!Cache)
32293287
return;
32303288

lib/ClangImporter/ClangImporter.cpp

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

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

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

0 commit comments

Comments
 (0)