Skip to content

Import Forward Declared Objective-C Interfaces and Protocols #61606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ class ASTContext final {
llvm::SmallPtrSet<DerivativeAttr *, 1>>
DerivativeAttrs;

/// The Swift module currently being compiled.
ModuleDecl *MainModule = nullptr;

private:
/// The current generation number, which reflects the number of
/// times that external modules have been loaded.
Expand Down
8 changes: 6 additions & 2 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
HasAnyUnavailableValues : 1
);

SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1,
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1,
/// If the module is compiled as static library.
StaticLibrary : 1,

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

/// Whether this module has been compiled with comprehensive checking for
/// concurrency, e.g., Sendable checking.
IsConcurrencyChecked : 1
IsConcurrencyChecked : 1,

/// If the map from @objc provided name to top level swift::Decl in this
/// module is populated
ObjCNameLookupCachePopulated : 1
);

SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2,
Expand Down
23 changes: 21 additions & 2 deletions include/swift/AST/DiagnosticsClangImporter.def
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,27 @@ NOTE(invoked_func_not_imported, none, "function %0 unavailable (cannot import)",
NOTE(record_method_not_imported, none, "method %0 unavailable (cannot import)", (const clang::NamedDecl*))
NOTE(objc_property_not_imported, none, "property %0 unavailable (cannot import)", (const clang::NamedDecl*))

NOTE(forward_declared_interface_label, none, "interface %0 forward declared here", (const clang::NamedDecl*))
NOTE(forward_declared_protocol_label, none, "protocol %0 forward declared here", (const clang::NamedDecl*))
NOTE(placeholder_for_forward_declared_interface_member_access_failure, none,
"class '%0' will be imported as an opaque placeholder class and may be "
"missing members; import the definition to access the complete "
"interface", (StringRef))
NOTE(placeholder_for_forward_declared_protocol_member_access_failure, none,
"protocol '%0' will be imported as an opaque placeholder protocol "
"and may be missing members; import the definition to access the "
"complete protocol", (StringRef))
NOTE(forward_declared_interface_label, none,
"interface %0 forward declared here", (const clang::NamedDecl*))
NOTE(forward_declared_protocol_label, none,
"protocol %0 forward declared here", (const clang::NamedDecl*))

NOTE(forward_declared_interface_clashes_with_imported_objc_Swift_interface, none,
"interface %0 is incomplete and cannot be imported as a stub; "
"its name conflicts with a %1 in module %2",
(const clang::NamedDecl*, StringRef, StringRef))
NOTE(forward_declared_protocol_clashes_with_imported_objc_Swift_protocol, none,
"protocol %0 is incomplete and cannot be imported as a stub; "
"its name conflicts with a %1 in module %2",
(const clang::NamedDecl*, StringRef, StringRef))

#define UNDEFINE_DIAGNOSTIC_MACROS
#include "DefineDiagnosticMacros.h"
28 changes: 28 additions & 0 deletions include/swift/AST/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ class ModuleDecl
llvm::SmallDenseMap<Identifier, SmallVector<OverlayFile *, 1>>
declaredCrossImports;

llvm::DenseMap<Identifier, SmallVector<Decl *, 2>> ObjCNameLookupCache;

/// A description of what should be implicitly imported by each file of this
/// module.
const ImplicitImportInfo ImportInfo;
Expand Down Expand Up @@ -667,6 +669,14 @@ class ModuleDecl
Bits.ModuleDecl.IsConcurrencyChecked = value;
}

bool isObjCNameLookupCachePopulated() const {
return Bits.ModuleDecl.ObjCNameLookupCachePopulated;
}

void setIsObjCNameLookupCachePopulated(bool value) {
Bits.ModuleDecl.ObjCNameLookupCachePopulated = value;
}

