Emit module selectors in swiftinterfaces

This support is currently opt-in and can be disabled by a blocklist.
This commit is contained in:
Becca Royal-Gordon
2025-10-17 19:05:53 -07:00
parent 89ecc5ab2a
commit 9454c0aaa4
10 changed files with 245 additions and 16 deletions

View File

@@ -580,6 +580,9 @@ public:
/// with types sharing a name with a module.
bool AliasModuleNames = false;
/// Use module selectors when printing names.
bool UseModuleSelectors = false;
/// Name of the modules that have been aliased in AliasModuleNames mode.
/// Ideally we would use something other than a string to identify a module,
/// but since one alias can apply to more than one module, strings happen
@@ -769,6 +772,7 @@ public:
///
/// \see swift::emitSwiftInterface
static PrintOptions printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
bool useModuleSelectors,
bool preferTypeRepr,
bool printFullConvention,
InterfaceMode interfaceMode,

View File

@@ -27,5 +27,6 @@ BLOCKLIST_ACTION(ShouldDisableOwnershipVerification)
BLOCKLIST_ACTION(SkipEmittingFineModuleTrace)
BLOCKLIST_ACTION(SkipIndexingModule)
BLOCKLIST_ACTION(ShouldUseTypeCheckerPerfHacks)
BLOCKLIST_ACTION(DisableModuleSelectorsInModuleInterface)
#undef BLOCKLIST_ACTION

View File

