Skip to content

[cxx-interop] Configure requires ObjC from frontend option #83130

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
Jul 18, 2025
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: 2 additions & 1 deletion include/swift/AST/SwiftNameTranslation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,10 @@ namespace swift {
/// All block list configuration files to be honored in this compilation.
std::vector<std::string> BlocklistConfigFilePaths;

/// List of top level modules to be considered as if they had require ObjC
/// in their module map.
llvm::SmallVector<StringRef> ModulesRequiringObjC;

/// Whether to ignore checks that a module is resilient during
/// type-checking, SIL verification, and IR emission,
bool BypassResilienceChecks = false;
Expand Down
31 changes: 22 additions & 9 deletions lib/AST/SwiftNameTranslation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <optional>

Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lib/PrintAsClang/PrintClangValueType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 2 additions & 0 deletions test/Misc/verify-swift-feature-testing.test-sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down