/// For the main module, retrieves the list of primary source files being
/// compiled, that is, the files we're generating code for.
ArrayRef<SourceFile *> getPrimarySourceFiles() const;
Expand Down Expand Up @@ -704,6 +714,24 @@ class ModuleDecl
VisibleDeclConsumer &Consumer,
NLKind LookupKind) const;

private:
void populateObjCNameLookupCache();

public:
/// Finds top-levels decls of this module by @objc provided name.
/// Decls that have no @objc attribute are not considered.
///
/// This does a simple local lookup, not recursively looking through imports.
/// The order of the results is not guaranteed to be meaningful.
///
/// \param Results Vector collecting the decls.
///
/// \param name The @objc simple name to look for. Declarations with matching
/// name and "anonymous" @objc attribute, as well a matching named @objc
/// attribute will be added to Results.
void lookupTopLevelDeclsByObjCName(SmallVectorImpl<Decl *> &Results,
DeclName name);

/// This is a hack for 'main' file parsing and the integrated REPL.
///
/// FIXME: Refactor main file parsing to not pump the parser incrementally.
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ def enable_experimental_eager_clang_module_diagnostics :
Flag<["-"], "enable-experimental-eager-clang-module-diagnostics">,
HelpText<"Enable experimental eager diagnostics reporting on the importability of all referenced C, C++, and Objective-C libraries">;

def enable_import_objc_forward_declarations :
Flag<["-"], "enable-import-objc-forward-declarations">,
HelpText<"Attempt to import Objective-C forward declarations">;

def enable_experimental_pairwise_build_block :
Flag<["-"], "enable-experimental-pairwise-build-block">,
HelpText<"Enable experimental pairwise 'buildBlock' for result builders">;
Expand Down
58 changes: 58 additions & 0 deletions lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx,
Bits.ModuleDecl.HasIncrementalInfo = 0;
Bits.ModuleDecl.HasHermeticSealAtLink = 0;
Bits.ModuleDecl.IsConcurrencyChecked = 0;
Bits.ModuleDecl.ObjCNameLookupCachePopulated = 0;
}

ImplicitImportList ModuleDecl::getImplicitImports() const {
Expand Down Expand Up @@ -1176,6 +1177,60 @@ void ModuleDecl::getTopLevelDeclsWhereAttributesMatch(
FORWARD(getTopLevelDeclsWhereAttributesMatch, (Results, matchAttributes));
}

void ModuleDecl::lookupTopLevelDeclsByObjCName(SmallVectorImpl<Decl *> &Results,
DeclName name) {
if (!isObjCNameLookupCachePopulated())
populateObjCNameLookupCache();

// A top level decl can't be special anyways
if (name.isSpecial())
return;

auto resultsForFileUnit = ObjCNameLookupCache.find(name.getBaseIdentifier());
if (resultsForFileUnit == ObjCNameLookupCache.end())
return;

Results.append(resultsForFileUnit->second.begin(),
resultsForFileUnit->second.end());
}

void ModuleDecl::populateObjCNameLookupCache() {
SmallVector<Decl *> topLevelObjCExposedDeclsInFileUnit;
auto hasObjCAttrNamePredicate = [](const DeclAttributes &attrs) -> bool {
return attrs.hasAttribute<ObjCAttr>();
};

for (FileUnit *file : getFiles()) {
file->getTopLevelDeclsWhereAttributesMatch(
topLevelObjCExposedDeclsInFileUnit, hasObjCAttrNamePredicate);
if (auto *synth = file->getSynthesizedFile()) {
synth->getTopLevelDeclsWhereAttributesMatch(
topLevelObjCExposedDeclsInFileUnit, hasObjCAttrNamePredicate);
}
}

for (Decl *decl : topLevelObjCExposedDeclsInFileUnit) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could Decl *decl be const Decl *decl here?

if (ValueDecl *VD = dyn_cast<ValueDecl>(decl); VD && VD->hasName()) {
const auto &declObjCAttribute = VD->getAttrs().getAttribute<ObjCAttr>();
// No top level decl (class, protocol, extension etc.) is allowed to have a
// compound name, @objc provided or otherwise. Global functions are allowed to
// have compound names, but not allowed to have @objc attributes. Thus we
// are sure to not hit asserts getting the simple name.
//
// Similarly, init, dealloc and subscript (the special names) can't be top
// level decls, so we won't hit asserts getting the base identifier out of the
// value decl.
const Identifier &declObjCName =
declObjCAttribute->hasName()
? declObjCAttribute->getName()->getSimpleName()
: VD->getName().getBaseIdentifier();
ObjCNameLookupCache[declObjCName].push_back(decl);
}
}

setIsObjCNameLookupCachePopulated(true);
}