@@ -40,6 +40,9 @@ struct ModuleInterfaceOptions {
/// with types sharing a name with a module.
bool AliasModuleNames = false;
/// Should we emit module selectors into the module interface?
bool UseModuleSelectors = false;
/// See \ref FrontendOptions.PrintFullConvention.
/// [TODO: Clang-type-plumbing] This check should go away.
bool PrintFullConvention = false;

View File

@@ -736,6 +736,16 @@ def emit_variant_package_module_interface_path :
MetaVarName<"<path>">,
HelpText<"Output package module interface file for the target variant to <path>">;
def enable_module_selectors_in_module_interface :
Flag<["-"], "enable-module-selectors-in-module-interface">,
Flags<[FrontendOption, NoInteractiveOption]>,
HelpText<"When emitting module interface files, use module selectors to avoid name collisions">;
def disable_module_selectors_in_module_interface :
Flag<["-"], "disable-module-selectors-in-module-interface">,
Flags<[FrontendOption, NoInteractiveOption]>,
HelpText<"When emitting module interface files, do not use module selectors to avoid name collisions">;
def verify_emitted_module_interface :
Flag<["-"], "verify-emitted-module-interface">,
Flags<[NoInteractiveOption, DoesNotAffectIncrementalBuild]>,

View File

@@ -263,6 +263,7 @@ private:
};
PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
bool useModuleSelectors,
bool preferTypeRepr,
bool printFullConvention,
InterfaceMode interfaceMode,
@@ -276,6 +277,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
result.PrintLongAttrsOnSeparateLines = true;
result.TypeDefinitions = true;
result.CurrentModule = ModuleToPrint;
result.UseModuleSelectors = useModuleSelectors;
result.FullyQualifiedTypes = true;
result.FullyQualifiedTypesIfAmbiguous = true;
result.FullyQualifiedExtendedTypesIfAmbiguous = true;
@@ -6058,9 +6060,8 @@ class TypePrinter : public TypeVisitor<TypePrinter, void, NonRecursivePrintOptio
return Options.CurrentModule->getVisibleClangModules(Options.InterfaceContentKind);
}
template <typename T>
void printModuleContext(T *Ty) {
FileUnit *File = cast<FileUnit>(Ty->getDecl()->getModuleScopeContext());
void printModuleContext(GenericTypeDecl *TyDecl) {
FileUnit *File = cast<FileUnit>(TyDecl->getModuleScopeContext());
ModuleDecl *Mod = File->getParentModule();
StringRef ExportedModuleName = File->getExportedModuleName();
@@ -6069,7 +6070,7 @@ class TypePrinter : public TypeVisitor<TypePrinter, void, NonRecursivePrintOptio
// all of these modules may be visible. We therefore need to make sure we
// choose a module that is visible from the current module. This is possible
// only if we know what the current module is.
const clang::Decl *ClangDecl = Ty->getDecl()->getClangDecl();
const clang::Decl *ClangDecl = TyDecl->getClangDecl();
if (ClangDecl && Options.CurrentModule) {
for (auto *Redecl : ClangDecl->redecls()) {
auto *owningModule = Redecl->getOwningModule();
@@ -6106,12 +6107,10 @@ class TypePrinter : public TypeVisitor<TypePrinter, void, NonRecursivePrintOptio
}
if (Options.UseOriginallyDefinedInModuleNames) {
Decl *D = Ty->getDecl();
for (auto attr: D->getAttrs().getAttributes<OriginallyDefinedInAttr>()) {
if (auto attr =
TyDecl->getAttrs().getAttribute<OriginallyDefinedInAttr>()) {
Name = Mod->getASTContext().getIdentifier(
const_cast<OriginallyDefinedInAttr *>(attr)
->getManglingModuleName());
break;
attr->getManglingModuleName());
}
}
@@ -6122,7 +6121,6 @@ class TypePrinter : public TypeVisitor<TypePrinter, void, NonRecursivePrintOptio
}
Printer.printModuleRef(Mod, Name);
Printer << ".";
}
template <typename T>
@@ -6140,7 +6138,42 @@ class TypePrinter : public TypeVisitor<TypePrinter, void, NonRecursivePrintOptio
return M->getRealName().str().starts_with(LLDB_EXPRESSIONS_MODULE_NAME_PREFIX);
}
bool isMemberOfGenericParameter(TypeBase *T) {
Type parent = nullptr;
if (auto alias = dyn_cast<TypeAliasType>(T))
parent = alias->getParent();
else if (auto generic = T->getAs<AnyGenericType>())
parent = generic->getParent();
return parent && parent->isTypeParameter();
}
bool shouldPrintModuleSelector(TypeBase *T) {
if (!Options.UseModuleSelectors)
return false;
GenericTypeDecl *GTD = T->getAnyGeneric();
if (!GTD && isa<TypeAliasType>(T))
GTD = cast<TypeAliasType>(T)->getDecl();
if (!GTD)
return false;
// Builtin types must always be qualified somehow.
ModuleDecl *M = GTD->getDeclContext()->getParentModule();
if (M->isBuiltinModule())
return true;
// A member of a generic parameter can't be qualified by a module selector.
if (isMemberOfGenericParameter(T))
return false;
// Module selectors skip over local types, so don't add one.
return GTD->getLocalContext() == nullptr;
}
bool shouldPrintFullyQualified(TypeBase *T) {
if (Options.UseModuleSelectors)
return false;
if (Options.FullyQualifiedTypes)
return true;
@@ -6197,7 +6230,15 @@ public:
printParentType(parent);
NameContext = PrintNameContext::TypeMember;
} else if (shouldPrintFullyQualified(Ty)) {
printModuleContext(Ty);
printModuleContext(Ty->getDecl());
Printer << ".";
NameContext = PrintNameContext::TypeMember;
}
// We print module selectors whether or not we printed a parent type.
if (shouldPrintModuleSelector(Ty)) {
printModuleContext(Ty->getDecl());
Printer << "::";
NameContext = PrintNameContext::TypeMember;
}

View File

@@ -316,6 +316,8 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
inputArgs.AddLastArg(arguments, options::OPT_module_cache_path);
inputArgs.AddLastArg(arguments, options::OPT_module_link_name);
inputArgs.AddLastArg(arguments, options::OPT_module_abi_name);
inputArgs.AddLastArg(arguments, options::OPT_enable_module_selectors_in_module_interface,
options::OPT_disable_module_selectors_in_module_interface);
inputArgs.AddLastArg(arguments, options::OPT_package_name);
inputArgs.AddLastArg(arguments, options::OPT_export_as);
inputArgs.AddLastArg(arguments, options::OPT_nostdimport);

