Skip to content

Commit 86c5698

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 8d7c115 commit 86c5698

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
@@ -356,6 +356,9 @@ class ASTContext final {
356356
llvm::SmallPtrSet<DerivativeAttr *, 1>>
357357
DerivativeAttrs;
358358

359+
/// The Swift module currently being compiled.
360+
ModuleDecl *MainModule = nullptr;
361+
359362
private:
360363
/// The current generation number, which reflects the number of
361364
/// 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
@@ -275,6 +275,8 @@ class ModuleDecl
275275
llvm::SmallDenseMap<Identifier, SmallVector<OverlayFile *, 1>>
276276
declaredCrossImports;
277277

278+
llvm::DenseMap<Identifier, SmallVector<Decl *, 2>> ObjCNameLookupCache;
279+
278280
/// A description of what should be implicitly imported by each file of this
279281
/// module.
280282
const ImplicitImportInfo ImportInfo;
@@ -667,6 +669,14 @@ class ModuleDecl
667669
Bits.ModuleDecl.IsConcurrencyChecked = value;
668670
}
669671

672+
bool isObjCNameLookupCachePopulated() const {
673+
return Bits.ModuleDecl.ObjCNameLookupCachePopulated;
674+
}
675+
676+
void setIsObjCNameLookupCachePopulated(bool value) {
677+
Bits.ModuleDecl.ObjCNameLookupCachePopulated = value;
678+
}
679+
670680
/// For the main module, retrieves the list of primary source files being
671681
/// compiled, that is, the files we're generating code for.
672682
ArrayRef<SourceFile *> getPrimarySourceFiles() const;
@@ -704,6 +714,24 @@ class ModuleDecl
704714
VisibleDeclConsumer &Consumer,
705715
NLKind LookupKind) const;
706716

717+
private:
718+
void populateObjCNameLookupCache();
719+
720+
public:
721+
/// Finds top-levels decls of this module by @objc provided name.
722+
/// Decls that have no @objc attribute are not considered.
723+
///
724+
/// This does a simple local lookup, not recursively looking through imports.
725+
/// The order of the results is not guaranteed to be meaningful.
726+
///
727+
/// \param Results Vector collecting the decls.
728+
///
729+
/// \param name The @objc simple name to look for. Declarations with matching
730+
/// name and "anonymous" @objc attribute, as well a matching named @objc
731+
/// attribute will be added to Results.
732+
void lookupTopLevelDeclsByObjCName(SmallVectorImpl<Decl *> &Results,
733+
DeclName name);
734+
707735
/// This is a hack for 'main' file parsing and the integrated REPL.
708736
///
709737
/// 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
@@ -597,6 +597,7 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx,
597597
Bits.ModuleDecl.HasIncrementalInfo = 0;
598598
Bits.ModuleDecl.HasHermeticSealAtLink = 0;
599599
Bits.ModuleDecl.IsConcurrencyChecked = 0;
600+
Bits.ModuleDecl.ObjCNameLookupCachePopulated = 0;
600601
}
601602

