diff --git a/include/swift/AST/SwiftNameTranslation.h b/include/swift/AST/SwiftNameTranslation.h index 2f7054fe00c4a..1ae3f691fccd9 100644 --- a/include/swift/AST/SwiftNameTranslation.h +++ b/include/swift/AST/SwiftNameTranslation.h @@ -13,6 +13,7 @@ #ifndef SWIFT_NAME_TRANSLATION_H #define SWIFT_NAME_TRANSLATION_H +#include "swift/AST/ASTContext.h" #include "swift/AST/AttrKind.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticEngine.h" @@ -113,7 +114,7 @@ inline bool isExposableToCxx( } bool isObjCxxOnly(const ValueDecl *VD); -bool isObjCxxOnly(const clang::Decl *D); +bool isObjCxxOnly(const clang::Decl *D, const ASTContext &ctx); /// Returns true if the given value decl D is visible to C++ of its /// own accord (i.e. without considering its context) diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index a0efc780144f1..a75dfb1a565f7 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -640,6 +640,10 @@ namespace swift { /// All block list configuration files to be honored in this compilation. std::vector BlocklistConfigFilePaths; + /// List of top level modules to be considered as if they had require ObjC + /// in their module map. + llvm::SmallVector ModulesRequiringObjC; + /// Whether to ignore checks that a module is resilient during /// type-checking, SIL verification, and IR emission, bool BypassResilienceChecks = false; diff --git a/lib/AST/SwiftNameTranslation.cpp b/lib/AST/SwiftNameTranslation.cpp index 7c69702f6b319..f7a7c422d7655 100644 --- a/lib/AST/SwiftNameTranslation.cpp +++ b/lib/AST/SwiftNameTranslation.cpp @@ -30,6 +30,7 @@ #include "clang/AST/DeclObjC.h" #include "clang/Basic/Module.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include @@ -241,10 +242,12 @@ swift::cxx_translation::getNameForCxx(const ValueDecl *VD, namespace { struct ObjCTypeWalker : TypeWalker { bool hadObjCType = false; + const ASTContext &ctx; + ObjCTypeWalker(const ASTContext &ctx) : ctx(ctx) {} Action walkToTypePre(Type ty) override { if (auto *nominal = ty->getNominalOrBoundGenericNominal()) { if (auto clangDecl = nominal->getClangDecl()) { - if (cxx_translation::isObjCxxOnly(clangDecl)) { + if (cxx_translation::isObjCxxOnly(clangDecl, ctx)) { hadObjCType = true; return Action::Stop; } @@ -256,19 +259,29 @@ struct ObjCTypeWalker : TypeWalker { } // namespace bool swift::cxx_translation::isObjCxxOnly(const ValueDecl *VD) { - ObjCTypeWalker walker; + ObjCTypeWalker walker{VD->getASTContext()}; VD->getInterfaceType().walk(walker); return walker.hadObjCType; } -bool swift::cxx_translation::isObjCxxOnly(const clang::Decl *D) { +bool swift::cxx_translation::isObjCxxOnly(const clang::Decl *D, + const ASTContext &ctx) { // By default, we import all modules in Obj-C++ mode, so there is no robust - // way to tell if something is coming from an Obj-C module. The best - // approximation is to check if something is a framework module as most of - // those are written in Obj-C. Ideally, we want to add `requires ObjC` to all - // ObjC modules and respect that here to make this completely precise. - return D->getASTContext().getLangOpts().ObjC && - D->getOwningModule()->isPartOfFramework(); + // way to tell if something is coming from an Obj-C module. Use the + // requirements and the language options to check if we should actually + // consider the module to have ObjC constructs. + const auto &langOpts = D->getASTContext().getLangOpts(); + auto clangModule = D->getOwningModule()->getTopLevelModule(); + bool requiresObjC = false; + for (auto req : clangModule->Requirements) + if (req.RequiredState && req.FeatureName == "objc") + requiresObjC = true; + return langOpts.ObjC && + (requiresObjC || + llvm::any_of(ctx.LangOpts.ModulesRequiringObjC, + [clangModule](StringRef moduleName) { + return clangModule->getFullModuleName() == moduleName; + })); } swift::cxx_translation::DeclRepresentation diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 6b31e72876c2d..2b473f6e7d105 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -850,7 +850,8 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args, // Collect some special case pseudo-features which should be processed // separately. if (argValue.starts_with("StrictConcurrency") || - argValue.starts_with("AvailabilityMacro=")) { + argValue.starts_with("AvailabilityMacro=") || + argValue.starts_with("RequiresObjC=")) { if (isEnableFeatureFlag) psuedoFeatures.push_back(argValue); continue; @@ -977,6 +978,11 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args, Opts.AvailabilityMacros.push_back(availability.str()); continue; } + + if (featureName->starts_with("RequiresObjC")) { + auto modules = featureName->split("=").second; + modules.split(Opts.ModulesRequiringObjC, ","); + } } // Map historical flags over to experimental features. We do this for all diff --git a/lib/PrintAsClang/PrintClangValueType.cpp b/lib/PrintAsClang/PrintClangValueType.cpp index 76198eaae12af..7c4c27cb86254 100644 --- a/lib/PrintAsClang/PrintClangValueType.cpp +++ b/lib/PrintAsClang/PrintClangValueType.cpp @@ -16,6 +16,7 @@ #include "OutputLanguageMode.h" #include "PrimitiveTypeMapping.h" #include "SwiftToClangInteropContext.h" +#include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/SwiftNameTranslation.h" #include "swift/AST/Type.h" @@ -619,7 +620,7 @@ void ClangValueTypePrinter::printTypeGenericTraits( bool objCxxOnly = false; if (const auto *clangDecl = typeDecl->getClangDecl()) { - if (cxx_translation::isObjCxxOnly(clangDecl)) + if (cxx_translation::isObjCxxOnly(clangDecl, typeDecl->getASTContext())) objCxxOnly = true; } diff --git a/test/Interop/SwiftToCxx/stdlib/foundation-type-not-exposed-by-default-to-cxx.swift b/test/Interop/SwiftToCxx/stdlib/foundation-type-not-exposed-by-default-to-cxx.swift index 26fbae249059c..b1779211c630c 100644 --- a/test/Interop/SwiftToCxx/stdlib/foundation-type-not-exposed-by-default-to-cxx.swift +++ b/test/Interop/SwiftToCxx/stdlib/foundation-type-not-exposed-by-default-to-cxx.swift @@ -1,6 +1,6 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend %s -module-name UseFoundation -enable-experimental-cxx-interop -typecheck -verify -emit-clang-header-path %t/UseFoundation.h +// RUN: %target-swift-frontend %s -module-name UseFoundation -enable-experimental-cxx-interop -typecheck -verify -emit-clang-header-path %t/UseFoundation.h -enable-experimental-feature RequiresObjC=CoreGraphics,Foundation // RUN: %FileCheck %s < %t/UseFoundation.h // RUN: %check-interop-cxx-header-in-clang(%t/UseFoundation.h -DSWIFT_CXX_INTEROP_HIDE_STL_OVERLAY) diff --git a/test/Misc/verify-swift-feature-testing.test-sh b/test/Misc/verify-swift-feature-testing.test-sh index 5d47339ea21f9..363f588315403 100755 --- a/test/Misc/verify-swift-feature-testing.test-sh +++ b/test/Misc/verify-swift-feature-testing.test-sh @@ -21,6 +21,8 @@ EXCEPTIONAL_FILES = [ pathlib.Path("test/ModuleInterface/swift-export-as.swift"), # Uses the pseudo-feature AvailabilityMacro= pathlib.Path("test/Availability/availability_define.swift"), + # Uses the pseudo-feature RequiresObjC= + pathlib.Path("test/Interop/SwiftToCxx/stdlib/foundation-type-not-exposed-by-default-to-cxx.swift"), # Tests behavior when you try to use a feature without enabling it pathlib.Path("test/attr/feature_requirement.swift"), # Tests completion with features both enabled and disabled