View File

@@ -536,7 +536,8 @@ static void PrintArg(raw_ostream &OS, const char *Arg, StringRef TempDir) {
}
static void ParseModuleInterfaceArgs(ModuleInterfaceOptions &Opts,
ArgList &Args) {
ArgList &Args,
DiagnosticEngine &diags) {
using namespace options;
Opts.PreserveTypesAsWritten |=
@@ -545,6 +546,7 @@ static void ParseModuleInterfaceArgs(ModuleInterfaceOptions &Opts,
Args.hasFlag(OPT_alias_module_names_in_module_interface,
OPT_disable_alias_module_names_in_module_interface,
::getenv("SWIFT_ALIAS_MODULE_NAMES_IN_INTERFACES"));
Opts.PrintFullConvention |=
Args.hasArg(OPT_experimental_print_full_convention);
Opts.DebugPrintInvalidSyntax |=
@@ -558,6 +560,21 @@ static void ParseModuleInterfaceArgs(ModuleInterfaceOptions &Opts,
Opts.setInterfaceMode(PrintOptions::InterfaceMode::Private);
}
}
if (Args.hasArgNoClaim(OPT_enable_module_selectors_in_module_interface)
|| Args.hasArgNoClaim(OPT_disable_module_selectors_in_module_interface)) {
Opts.UseModuleSelectors =
Args.hasFlag(OPT_enable_module_selectors_in_module_interface,
OPT_disable_module_selectors_in_module_interface,
false);
} else if (auto envValue = ::getenv("SWIFT_MODULE_SELECTORS_IN_INTERFACES")) {
Opts.UseModuleSelectors = llvm::StringSwitch<bool>(envValue)
.CasesLower("false", "no", "off", "0", false)
.Default(true);
} else {
// Any heuristics we might add would go here.
Opts.UseModuleSelectors = false;
}
}
/// Checks if an arg is generally allowed to be included
@@ -4169,7 +4186,7 @@ bool CompilerInvocation::parseArgs(
setMainExecutablePath(mainExecutablePath);
}
ParseModuleInterfaceArgs(ModuleInterfaceOpts, ParsedArgs);
ParseModuleInterfaceArgs(ModuleInterfaceOpts, ParsedArgs, Diags);
SaveModuleInterfaceArgs(ModuleInterfaceOpts, FrontendOpts, ParsedArgs, Diags);
if (ParseCASArgs(CASOpts, ParsedArgs, Diags, FrontendOpts)) {

View File

@@ -884,13 +884,22 @@ bool swift::emitSwiftInterface(raw_ostream &out,
printImports(out, Opts, M, aliasModuleNamesTargets);
bool useExportedModuleNames = Opts.printPublicInterface();
// Apply module selector blocklist.
bool useModuleSelectors = Opts.UseModuleSelectors;
if (useModuleSelectors && M->getASTContext().blockListConfig
.hasBlockListAction(M->getNameStr(), BlockListKeyKind::ModuleName,
BlockListAction::
DisableModuleSelectorsInModuleInterface))
useModuleSelectors = false;
bool useExportedModuleNames = Opts.printPublicInterface();
const PrintOptions printOptions = PrintOptions::printSwiftInterfaceFile(
M, Opts.PreserveTypesAsWritten, Opts.PrintFullConvention,
M, useModuleSelectors, Opts.PreserveTypesAsWritten,
Opts.PrintFullConvention,
Opts.InterfaceContentMode,
useExportedModuleNames,
Opts.AliasModuleNames, &aliasModuleNamesTargets);
InheritedProtocolCollector::PerTypeMap inheritedProtocolMap;
SmallVector<Decl *, 16> topLevelDecls;
@@ -909,7 +918,8 @@ bool swift::emitSwiftInterface(raw_ostream &out,
D->print(out, printOptions);
out << "\n";
diagnoseIfDeclShadowsKnownModule(Opts, const_cast<Decl *>(D), M);
if (!useModuleSelectors)
diagnoseIfDeclShadowsKnownModule(Opts, const_cast<Decl *>(D), M);
}
// Print dummy extensions for any protocols that were indirectly conformed to.

View File

@@ -0,0 +1,4 @@
---
DisableModuleSelectorsInModuleInterface:
ModuleName:
- TestCase

View File

@@ -0,0 +1,137 @@
// RUN: %empty-directory(%t)
// Test with -enable-module-selectors-in-module-interface
// RUN: %empty-directory(%t/enabled)
// RUN: %target-swift-emit-module-interface(%t/enabled/TestCase.swiftinterface) %s %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase -enable-module-selectors-in-module-interface
// RUN: %FileCheck --input-file %t/enabled/TestCase.swiftinterface %s --check-prefixes CHECK,CHECK-ENABLED
// RUN: %target-swift-typecheck-module-from-interface(%t/enabled/TestCase.swiftinterface) %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase
// Test with -disable-module-selectors-in-module-interface
// RUN: %empty-directory(%t/disabled)
// RUN: %target-swift-emit-module-interface(%t/disabled/TestCase.swiftinterface) %s %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase -disable-module-selectors-in-module-interface
// RUN: %FileCheck --input-file %t/disabled/TestCase.swiftinterface %s --check-prefixes CHECK,CHECK-DISABLED
// RUN: %target-swift-typecheck-module-from-interface(%t/disabled/TestCase.swiftinterface) %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase
// Test default behavior
// RUN: %empty-directory(%t/default)
// RUN: %target-swift-emit-module-interface(%t/default/TestCase.swiftinterface) %s %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase
// RUN: %FileCheck --input-file %t/default/TestCase.swiftinterface %s --check-prefixes CHECK,CHECK-DISABLED
// RUN: %target-swift-typecheck-module-from-interface(%t/default/TestCase.swiftinterface) %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase
// Test with -enable-module-selectors-in-module-interface and blocklist
// RUN: %empty-directory(%t/blocked)
// RUN: %target-swift-emit-module-interface(%t/blocked/TestCase.swiftinterface) %s %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase -enable-module-selectors-in-module-interface -blocklist-file %S/Inputs/module_selector/blocklist.yml
// RUN: %FileCheck --input-file %t/blocked/TestCase.swiftinterface %s --check-prefixes CHECK,CHECK-DISABLED
// RUN: %target-swift-typecheck-module-from-interface(%t/blocked/TestCase.swiftinterface) %clang-importer-sdk -F %clang-importer-sdk-path/frameworks -I %S/Inputs/module_selector -target %target-stable-abi-triple -module-name TestCase
// CHECK: import enums_using_attributes
import enums_using_attributes
// CHECK-LABEL: public struct Struct<T> :
// CHECK-ENABLED-SAME: Swift::Hashable where T : Swift::Hashable {
// CHECK-DISABLED-SAME: Swift.Hashable where T : Swift.Hashable {
public struct Struct<T: Hashable>: Hashable {
// CHECK: public let integer:
// CHECK-ENABLED-SAME: Swift::Int
// CHECK-DISABLED-SAME: Swift.Int
public let integer: Int = 42
// CHECK: public let enumeration:
// CHECK-ENABLED-SAME: enums_using_attributes::CFEnumWithAttr
// CHECK-DISABLED-SAME: enums_using_attributes.CFEnumWithAttr
public let enumeration: CFEnumWithAttr = CFEnumWithAttr.first
// CHECK: public let t: T?
public let t: T? = nil
// CHECK: public static func ==
// CHECK-ENABLED-SAME: (a: TestCase::Struct<T>, b: TestCase::Struct<T>) -> Swift::Bool
// CHECK-DISABLED-SAME: (a: TestCase.Struct<T>, b: TestCase.Struct<T>) -> Swift.Bool
// CHECK: public func hash
// CHECK-ENABLED-SAME: (into hasher: inout Swift::Hasher)
// CHECK-DISABLED-SAME: (into hasher: inout Swift.Hasher)
// CHECK: public var hashValue:
// CHECK-ENABLED-SAME: Swift::Int {
// CHECK-DISABLED-SAME: Swift.Int {
// CHECK: }
}
// CHECK-ENABLED: extension TestCase::Struct {
// CHECK-DISABLED: extension TestCase.Struct {
extension Struct {
// CHECK-LABEL: public enum Nested {
public enum Nested {
// CHECK: case integer
// CHECK-ENABLED-SAME: (Swift::Int)
// CHECK-DISABLED-SAME: (Swift.Int)
case integer(Int)
// CHECK: case enumeration
// CHECK-ENABLED-SAME: (enums_using_attributes::CFEnumWithAttr)
// CHECK-DISABLED-SAME: (enums_using_attributes.CFEnumWithAttr)
case enumeration(CFEnumWithAttr)
// CHECK: case t(T)
case t(T)
// CHECK: case `struct`
// CHECK-ENABLED-SAME: (TestCase::Struct<T>)
// CHECK-DISABLED-SAME: (TestCase.Struct<T>)
case `struct`(Struct)
// CHECK: }
}
// CHECK: }
}
// CHECK-ENABLED: extension Swift::Int {
// CHECK-DISABLED: extension Swift.Int {
extension Swift::Int {
// CHECK-LABEL: public enum RetroactiveNested {
public enum RetroactiveNested {}
}
// CHECK-ENABLED: extension Swift::Int.TestCase::RetroactiveNested {
// CHECK-DISABLED: extension Swift.Int.RetroactiveNested {
extension Int.RetroactiveNested {
public func anchor() {}
}
// CHECK-LABEL: public func fn<T>
// CHECK-ENABLED-SAME: (_: TestCase::Struct<T>, _: TestCase::Struct<T>.TestCase::Nested)
// CHECK-DISABLED-SAME: (_: TestCase.Struct<T>, _: TestCase.Struct<T>.Nested)
public func fn<T: Hashable>(_: Struct<T>, _: Struct<T>.Nested) {}
// CHECK-LABEL: public func fn2<T>
// CHECK-ENABLED-SAME: (_: T) where T : Swift::Identifiable, T.ID == TestCase::Struct<Swift::Int>
// CHECK-DISABLED-SAME: (_: T) where T : Swift.Identifiable, T.ID == TestCase.Struct<Swift.Int>
@available(SwiftStdlib 5.1, *)
public func fn2<T: Identifiable>(_: T) where T.ID == Struct<Int> {}
// CHECK-LABEL: public protocol Proto {
@available(SwiftStdlib 5.1, *)
public protocol Proto {
// CHECK: associatedtype AssocType :
// CHECK-ENABLED-SAME: Swift::Identifiable
// CHECK-DISABLED-SAME: Swift.Identifiable
associatedtype AssocType: Identifiable
// CHECK: typealias TypeAlias =
// CHECK-ENABLED-SAME: TestCase::Struct<Swift::Int>
// CHECK-DISABLED-SAME: TestCase.Struct<Swift.Int>
typealias TypeAlias = Struct<Int>
// CHECK: func requirement() -> Self.AssocType.ID
func requirement() -> AssocType.ID
// CHECK: func requirement2() ->
// CHECK-ENABLED: Self.TypeAlias.TestCase::Nested
// CHECK-DISABLED: Self.TypeAlias.Nested
func requirement2() -> TypeAlias.Nested
// CHECK: }
}