void SourceFile::getTopLevelDecls(SmallVectorImpl<Decl*> &Results) const {
auto decls = getTopLevelDecls();
Results.append(decls.begin(), decls.end());
Expand Down Expand Up @@ -3347,6 +3402,9 @@ bool SourceFile::shouldCrossImport() const {
void ModuleDecl::clearLookupCache() {
getASTContext().getImportCache().clear();

setIsObjCNameLookupCachePopulated(false);
ObjCNameLookupCache.clear();

if (!Cache)
return;

Expand Down
45 changes: 44 additions & 1 deletion lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6209,7 +6209,15 @@ void ClangImporter::diagnoseTopLevelValue(const DeclName &name) {

void ClangImporter::diagnoseMemberValue(const DeclName &name,
const Type &baseType) {
if (!baseType->getAnyNominal())

// Return early for any type that namelookup::extractDirectlyReferencedNominalTypes
// does not know how to handle.
if (!(baseType->getAnyNominal() ||
baseType->is<ExistentialType>() ||
baseType->is<UnboundGenericType>() ||
baseType->is<ArchetypeType>() ||
baseType->is<ProtocolCompositionType>() ||
baseType->is<TupleType>()))
return;

SmallVector<NominalTypeDecl *, 4> nominalTypesToLookInto;
Expand All @@ -6221,6 +6229,41 @@ void ClangImporter::diagnoseMemberValue(const DeclName &name,
Impl.diagnoseMemberValue(name,
cast<clang::DeclContext>(clangContainerDecl));
}

if (Impl.ImportForwardDeclarations) {
const clang::Decl *clangContainerDecl = containerDecl->getClangDecl();
if (const clang::ObjCInterfaceDecl *objCInterfaceDecl =
llvm::dyn_cast_or_null<clang::ObjCInterfaceDecl>(
clangContainerDecl); objCInterfaceDecl && !objCInterfaceDecl->hasDefinition()) {
// Emit a diagnostic about how the base type represents a forward
// declared ObjC interface and is in all likelihood missing members.
// We only attach this diagnostic in diagnoseMemberValue rather than
// in SwiftDeclConverter because it is only relevant when the user
// tries to access an unavailable member.
Impl.addImportDiagnostic(
objCInterfaceDecl,
Diagnostic(
diag::
placeholder_for_forward_declared_interface_member_access_failure,
objCInterfaceDecl->getName()),
objCInterfaceDecl->getSourceRange().getBegin());
// Emit any diagnostics attached to the source Clang node (ie. forward
// declaration here note)
Impl.diagnoseTargetDirectly(clangContainerDecl);
} else if (const clang::ObjCProtocolDecl *objCProtocolDecl =
llvm::dyn_cast_or_null<clang::ObjCProtocolDecl>(
clangContainerDecl); objCProtocolDecl && !objCProtocolDecl->hasDefinition()) {
// Same as above but for protocols
Impl.addImportDiagnostic(
objCProtocolDecl,
Diagnostic(
diag::
placeholder_for_forward_declared_protocol_member_access_failure,
objCProtocolDecl->getName()),
objCProtocolDecl->getSourceRange().getBegin());
Impl.diagnoseTargetDirectly(clangContainerDecl);
}
}
}
}

Expand Down
Loading