Skip to content

Commit ce5212f

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 ce5212f

File tree

50 files changed

+1508
-46
lines changed

Some content is hidden

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

50 files changed

+1508
-46
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/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_shadows_imported_objc_Swift_interface, none,
232+
"incomplete interface %0 shadows an imported @objc Swift class from module '%1'; "
233+
"import the generated Swift header for module '%1' in this header to realize this declaration",
234+
(const clang::NamedDecl*, StringRef))
235+
NOTE(forward_declared_protocol_shadows_imported_objc_Swift_protocol, none,
236+
"incomplete protocol %0 shadows an imported @objc Swift protocol from module '%1'; "
237+
"import the generated Swift header for module '%1' in this header to realize this declaration",
238+
(const clang::NamedDecl*, StringRef))
220239

221240
#define UNDEFINE_DIAGNOSTIC_MACROS
222241
#include "DefineDiagnosticMacros.h"

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/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

lib/ClangImporter/ImportDecl.cpp

Lines changed: 136 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4244,7 +4244,9 @@ namespace {
42444244

42454245
template <typename T, typename U>
42464246
T *resolveSwiftDeclImpl(const U *decl, Identifier name,
4247-
bool hasKnownSwiftName, ModuleDecl *overlay) {
4247+
bool hasKnownSwiftName, ModuleDecl *module,
4248+
bool allowObjCMismatchFallback,
4249+
bool cacheResult) {
42484250
const auto &languageVersion =
42494251
Impl.SwiftContext.LangOpts.EffectiveLanguageVersion;
42504252

@@ -4276,7 +4278,7 @@ namespace {
42764278

42774279
// First look at Swift types with the same name.
42784280
SmallVector<ValueDecl *, 4> swiftDeclsByName;
4279-
overlay->lookupValue(name, NLKind::QualifiedLookup, swiftDeclsByName);
4281+
module->lookupValue(name, NLKind::QualifiedLookup, swiftDeclsByName);
42804282
T *found = nullptr;
42814283
for (auto result : swiftDeclsByName) {
42824284
if (auto singleResult = dyn_cast<T>(result)) {
@@ -4298,7 +4300,7 @@ namespace {
42984300
SmallVector<Decl *, 4> matchingTopLevelDecls;
42994301

43004302
// Get decls with a matching @objc attribute
4301-
overlay->getTopLevelDeclsWhereAttributesMatch(
4303+
module->getTopLevelDeclsWhereAttributesMatch(
43024304
matchingTopLevelDecls,
43034305
[&name](const DeclAttributes attrs) -> bool {
43044306
if (auto objcAttr = attrs.getAttribute<ObjCAttr>())
@@ -4317,7 +4319,7 @@ namespace {
43174319
}
43184320
}
43194321

4320-
if (!found) {
4322+
if (!found && allowObjCMismatchFallback) {
43214323
// Go back to the first list and find classes with matching Swift names
43224324
// *even if the ObjC name doesn't match.*
43234325
// This shouldn't be allowed but we need it for source compatibility;
@@ -4335,7 +4337,7 @@ namespace {
43354337
}
43364338
}
43374339

4338-
if (found)
4340+
if (found && cacheResult)
43394341
Impl.ImportedDecls[{decl->getCanonicalDecl(),
43404342
getActiveSwiftVersion()}] = found;
43414343

@@ -4346,27 +4348,81 @@ namespace {
43464348
T *resolveSwiftDecl(const U *decl, Identifier name,
43474349
bool hasKnownSwiftName, ClangModuleUnit *clangModule) {
43484350
if (auto overlay = clangModule->getOverlayModule())
4349-
return resolveSwiftDeclImpl<T>(decl, name, hasKnownSwiftName, overlay);
4351+
return resolveSwiftDeclImpl<T>(decl, name, hasKnownSwiftName, overlay,
4352+
/*allowObjCMismatchFallback*/ true, /*cacheResult*/ true);
43504353
if (clangModule == Impl.ImportedHeaderUnit) {
43514354
// Use an index-based loop because new owners can come in as we're
43524355
// iterating.
43534356
for (size_t i = 0; i < Impl.ImportedHeaderOwners.size(); ++i) {
43544357
ModuleDecl *owner = Impl.ImportedHeaderOwners[i];
4355-
if (T *result = resolveSwiftDeclImpl<T>(decl, name,
4356-
hasKnownSwiftName, owner))
4358+
if (T *result =
4359+
resolveSwiftDeclImpl<T>(decl, name, hasKnownSwiftName, owner,
4360+
/*allowObjCMismatchFallback*/ true, /*cacheResult*/ true))
43574361
return result;
43584362
}
43594363
}
43604364
return nullptr;
43614365
}
43624366

4367+
/// Given some forward declared Objective-C type `@class Foo` or `@protocol Bar`, this
4368+
/// method attempts to find a matching @objc annotated Swift declaration `@objc class Foo {}`
4369+
/// or `@objc protocol Bar {}`, in an imported Swift module. That is if the Clang node is in
4370+
/// a Clang module, the Swift overlay for that module does not count as "non-local". Similarly,
4371+
/// if the Clang node is in a bridging header, any owners of that header also do not count as
4372+
/// "non-local". This is intended to find @objc exposed Swift declarations in a different module
4373+
/// that share the name as the forward declaration.
4374+
///
4375+
/// Pass \p hasKnownSwiftName when the Clang declaration is annotated with NS_SWIFT_NAME or similar,
4376+
/// such that the @objc provided name is known.
4377+
template <typename T, typename U>
4378+
T* hasNonLocalNativeSwiftDecl(U *decl, Identifier name, bool hasKnownSwiftName) {
4379+
assert(!decl->hasDefinition() && "This method is only intended to be used on incomplete Clang types");
4380+
4381+
// We intentionally do not consider if the declaration has a clang::ExternalSourceSymbolAttr
4382+
// attribute, since we can't know if the corresponding Swift definition is "local" (ie.
4383+
// in the overlay or bridging header owner) or not.
4384+
4385+
// Check first if the Swift definition is "local"
4386+
auto owningClangModule = Impl.getClangModuleForDecl(decl, /*allowForwardDeclaration*/ true);
4387+
if (owningClangModule && resolveSwiftDecl<T>(decl, name, hasKnownSwiftName, owningClangModule))
4388+
return nullptr;
4389+
4390+
// If not, check all imported Swift modules for a definition
4391+
if (auto mainModule = Impl.SwiftContext.MainModule) {
4392+
llvm::SmallVector<ValueDecl *> results;
4393+
llvm::SmallVector<ImportedModule> importedModules;
4394+
4395+
ModuleDecl::ImportFilter moduleImportFilter = ModuleDecl::ImportFilterKind::Default;
4396+
moduleImportFilter |= ModuleDecl::ImportFilterKind::Default;
4397+
moduleImportFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly;
4398+
moduleImportFilter |= ModuleDecl::ImportFilterKind::SPIAccessControl;
4399+
moduleImportFilter |= ModuleDecl::ImportFilterKind::SPIOnly;
4400+
moduleImportFilter |= ModuleDecl::ImportFilterKind::ShadowedByCrossImportOverlay;
4401+
4402+
mainModule->getImportedModules(importedModules, moduleImportFilter);
4403+
4404+
for (auto &import : importedModules) {
4405+
if (import.importedModule->isNonSwiftModule())
4406+
continue;
4407+
4408+
if (T *result = resolveSwiftDeclImpl<T>(
4409+
decl, name, hasKnownSwiftName, import.importedModule,
4410+
/*allowObjCMismatchFallback*/ false, /*cacheResult*/ false))
4411+
return result;
4412+
}
4413+
}
4414+
4415+
return nullptr;
4416+
}
4417+
43634418
template <typename T, typename U>
43644419
bool hasNativeSwiftDecl(const U *decl, Identifier name,
4365-
const DeclContext *dc, T *&swiftDecl) {
4420+
const DeclContext *dc, T *&swiftDecl,
4421+
bool hasKnownSwiftName = true) {
43664422
if (!importer::hasNativeSwiftDecl(decl))
43674423
return false;
43684424
auto wrapperUnit = cast<ClangModuleUnit>(dc->getModuleScopeContext());
4369-
swiftDecl = resolveSwiftDecl<T>(decl, name, /*hasCustomSwiftName=*/true,
4425+
swiftDecl = resolveSwiftDecl<T>(decl, name, hasKnownSwiftName,
43704426
wrapperUnit);
43714427
return true;
43724428
}
@@ -4413,6 +4469,39 @@ namespace {
44134469
decl, Diagnostic(diag::forward_declared_protocol_label, decl),
44144470
decl->getSourceRange().getBegin());
44154471

4472+
if (Impl.ImportForwardDeclarations) {
4473+
if (auto native = hasNonLocalNativeSwiftDecl<ProtocolDecl>(decl, name, hasKnownSwiftName)) {
4474+
const ModuleDecl* moduleForNativeDecl = native->getParentModule();
4475+
assert(moduleForNativeDecl);
4476+
Impl.addImportDiagnostic(decl, Diagnostic(diag::forward_declared_protocol_shadows_imported_objc_Swift_protocol,
4477+
decl, moduleForNativeDecl->getNameStr()),
4478+
decl->getSourceRange().getBegin());
4479+
} else {
4480+
auto result = Impl.createDeclWithClangNode<ProtocolDecl>(
4481+
decl, AccessLevel::Public,
4482+
Impl.getClangModuleForDecl(decl->getCanonicalDecl(),
4483+
/*allowForwardDeclaration=*/true),
4484+
Impl.importSourceLoc(decl->getBeginLoc()),
4485+
Impl.importSourceLoc(decl->getLocation()), name,
4486+
ArrayRef<PrimaryAssociatedTypeName>(), None,
4487+
/*TrailingWhere=*/nullptr);
4488+
4489+
Impl.ImportedDecls[{decl->getCanonicalDecl(), getVersion()}] = result;
4490+
result->setAddedImplicitInitializers(); // suppress all initializers
4491+
addObjCAttribute(result,
4492+
Impl.importIdentifier(decl->getIdentifier()));
4493+
result->setImplicit();
4494+
auto attr = AvailableAttr::createPlatformAgnostic(
4495+
Impl.SwiftContext,
4496+
"This Objective-C protocol has only been forward-declared; "
4497+
"import its owning module to use it");
4498+
result->getAttrs().add(attr);
4499+
result->getAttrs().add(new (Impl.SwiftContext)
4500+
ForbidSerializingReferenceAttr(true));
4501+
return result;
4502+
}
4503+
}
4504+
44164505
forwardDeclaration = true;
44174506
return nullptr;
44184507
}
@@ -4466,7 +4555,9 @@ namespace {
44664555
}
44674556

44684557
Decl *VisitObjCInterfaceDecl(const clang::ObjCInterfaceDecl *decl) {
4469-
auto createFakeRootClass = [=](Identifier name,
4558+
4559+
auto createFakeRootClass = [=](Identifier name, bool cacheResult,
4560+
bool inheritFromNSObject,
44704561
DeclContext *dc = nullptr) -> ClassDecl * {
44714562
if (!dc) {
44724563
dc = Impl.getClangModuleForDecl(decl->getCanonicalDecl(),
@@ -4479,8 +4570,14 @@ namespace {
44794570
SourceLoc(), None,
44804571
nullptr, dc,
44814572
/*isActor*/false);
4482-
Impl.ImportedDecls[{decl->getCanonicalDecl(), getVersion()}] = result;
4483-
result->setSuperclass(Type());
4573+
if (cacheResult)
4574+
Impl.ImportedDecls[{decl->getCanonicalDecl(), getVersion()}] = result;
4575+
4576+
if (inheritFromNSObject)
4577+
result->setSuperclass(Impl.getNSObjectType());
4578+
else
4579+
result->setSuperclass(Type());
4580+
44844581
result->setAddedImplicitInitializers(); // suppress all initializers
44854582
result->setHasMissingVTableEntries(false);
44864583
addObjCAttribute(result, Impl.importIdentifier(decl->getIdentifier()));
@@ -4501,7 +4598,9 @@ namespace {
45014598
nsObjectTy->getClassOrBoundGenericClass();
45024599

45034600
auto result = createFakeRootClass(Impl.SwiftContext.Id_Protocol,
4504-
nsObjectDecl->getDeclContext());
4601+
/* cacheResult */ false,
4602+
/* inheritFromNSObject */ false,
4603+
nsObjectDecl->getDeclContext());
45054604
result->setForeignClassKind(ClassDecl::ForeignKind::RuntimeOnly);
45064605
return result;
45074606
}
@@ -4533,21 +4632,30 @@ namespace {
45334632
}
45344633
}
45354634

4635+
Impl.addImportDiagnostic(
4636+
decl, Diagnostic(diag::forward_declared_interface_label, decl),
4637+
decl->getSourceRange().getBegin());
4638+
45364639
if (Impl.ImportForwardDeclarations) {
4537-
// Fake it by making an unavailable opaque @objc root class.
4538-
auto result = createFakeRootClass(name);
4539-
result->setImplicit();
4540-
auto attr = AvailableAttr::createPlatformAgnostic(Impl.SwiftContext,
4541-
"This Objective-C class has only been forward-declared; "
4542-
"import its owning module to use it");
4543-
result->getAttrs().add(attr);
4544-
result->getAttrs().add(
4545-
new (Impl.SwiftContext) ForbidSerializingReferenceAttr(true));
4546-
return result;
4547-
} else {
4548-
Impl.addImportDiagnostic(
4549-
decl, Diagnostic(diag::forward_declared_interface_label, decl),
4550-
decl->getSourceRange().getBegin());
4640+
if (auto native = hasNonLocalNativeSwiftDecl<ClassDecl>(decl, name, hasKnownSwiftName)) {
4641+
const ModuleDecl* moduleForNativeDecl = native->getParentModule();
4642+
assert(moduleForNativeDecl);
4643+
Impl.addImportDiagnostic(decl, Diagnostic(diag::forward_declared_interface_shadows_imported_objc_Swift_interface,
4644+
decl, moduleForNativeDecl->getNameStr()),
4645+
decl->getSourceRange().getBegin());
4646+
} else {
4647+
// Fake it by making an unavailable opaque @objc root class.
4648+
auto result = createFakeRootClass(name, /* cacheResult */ true,
4649+
/* inheritFromNSObject */ true);
4650+
result->setImplicit();
4651+
auto attr = AvailableAttr::createPlatformAgnostic(Impl.SwiftContext,
4652+
"This Objective-C class has only been forward-declared; "
4653+
"import its owning module to use it");
4654+
result->getAttrs().add(attr);
4655+
result->getAttrs().add(
4656+
new (Impl.SwiftContext) ForbidSerializingReferenceAttr(true));
4657+
return result;
4658+
}
45514659
}
45524660

45534661
forwardDeclaration = true;

0 commit comments

Comments
 (0)