602603
ImplicitImportList ModuleDecl::getImplicitImports() const {
@@ -1176,6 +1177,60 @@ void ModuleDecl::getTopLevelDeclsWhereAttributesMatch(
11761177
FORWARD(getTopLevelDeclsWhereAttributesMatch, (Results, matchAttributes));
11771178
}
11781179

1180+
void ModuleDecl::lookupTopLevelDeclsByObjCName(SmallVectorImpl<Decl *> &Results,
1181+
DeclName name) {
1182+
if (!isObjCNameLookupCachePopulated())
1183+
populateObjCNameLookupCache();
1184+
1185+
// A top level decl can't be special anyways
1186+
if (name.isSpecial())
1187+
return;
1188+
1189+
auto resultsForFileUnit = ObjCNameLookupCache.find(name.getBaseIdentifier());
1190+
if (resultsForFileUnit == ObjCNameLookupCache.end())
1191+
return;
1192+
1193+
Results.append(resultsForFileUnit->second.begin(),
1194+
resultsForFileUnit->second.end());
1195+
}
1196+
1197+
void ModuleDecl::populateObjCNameLookupCache() {
1198+
SmallVector<Decl *> topLevelObjCExposedDeclsInFileUnit;
1199+
auto hasObjCAttrNamePredicate = [](const DeclAttributes &attrs) -> bool {
1200+
return attrs.hasAttribute<ObjCAttr>();
1201+
};
1202+
1203+
for (FileUnit *file : getFiles()) {
1204+
file->getTopLevelDeclsWhereAttributesMatch(
1205+
topLevelObjCExposedDeclsInFileUnit, hasObjCAttrNamePredicate);
1206+
if (auto *synth = file->getSynthesizedFile()) {
1207+
synth->getTopLevelDeclsWhereAttributesMatch(
1208+
topLevelObjCExposedDeclsInFileUnit, hasObjCAttrNamePredicate);
1209+
}
1210+
}
1211+
1212+
for (Decl *decl : topLevelObjCExposedDeclsInFileUnit) {
1213+
if (ValueDecl *VD = dyn_cast<ValueDecl>(decl); VD && VD->hasName()) {
1214+
const auto &declObjCAttribute = VD->getAttrs().getAttribute<ObjCAttr>();
1215+
// No top level decl (class, protocol, extension etc.) is allowed to have a
1216+
// compound name, @objc provided or otherwise. Global functions are allowed to
1217+
// have compound names, but not allowed to have @objc attributes. Thus we
1218+
// are sure to not hit asserts getting the simple name.
1219+
//
1220+
// Similarly, init, dealloc and subscript (the special names) can't be top
1221+
// level decls, so we won't hit asserts getting the base identifier out of the
1222+
// value decl.
1223+
const Identifier &declObjCName =
1224+
declObjCAttribute->hasName()
1225+
? declObjCAttribute->getName()->getSimpleName()
1226+
: VD->getName().getBaseIdentifier();
1227+
ObjCNameLookupCache[declObjCName].push_back(decl);
1228+
}
1229+
}
1230+
1231+
setIsObjCNameLookupCachePopulated(true);
1232+
}
1233+
11791234
void SourceFile::getTopLevelDecls(SmallVectorImpl<Decl*> &Results) const {
11801235
auto decls = getTopLevelDecls();
11811236
Results.append(decls.begin(), decls.end());
@@ -3347,6 +3402,9 @@ bool SourceFile::shouldCrossImport() const {
33473402
void ModuleDecl::clearLookupCache() {
33483403
getASTContext().getImportCache().clear();
33493404

3405+
setIsObjCNameLookupCachePopulated(false);
3406+
ObjCNameLookupCache.clear();
3407+
33503408
if (!Cache)
33513409
return;
33523410

lib/ClangImporter/ClangImporter.cpp

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

62106210
void ClangImporter::diagnoseMemberValue(const DeclName &name,
62116211
const Type &baseType) {
6212-
if (!baseType->getAnyNominal())
6212+
6213+
// Return early for any type that namelookup::extractDirectlyReferencedNominalTypes
6214+
// does not know how to handle.
6215+
if (!(baseType->getAnyNominal() ||
6216+
baseType->is<ExistentialType>() ||
6217+
baseType->is<UnboundGenericType>() ||
6218+
baseType->is<ArchetypeType>() ||
6219+
baseType->is<ProtocolCompositionType>() ||
6220+
baseType->is<TupleType>()))
62136221
return;
62146222

62156223
SmallVector<NominalTypeDecl *, 4> nominalTypesToLookInto;
@@ -6221,6 +6229,41 @@ void ClangImporter::diagnoseMemberValue(const DeclName &name,
62216229
Impl.diagnoseMemberValue(name,
62226230
cast<clang::DeclContext>(clangContainerDecl));
62236231
}
6232+
6233+
if (Impl.ImportForwardDeclarations) {
6234+
const clang::Decl *clangContainerDecl = containerDecl->getClangDecl();
6235+
if (const clang::ObjCInterfaceDecl *objCInterfaceDecl =
6236+
llvm::dyn_cast_or_null<clang::ObjCInterfaceDecl>(
6237+
clangContainerDecl); objCInterfaceDecl && !objCInterfaceDecl->hasDefinition()) {
6238+
// Emit a diagnostic about how the base type represents a forward
6239+
// declared ObjC interface and is in all likelihood missing members.
6240+
// We only attach this diagnostic in diagnoseMemberValue rather than
6241+
// in SwiftDeclConverter because it is only relevant when the user
6242+
// tries to access an unavailable member.
6243+
Impl.addImportDiagnostic(
6244+
objCInterfaceDecl,
6245+
Diagnostic(
6246+
diag::
6247+
placeholder_for_forward_declared_interface_member_access_failure,
6248+
objCInterfaceDecl->getName()),
6249+
objCInterfaceDecl->getSourceRange().getBegin());
6250+
// Emit any diagnostics attached to the source Clang node (ie. forward
6251+
// declaration here note)
6252+
Impl.diagnoseTargetDirectly(clangContainerDecl);
6253+
} else if (const clang::ObjCProtocolDecl *objCProtocolDecl =
6254+
llvm::dyn_cast_or_null<clang::ObjCProtocolDecl>(
6255+
clangContainerDecl); objCProtocolDecl && !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+
}
62246267
}
62256268
}
62266269

0 commit comments

Comments
 (0)