Skip to content

[ModuleInterface] Avoid ambiguities in swiftinterfaces by writing aliases for module names #61941

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 7 commits into from
Nov 7, 2022
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
7 changes: 6 additions & 1 deletion include/swift/AST/ASTPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include "llvm/Support/raw_ostream.h"
#include "swift/AST/PrintOptions.h"

// Prefix to use when printing module names in module interfaces to avoid
// ambiguities with type names, in AliasModuleNames mode.
#define MODULE_DISAMBIGUATING_PREFIX "Module___"

namespace swift {
class Decl;
class DeclContext;
Expand Down Expand Up @@ -162,7 +166,8 @@ class ASTPrinter {
PrintNameContext NameContext = PrintNameContext::Normal);

/// Called when printing the referenced name of a module.
virtual void printModuleRef(ModuleEntity Mod, Identifier Name);
virtual void printModuleRef(ModuleEntity Mod, Identifier Name,
const PrintOptions &Options);

/// Called before printing a synthesized extension.
virtual void printSynthesizedExtensionPre(const ExtensionDecl *ED,
Expand Down
9 changes: 7 additions & 2 deletions include/swift/AST/PrintOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,10 @@ struct PrintOptions {
/// of the alias.
bool PrintTypeAliasUnderlyingType = false;

/// Use aliases when printing references to modules to avoid ambiguities
/// with types sharing a name with a module.
bool AliasModuleNames = false;

/// When printing an Optional<T>, rather than printing 'T?', print
/// 'T!'. Used as a modifier only when we know we're printing
/// something that was declared as an implicitly unwrapped optional
Expand Down Expand Up @@ -601,7 +605,7 @@ struct PrintOptions {
return result;
}

/// Retrieve the set of options suitable for interface generation.
/// Retrieve the set of options suitable for IDE interface generation.
static PrintOptions printInterface(bool printFullConvention) {
PrintOptions result =
printForDiagnostics(AccessLevel::Public, printFullConvention);
Expand Down Expand Up @@ -636,7 +640,8 @@ struct PrintOptions {
static PrintOptions printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
bool preferTypeRepr,
bool printFullConvention,
bool printSPIs);
bool printSPIs,
bool aliasModuleNames);

/// Retrieve the set of options suitable for "Generated Interfaces", which
/// are a prettified representation of the public API of a module, to be
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Frontend/ModuleInterfaceSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ struct ModuleInterfaceOptions {
/// interface, or should we fully-qualify them?
bool PreserveTypesAsWritten = false;

/// Use aliases when printing references to modules to avoid ambiguities
/// with types sharing a name with a module.
bool AliasModuleNames = false;

/// See \ref FrontendOptions.PrintFullConvention.
/// [TODO: Clang-type-plumbing] This check should go away.
bool PrintFullConvention = false;
Expand Down
9 changes: 9 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,15 @@ def module_interface_preserve_types_as_written :
HelpText<"When emitting a module interface, preserve types as they were "
"written in the source">;

def alias_module_names_in_module_interface :
Flag<["-"], "alias-module-names-in-module-interface">,
HelpText<"When emitting a module interface, disambiguate modules using "
"distinct alias names">;
def disable_alias_module_names_in_module_interface :
Flag<["-"], "disable-alias-module-names-in-module-interface">,
HelpText<"When emitting a module interface, disable disambiguating modules "
"using distinct alias names">;

def experimental_spi_imports :
Flag<["-"], "experimental-spi-imports">,
HelpText<"Enable experimental support for SPI imports">;
Expand Down
17 changes: 12 additions & 5 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ static bool contributesToParentTypeStorage(const AbstractStorageDecl *ASD) {
PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
bool preferTypeRepr,
bool printFullConvention,
bool printSPIs) {
bool printSPIs,
bool aliasModuleNames) {
PrintOptions result;
result.IsForSwiftInterface = true;
result.PrintLongAttrsOnSeparateLines = true;
Expand All @@ -152,6 +153,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
result.OpaqueReturnTypePrinting =
OpaqueReturnTypePrintingMode::StableReference;
result.PreferTypeRepr = preferTypeRepr;
result.AliasModuleNames = aliasModuleNames;
if (printFullConvention)
result.PrintFunctionRepresentationAttrs =
PrintOptions::FunctionRepresentationMode::Full;
Expand Down Expand Up @@ -365,7 +367,11 @@ void ASTPrinter::printTypeRef(Type T, const TypeDecl *RefTo, Identifier Name,
printName(Name, Context);
}

void ASTPrinter::printModuleRef(ModuleEntity Mod, Identifier Name) {
void ASTPrinter::printModuleRef(ModuleEntity Mod, Identifier Name,
const PrintOptions &Options) {
if (Options.AliasModuleNames)
printTextImpl(MODULE_DISAMBIGUATING_PREFIX);

printName(Name);
}

Expand Down Expand Up @@ -2503,7 +2509,7 @@ void PrintAST::visitImportDecl(ImportDecl *decl) {
Name = Declaring->getRealName();
}
}
Printer.printModuleRef(Mods.front(), Name);
Printer.printModuleRef(Mods.front(), Name, Options);
Mods = Mods.slice(1);
} else {
Printer << Elem.Item.str();
Expand Down Expand Up @@ -5391,7 +5397,7 @@ class TypePrinter : public TypeVisitor<TypePrinter> {
}
}

Printer.printModuleRef(Mod, Name);
Printer.printModuleRef(Mod, Name, Options);
Printer << ".";
}

Expand Down Expand Up @@ -5742,7 +5748,8 @@ class TypePrinter : public TypeVisitor<TypePrinter> {
Printer << "module<";
// Should print the module real name in case module aliasing is
// used (see -module-alias), since that's the actual binary name.
Printer.printModuleRef(T->getModule(), T->getModule()->getRealName());
Printer.printModuleRef(T->getModule(), T->getModule()->getRealName(),
Options);
Printer << ">";
}

Expand Down
2 changes: 1 addition & 1 deletion lib/AST/TypeRepr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ void ComponentIdentTypeRepr::printImpl(ASTPrinter &Printer,
const PrintOptions &Opts) const {
if (auto *TD = dyn_cast_or_null<TypeDecl>(getBoundDecl())) {
if (auto MD = dyn_cast<ModuleDecl>(TD))
Printer.printModuleRef(MD, getNameRef().getBaseIdentifier());
Printer.printModuleRef(MD, getNameRef().getBaseIdentifier(), Opts);
else
Printer.printTypeRef(Type(), TD, getNameRef().getBaseIdentifier());
} else {
Expand Down
10 changes: 8 additions & 2 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1926,20 +1926,26 @@ bool ClangImporter::canImportModule(ImportPath::Module modulePath,
ModuleDecl *ClangImporter::Implementation::loadModuleClang(
SourceLoc importLoc, ImportPath::Module path) {
auto &clangHeaderSearch = getClangPreprocessor().getHeaderSearchInfo();
auto realModuleName = SwiftContext.getRealModuleName(path.front().Item).str();

// Look up the top-level module first, to see if it exists at all.
clang::Module *clangModule = clangHeaderSearch.lookupModule(
path.front().Item.str(), /*ImportLoc=*/clang::SourceLocation(),
realModuleName, /*ImportLoc=*/clang::SourceLocation(),
/*AllowSearch=*/true, /*AllowExtraModuleMapSearch=*/true);
if (!clangModule)
return nullptr;

// Convert the Swift import path over to a Clang import path.
SmallVector<std::pair<clang::IdentifierInfo *, clang::SourceLocation>, 4>
clangPath;
bool isTopModuleComponent = true;
for (auto component : path) {
StringRef item = isTopModuleComponent? realModuleName:
component.Item.str();
isTopModuleComponent = false;

clangPath.emplace_back(
getClangPreprocessor().getIdentifierInfo(component.Item.str()),
getClangPreprocessor().getIdentifierInfo(item),
exportSourceLoc(component.Loc));
}

Expand Down
7 changes: 2 additions & 5 deletions lib/Frontend/ArgsToFrontendOptionsConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -755,15 +755,12 @@ bool ModuleAliasesConverter::computeModuleAliases(std::vector<std::string> args,
if (!allowModuleName) {
if (value == options.ModuleName ||
value == options.ModuleABIName ||
value == options.ModuleLinkName) {
value == options.ModuleLinkName ||
value == STDLIB_NAME) {
diags.diagnose(SourceLoc(), diag::error_module_alias_forbidden_name, value);
return false;
}
}
if (value == STDLIB_NAME) {
diags.diagnose(SourceLoc(), diag::error_module_alias_forbidden_name, value);
return false;
}
if (!Lexer::isIdentifier(value)) {
diags.diagnose(SourceLoc(), diag::error_bad_module_name, value, false);
return false;
Expand Down
4 changes: 4 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ static void ParseModuleInterfaceArgs(ModuleInterfaceOptions &Opts,

Opts.PreserveTypesAsWritten |=
Args.hasArg(OPT_module_interface_preserve_types_as_written);
Opts.AliasModuleNames |=
Args.hasFlag(OPT_alias_module_names_in_module_interface,
OPT_disable_alias_module_names_in_module_interface,
false);
Opts.PrintFullConvention |=
Args.hasArg(OPT_experimental_print_full_convention);
Opts.ExperimentalSPIImports |=
Expand Down
3 changes: 2 additions & 1 deletion lib/Frontend/ModuleInterfaceLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1027,9 +1027,10 @@ class ModuleInterfaceLoaderImpl {
}
// Set up a builder if we need to build the module. It'll also set up
// the genericSubInvocation we'll need to use to compute the cache paths.
Identifier realName = ctx.getRealModuleName(ctx.getIdentifier(moduleName));
ImplicitModuleInterfaceBuilder builder(
ctx.SourceMgr, diagsToUse,
astDelegate, interfacePath, moduleName, cacheDir,
astDelegate, interfacePath, realName.str(), cacheDir,
prebuiltCacheDir, backupInterfaceDir, StringRef(),
Opts.disableInterfaceLock,
ctx.IgnoreAdjacentModules, diagnosticLoc,
Expand Down
32 changes: 30 additions & 2 deletions lib/Frontend/ModuleInterfaceSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,32 @@ static void printToolVersionAndFlagsComment(raw_ostream &out,
out << "// " SWIFT_COMPILER_VERSION_KEY ": "
<< ToolsVersion << "\n";
out << "// " SWIFT_MODULE_FLAGS_KEY ": "
<< Opts.Flags << "\n";
<< Opts.Flags;

// Insert additional -module-alias flags
if (Opts.AliasModuleNames) {
llvm::SmallSet<StringRef, 2> aliasTargets;
StringRef moduleName = M->getNameStr();
aliasTargets.insert(M->getNameStr());
out << " -module-alias " << MODULE_DISAMBIGUATING_PREFIX <<
moduleName << "=" << moduleName;

SmallVector<ImportedModule> imports;
M->getImportedModules(imports,
{ModuleDecl::ImportFilterKind::Default,
ModuleDecl::ImportFilterKind::Exported,
ModuleDecl::ImportFilterKind::SPIOnly,
ModuleDecl::ImportFilterKind::SPIAccessControl});
for (ImportedModule import: imports) {
StringRef importedName = import.importedModule->getNameStr();
if (aliasTargets.insert(importedName).second) {
out << " -module-alias " << MODULE_DISAMBIGUATING_PREFIX <<
importedName << "=" << importedName;
}
}
}
out << "\n";

if (!Opts.IgnorableFlags.empty()) {
out << "// " SWIFT_MODULE_FLAGS_IGNORABLE_KEY ": "
<< Opts.IgnorableFlags << "\n";
Expand Down Expand Up @@ -295,6 +320,8 @@ static void printImports(raw_ostream &out,
}

out << "import ";
if (Opts.AliasModuleNames)
out << MODULE_DISAMBIGUATING_PREFIX;
importedModule->getReverseFullModuleName().printForward(out);

// Write the access path we should be honoring but aren't.
Expand Down Expand Up @@ -758,7 +785,8 @@ bool swift::emitSwiftInterface(raw_ostream &out,
printImports(out, Opts, M);

const PrintOptions printOptions = PrintOptions::printSwiftInterfaceFile(
M, Opts.PreserveTypesAsWritten, Opts.PrintFullConvention, Opts.PrintSPIs);
M, Opts.PreserveTypesAsWritten, Opts.PrintFullConvention, Opts.PrintSPIs,
Opts.AliasModuleNames);
InheritedProtocolCollector::PerTypeMap inheritedProtocolMap;

SmallVector<Decl *, 16> topLevelDecls;
Expand Down
5 changes: 3 additions & 2 deletions lib/IDE/ModuleInterfacePrinting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ class ClangCommentPrinter : public ASTPrinter {
PrintNameContext NameContext) override {
return OtherPrinter.printTypeRef(T, TD, Name, NameContext);
}
void printModuleRef(ModuleEntity Mod, Identifier Name) override {
return OtherPrinter.printModuleRef(Mod, Name);
void printModuleRef(ModuleEntity Mod, Identifier Name,
const PrintOptions &Options) override {
return OtherPrinter.printModuleRef(Mod, Name, Options);
}
void printSynthesizedExtensionPre(const ExtensionDecl *ED,
TypeOrExtensionDecl Target,
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/ImportResolution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,8 @@ getModuleImpl(ImportPath::Module modulePath, ModuleDecl *loadingModule,
//
// FIXME: We'd like to only use this in SIL mode, but unfortunately we use it
// for clang overlays as well.
if (moduleID.Item == loadingModule->getName() && modulePath.size() == 1) {
if (ctx.getRealModuleName(moduleID.Item) == loadingModule->getName() &&
modulePath.size() == 1) {
if (auto importer = ctx.getClangModuleLoader())
return importer->loadModule(moduleID.Loc, modulePath);
return nullptr;
Expand Down
1 change: 1 addition & 0 deletions test/Frontend/module-alias-invalid-input.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

// RUN: not %target-swift-frontend -emit-silgen -parse-as-library %s -module-name foo -module-alias Swift=Bar 2>&1 | %FileCheck -check-prefix=INVALID_MODULE_ALIAS1 %s
// INVALID_MODULE_ALIAS1: error: invalid module alias "Swift"; make sure the alias differs from the module name, module ABI name, module link name, and a standard library name
// RUN: %target-swift-frontend -emit-silgen -parse-as-library %s -module-name foo -module-alias Bar=Swift -verify

// RUN: not %target-swift-frontend -emit-silgen -parse-as-library %s -module-name foo -module-alias bar=bar 2>&1 | %FileCheck -check-prefix=INVALID_MODULE_ALIAS2 %s
// INVALID_MODULE_ALIAS2: error: duplicate module alias; the name "bar" is already used for a module alias or an underlying name
Expand Down
66 changes: 66 additions & 0 deletions test/ModuleInterface/ambiguous-aliases-workaround.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/// Test that the AliasModuleNames mode avoids ambiguities in swiftinterfaces

// RUN: %empty-directory(%t)
// RUN: split-file %s %t

// RUN: %target-swift-frontend -emit-module -module-name AmbiguousLib \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: -o %t/AmbiguousLib.swiftmodule \
// RUN: -emit-module-interface-path %t/AmbiguousLib.swiftinterface \
// RUN: %t/AmbiguousLib.swift
// RUN: %target-swift-typecheck-module-from-interface(%t/AmbiguousLib.swiftinterface)

// RUN: %target-swift-frontend -emit-module -module-name AmbiguousClientName \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: -o %t/AmbiguousClientName.swiftmodule \
// RUN: -emit-module-interface-path %t/AmbiguousClientName.swiftinterface \
// RUN: %t/AmbiguousClientName.swift -I%t \
// RUN: -alias-module-names-in-module-interface
// RUN: %target-swift-typecheck-module-from-interface(%t/AmbiguousClientName.swiftinterface) -I%t

//--- module.modulemap
module AmbiguousClientName {
header "AmbiguousClientName.h"
}

module SomeClangModule {
header "SomeClangModule.h"
}

//--- AmbiguousClientName.h
struct UnderlyingType {};
void underlyingFunc() {}

//--- SomeClangModule.h
struct CType {};

//--- AmbiguousLib.swift

// 1. AmbiguousLib defined a type named AmbiguousLib
public struct AmbiguousLib {
public struct Nested {}
}

// 2. A lib defines a type of the same name as a client's module
public struct AmbiguousClientName {
}

//--- AmbiguousClientName.swift

@_exported import AmbiguousClientName
import AmbiguousLib
import SomeClangModule

public struct SomeType {
@inlinable
public func inlinableFunc() {
var x: AmbiguousClientName
}
}

public func refToLocalType(_ a: SomeType) {}
public func refToNestedInLib(_ a: AmbiguousLib.Nested) {}

public func refToStdlib(_ a: Swift.Int) {}
public func refToUnderlying(_ a: UnderlyingType) {}
public func refToC(_ a: CType) {}
32 changes: 32 additions & 0 deletions test/ModuleInterface/module-alias-renaming-swiftinterface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/// Check that we can alias a module imported from a swiftinterface
// RUN: %empty-directory(%t)
// RUN: split-file %s %t

// RUN: %target-swift-frontend -emit-module -module-name Lib \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: -o %t/Lib.swiftmodule \
// RUN: -emit-module-interface-path %t/Lib.swiftinterface \
// RUN: %t/Lib.swift

/// We can alias an imported module built from a swiftmodule
// RUN: %target-swift-frontend -typecheck -module-name Client \
// RUN: -swift-version 5 \
// RUN: -module-alias AliasedLib=Lib \
// RUN: %t/Client.swift -I%t

/// We can alias an imported module built from a swiftinterface
// RUN: rm %t/Lib.swiftmodule
// RUN: %target-swift-frontend -typecheck -module-name Client \
// RUN: -swift-version 5 \
// RUN: -module-alias AliasedLib=Lib \
// RUN: %t/Client.swift -I%t

//--- Lib.swift
public func foo() {}

//--- Client.swift
import AliasedLib

func main() {
AliasedLib.foo()
}
Loading