Skip to content

[Module Aliasing] Add module aliasing option to swift-ide-test #40494

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
Dec 11, 2021
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
10 changes: 10 additions & 0 deletions include/swift/Frontend/Frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,16 @@ class CompilerInvocation {
return FrontendOpts.ModuleName;
}

/// Sets the module alias map with string args passed in via `-module-alias`.
/// \param args The arguments to `-module-alias`. If input has `-module-alias Foo=Bar
/// -module-alias Baz=Qux`, the args are ['Foo=Bar', 'Baz=Qux']. The name
/// Foo is the name that appears in source files, while it maps to Bar, the name
/// of the binary on disk, /path/to/Bar.swiftmodule(interface), under the hood.
/// \param diags Used to print diagnostics in case validation of the string args fails.
/// See \c ModuleAliasesConverter::computeModuleAliases on validation details.
/// \return Whether setting module alias map succeeded; false if args validation fails.
bool setModuleAliasMap(std::vector<std::string> args, DiagnosticEngine &diags);

std::string getOutputFilename() const {
return FrontendOpts.InputsAndOutputs.getSingleOutputFilename();
}
Expand Down
119 changes: 65 additions & 54 deletions lib/Frontend/ArgsToFrontendOptionsConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,9 @@ bool ArgsToFrontendOptionsConverter::convert(
Opts.ModuleLinkName = A->getValue();

// This must be called after computing module name, module abi name,
// and module link name.
if (computeModuleAliases())
// and module link name. If computing module aliases is unsuccessful,
// return early.
if (!computeModuleAliases())
return true;

if (const Arg *A = Args.getLastArg(OPT_access_notes_path))
Expand Down Expand Up @@ -515,58 +516,7 @@ bool ArgsToFrontendOptionsConverter::setUpImmediateArgs() {

bool ArgsToFrontendOptionsConverter::computeModuleAliases() {
auto list = Args.getAllArgValues(options::OPT_module_alias);
if (!list.empty()) {
auto validate = [this](StringRef value, bool allowModuleName) -> bool
{
if (!allowModuleName) {
if (value == Opts.ModuleName ||
value == Opts.ModuleABIName ||
value == Opts.ModuleLinkName) {
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;
}
return true;
};

for (auto item: list) {
auto str = StringRef(item);
// splits to an alias and the underlying name
auto pair = str.split('=');
auto lhs = pair.first;
auto rhs = pair.second;

if (rhs.empty()) { // '=' is missing
Diags.diagnose(SourceLoc(), diag::error_module_alias_invalid_format, str);
return true;
}
if (!validate(lhs, false) || !validate(rhs, true)) {
return true;
}

// First, add the underlying name as a key to prevent it from being
// used as an alias
if (!Opts.ModuleAliasMap.insert({rhs, StringRef()}).second) {
Diags.diagnose(SourceLoc(), diag::error_module_alias_duplicate, rhs);
return true;
}
// Next, add the alias as a key and the underlying name as a value to the map
auto underlyingName = Opts.ModuleAliasMap.find(rhs)->first();
if (!Opts.ModuleAliasMap.insert({lhs, underlyingName}).second) {
Diags.diagnose(SourceLoc(), diag::error_module_alias_duplicate, lhs);
return true;
}
}
}
return false;
return ModuleAliasesConverter::computeModuleAliases(list, Opts, Diags);
}

bool ArgsToFrontendOptionsConverter::computeModuleName() {
Expand Down Expand Up @@ -744,3 +694,64 @@ void ArgsToFrontendOptionsConverter::computeLLVMArgs() {
Opts.LLVMArgs.push_back(A->getValue());
}
}

bool ModuleAliasesConverter::computeModuleAliases(std::vector<std::string> args,
FrontendOptions &options,
DiagnosticEngine &diags) {
if (!args.empty()) {
// ModuleAliasMap should initially be empty as setting
// it should be called only once
options.ModuleAliasMap.clear();

auto validate = [&options, &diags](StringRef value, bool allowModuleName) -> bool
{
if (!allowModuleName) {
if (value == options.ModuleName ||
value == options.ModuleABIName ||
value == options.ModuleLinkName) {
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;
}
return true;
};

for (auto item: args) {
auto str = StringRef(item);
// splits to an alias and the underlying name
auto pair = str.split('=');
auto lhs = pair.first;
auto rhs = pair.second;

if (rhs.empty()) { // '=' is missing
diags.diagnose(SourceLoc(), diag::error_module_alias_invalid_format, str);
return false;
}
if (!validate(lhs, false) || !validate(rhs, true)) {
return false;
}

// First, add the underlying name as a key to prevent it from being
// used as an alias
if (!options.ModuleAliasMap.insert({rhs, StringRef()}).second) {
diags.diagnose(SourceLoc(), diag::error_module_alias_duplicate, rhs);
return false;
}
// Next, add the alias as a key and the underlying name as a value to the map
auto underlyingName = options.ModuleAliasMap.find(rhs)->first();
if (!options.ModuleAliasMap.insert({lhs, underlyingName}).second) {
diags.diagnose(SourceLoc(), diag::error_module_alias_duplicate, lhs);
return false;
}
}
}
return true;
}
16 changes: 16 additions & 0 deletions lib/Frontend/ArgsToFrontendOptionsConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ class ArgsToFrontendOptionsConverter {
determineRequestedAction(const llvm::opt::ArgList &);
};

class ModuleAliasesConverter {
public:
/// Sets the \c ModuleAliasMap in the \c FrontendOptions with args passed via `-module-alias`.
///
/// \param args The arguments to `-module-alias`. If input has `-module-alias Foo=Bar
/// -module-alias Baz=Qux`, the args are ['Foo=Bar', 'Baz=Qux']. The name
/// Foo is the name that appears in source files, while it maps to Bar, the name
/// of the binary on disk, /path/to/Bar.swiftmodule(interface), under the hood.
/// \param options FrontendOptions containings the module alias map to set args to.
/// \param diags Used to print diagnostics in case validation of the args fails.
/// \return Whether the validation passed and successfully set the module alias map
static bool computeModuleAliases(std::vector<std::string> args,
FrontendOptions &options,
DiagnosticEngine &diags);
};

} // namespace swift

#endif /* SWIFT_FRONTEND_ARGSTOFRONTENDOPTIONSCONVERTER_H */
5 changes: 5 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ void CompilerInvocation::setSDKPath(const std::string &Path) {
updateRuntimeLibraryPaths(SearchPathOpts, LangOpts.Target);
}

bool CompilerInvocation::setModuleAliasMap(std::vector<std::string> args,
DiagnosticEngine &diags) {
return ModuleAliasesConverter::computeModuleAliases(args, FrontendOpts, diags);
}

static bool ParseFrontendArgs(
FrontendOptions &opts, ArgList &args, DiagnosticEngine &diags,
SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> *buffers) {
Expand Down
13 changes: 12 additions & 1 deletion lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2204,7 +2204,18 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
SemanticContextKind::None,
expectedTypeContext);
Builder.setAssociatedDecl(MD);
Builder.addBaseName(MD->getNameStr());
auto moduleName = MD->getName();

// This checks if module aliasing was used. For example, when editing
// `import ...`, and `-module-alias Foo=Bar` was passed, we want to show
// Foo as an option to import, instead of Bar (name of the binary), as
// Foo is the name that should appear in source files.
auto aliasedName = Ctx.getRealModuleName(moduleName, ASTContext::ModuleAliasLookupOption::aliasFromRealName);
Comment on lines +2207 to +2213
Copy link
Member

@rintaro rintaro Dec 11, 2021

Choose a reason for hiding this comment

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

IIUC, MD->getName() should return the aliased name in the first place. I guess this is needed because the names returned from ASTContext::getVisibleTopLevelModuleNames() are "real" names, right? Is it possible to modify it (or *ModuleLoader::collectVisibleTopLevelModuleNames()) to return aliased names?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, the names here are the "real" names (binary names) that were searched through the search paths. Moving the logic to return aliases to those functions doesn't seem ideal as they are lower-level functions that should not have know about the input flag -module-alias.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per discussion, this is to be addressed in a separate PR if needed after merging this. cc @rintaro

if (aliasedName != moduleName && // check if module aliasing was applied
!aliasedName.empty()) { // check an alias mapped to the binary name exists
moduleName = aliasedName; // if so, use the aliased name
}
Builder.addBaseName(moduleName.str());
Builder.addTypeAnnotation("Module");
if (R)
Builder.setNotRecommended(*R);
Expand Down
80 changes: 80 additions & 0 deletions test/IDE/module-aliasing-batch-code-complete.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// RUN: %empty-directory(%t)
// RUN: %{python} %utils/split_file.py -o %t %s

// RUN: %empty-directory(%t/Modules)
// RUN: %target-swift-frontend -emit-module -module-name AppleLogging -module-alias XLogging=AppleLogging -o %t/Modules %t/FileLogging.swift

// BEGIN FileLogging.swift
public struct Logger {
public init() {}
}

public protocol Logging {
var name: String { get }
}

public func setupLogger() -> XLogging.Logger? {
return Logger()
}

// RUN: %empty-directory(%t/Out)
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %t/FileLib1.swift -module-alias XLogging=AppleLogging -filecheck %raw-FileCheck -completion-output-dir %t/Out -I %t/Modules

// BEGIN FileLib1.swift
import XLogging

func testModuleNameInBody() {
#^EXPR^#
}

// EXPR: Begin completion
// EXPR-NOT: AppleLogging
// EXPR-DAG: Decl[Module]/None: XLogging[#Module#]; name=XLogging
// EXPR-DAG: Decl[Protocol]/OtherModule[XLogging]/Flair[RareType]: Logging[#Logging#]; name=Logging
// EXPR-DAG: Decl[Struct]/OtherModule[XLogging]: Logger[#Logger#]; name=Logger
// EXPR-DAG: Decl[FreeFunction]/OtherModule[XLogging]: setupLogger()[#Logger?#]; name=setupLogger()
// EXPR: End completions

// RUN: %empty-directory(%t/Out)
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %t/FileLib2.swift -module-alias XLogging=AppleLogging -filecheck %raw-FileCheck -completion-output-dir %t/Out -I %t/Modules

// BEGIN FileLib2.swift
import XLogging

class ModuleNameInClause: #^MODULE^# {
}

// MODULE: Begin completion
// MODULE-NOT: AppleLogging
// MODULE-DAG: Decl[Module]/None: XLogging[#Module#]; name=XLogging

// MODULE-DAG: Decl[Protocol]/OtherModule[XLogging]: Logging[#Logging#]; name=Logging
// MODULE-DAG: Decl[Struct]/OtherModule[XLogging]: Logger[#Logger#]; name=Logger
// MODULE: End completions

// RUN: %empty-directory(%t/Out)
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %t/FileLib3.swift -module-alias XLogging=AppleLogging -filecheck %raw-FileCheck -completion-output-dir %t/Out -I %t/Modules

// BEGIN FileLib3.swift
import XLogging

func testModuleNameInDecl() -> #^TYPE^# {
}

// TYPE: Begin completion
// TYPE-NOT: AppleLogging
// TYPE-DAG: Decl[Module]/None: XLogging[#Module#]; name=XLogging
// TYPE-DAG: Decl[Protocol]/OtherModule[XLogging]: Logging[#Logging#]; name=Logging
// TYPE-DAG: Decl[Struct]/OtherModule[XLogging]: Logger[#Logger#]; name=Logger
// TYPE: End completions

// RUN: %empty-directory(%t/Out)
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %t/FileLib4.swift -module-alias XLogging=AppleLogging -filecheck %raw-FileCheck -completion-output-dir %t/Out -I %t/Modules

// BEGIN FileLib4.swift
import #^IMPORT^#

// IMPORT: Begin completion
// IMPORT-NOT: AppleLogging
// IMPORT: Decl[Module]/None: XLogging[#Module#]; name=XLogging
// IMPORT: End completions
Loading