Allow module aliases to use escaped identifiers as the alias name.

The original module names themselves must still be valid unescaped identifiers; most of the serialization logic in the compiler depends on the name of a module matching its name on the file system, and it would be very complex to turn escaped identifiers into file-safe names.
This commit is contained in:
Tony Allevato
2024-06-29 14:48:29 -04:00
committed by Tony Allevato
parent 329261593e
commit 2b0f9aa765
7 changed files with 192 additions and 19 deletions

View File

@@ -400,6 +400,10 @@ public:
// its name into runtime metadata.
static bool identifierMustAlwaysBeEscaped(StringRef str);
/// Determines if the given string is a valid non-operator
/// identifier if it were surrounded by backticks.
static bool isValidAsEscapedIdentifier(StringRef identifier);
/// Determine the token kind of the string, given that it is a valid
/// non-operator identifier. Return tok::identifier if the string is not a
/// reserved word.

View File

@@ -936,9 +936,12 @@ bool ModuleAliasesConverter::computeModuleAliases(std::vector<std::string> args,
// it should be called only once
options.ModuleAliasMap.clear();
auto validate = [&options, &diags](StringRef value, bool allowModuleName) -> bool
{
if (!allowModuleName) {
// validatingModuleName should be true if validating the alias target (an
// actual module name), or true if validating the alias name (which can be
// an escaped identifier).
auto validate = [&options, &diags](StringRef value,
bool validatingModuleName) -> bool {
if (!validatingModuleName) {
if (value == options.ModuleName ||
value == options.ModuleABIName ||
value == options.ModuleLinkName ||
@@ -947,7 +950,8 @@ bool ModuleAliasesConverter::computeModuleAliases(std::vector<std::string> args,
return false;
}
}
if (!Lexer::isIdentifier(value)) {
if ((validatingModuleName && !Lexer::isIdentifier(value)) ||
!Lexer::isValidAsEscapedIdentifier(value)) {
diags.diagnose(SourceLoc(), diag::error_bad_module_name, value, false);
return false;
}

View File

@@ -581,17 +581,7 @@ static bool isValidIdentifierStartCodePoint(uint32_t c) {
return true;
}
static bool isValidIdentifierEscapedCodePoint(uint32_t c) {
// An escaped identifier is terminated by a backtick, and the backslash is
// reserved for possible future escaping.
if (c == '`' || c == '\\')
return false;
// This is the set of code points satisfying the `White_Space` property,
// excluding the set satisfying the `Pattern_White_Space` property, and
// excluding any other ASCII non-printables and Unicode separators. In
// other words, the only whitespace code points allowed in a raw
// identifier are U+0020, and U+200E/200F (LTR/RTL marks).
static bool isForbiddenRawIdentifierWhitespace(uint32_t c) {
if ((c >= 0x0009 && c <= 0x000D) ||
c == 0x0085 ||
c == 0x00A0 ||
@@ -601,6 +591,30 @@ static bool isValidIdentifierEscapedCodePoint(uint32_t c) {
c == 0x202F ||
c == 0x205F ||
c == 0x3000)
return true;
return false;
}
static bool isPermittedRawIdentifierWhitespace(uint32_t c) {
return c == 0x0020 || c == 0x200E || c == 0x200F;
}
static bool isValidIdentifierEscapedCodePoint(uint32_t c) {
// An escaped identifier is terminated by a backtick, and the backslash is
// reserved for possible future escaping.
if (c == '`' || c == '\\')
return false;
if ((c >= 0x0000 && c <= 0x001F) || c == 0x007F)
return false;
// This is the set of code points satisfying the `White_Space` property,
// excluding the set satisfying the `Pattern_White_Space` property, and
// excluding any other ASCII non-printables and Unicode separators. In
// other words, the only whitespace code points allowed in a raw
// identifier are U+0020, and U+200E/200F (LTR/RTL marks).
if (isForbiddenRawIdentifierWhitespace(c))
return false;
return true;
@@ -644,6 +658,17 @@ static bool advanceIfValidContinuationOfOperator(char const *&ptr,
return advanceIf(ptr, end, Identifier::isOperatorContinuationCodePoint);
}
/// Returns true if the given string is entirely whitespace (considering only
/// those whitespace code points permitted in raw identifiers).
static bool isEntirelyWhitespace(StringRef string) {
if (string.empty()) return false;
char const *p = string.data(), *end = string.end();
if (!advanceIf(p, end, isPermittedRawIdentifierWhitespace))
return false;
while (p < end && advanceIf(p, end, isPermittedRawIdentifierWhitespace));
return p == end;
}
bool Lexer::isIdentifier(StringRef string) {
if (string.empty()) return false;
char const *p = string.data(), *end = string.end();
@@ -665,6 +690,19 @@ bool Lexer::identifierMustAlwaysBeEscaped(StringRef str) {
return mustEscape;
}
bool Lexer::isValidAsEscapedIdentifier(StringRef string) {
if (string.empty())
return false;
char const *p = string.data(), *end = string.end();
if (!advanceIfValidEscapedIdentifier(p, end))
return false;
while (p < end && advanceIfValidEscapedIdentifier(p, end))
;
if (p != end)
return false;
return !isEntirelyWhitespace(string);
}
/// Determines if the given string is a valid operator identifier,
/// without escaping characters.
bool Lexer::isOperator(StringRef string) {
@@ -2271,10 +2309,10 @@ void Lexer::lexEscapedIdentifier() {
while (advanceIfValidEscapedIdentifier(CurPtr, BufferEnd))
;
// If we have the terminating "`", it's an escaped identifier, unless it
// contained only operator characters.
if (*CurPtr == '`' &&
!isOperator(StringRef(IdentifierStart, CurPtr - IdentifierStart))) {
// If we have the terminating "`", it's an escaped/raw identifier, unless it
// contained only operator characters or was entirely whitespace.
StringRef IdStr(IdentifierStart, CurPtr - IdentifierStart);
if (*CurPtr == '`' && !isOperator(IdStr) && !isEntirelyWhitespace(IdStr)) {
++CurPtr;
formEscapedIdentifierToken(Quote);
return;

View File

@@ -0,0 +1,11 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -module-alias "//my/project:uncommon_name=CommonName" -typecheck -I %S/Inputs/custom-modules %s -Rmodule-loading 2> %t/load-result.output
// RUN: %FileCheck %s -input-file %t/load-result.output -check-prefix CHECK-FOO
// CHECK-FOO: import `//my/project:uncommon_name`
// CHECK-FOO-NEXT: remark: loaded module 'CommonName'
import `//my/project:uncommon_name`
_ = MyStruct()
_ = `//my/project:uncommon_name`.MyStruct()

View File

@@ -0,0 +1,11 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -module-alias UncommonName=CommonName -typecheck -I %S/Inputs/custom-modules %s -Rmodule-loading 2> %t/load-result.output
// RUN: %FileCheck %s -input-file %t/load-result.output -check-prefix CHECK-FOO
// CHECK-FOO: import UncommonName
// CHECK-FOO-NEXT: remark: loaded module 'CommonName'
import UncommonName
_ = MyStruct()
_ = UncommonName.MyStruct()

View File

@@ -0,0 +1,40 @@
/// Test the -module-alias flag with an escaped identifier alias.
// RUN: %empty-directory(%t)
// RUN: %{python} %utils/split_file.py -o %t %s
/// Create a module Bar
// RUN: %target-swift-frontend -module-name Bar %t/FileBar.swift -emit-module -emit-module-path %t/Bar.swiftmodule
/// Check Bar.swiftmodule is created
// RUN: test -f %t/Bar.swiftmodule
/// Create a module Foo that imports `//my/project:cat` with -module-alias "//my/project:cat=Bar" with a serialized module loader
// RUN: %target-swift-frontend -module-name Foo %t/FileFoo.swift -module-alias "//my/project:cat=Bar" -I %t -emit-module -emit-module-path %t/Foo.swiftmodule -Rmodule-loading 2> %t/load-result-foo.output
/// Check Foo.swiftmodule is created and Bar.swiftmodule is loaded
// RUN: test -f %t/Foo.swiftmodule
// RUN: test -f %t/Bar.swiftmodule
// RUN: not test -f %t/*cat.swiftmodule
// RUN: %FileCheck %s -input-file %t/load-result-foo.output -check-prefix CHECK-FOO
// CHECK-FOO: remark: loaded module {{.*}}Bar.swiftmodule
/// Create a module Zoo that imports `//my/project:cat` with -module-alias "//my/project:cat=Bar" with a source loader
// RUN: %target-swift-frontend -module-name Zoo %t/FileFoo.swift -module-alias "//my/project:cat=Bar" -I %t -emit-module -emit-module-path %t/Zoo.swiftmodule -enable-source-import -Rmodule-loading 2> %t/load-result-zoo.output
// RUN: test -f %t/Zoo.swiftmodule
// RUN: test -f %t/Bar.swiftmodule
// RUN: not test -f %t/*cat.swiftmodule
// RUN: %FileCheck %s -input-file %t/load-result-zoo.output -check-prefix CHECK-ZOO
// CHECK-ZOO: remark: loaded module {{.*}}Bar.swiftmodule
// BEGIN FileBar.swift
public func bar() {}
// BEGIN FileFoo.swift
import `//my/project:cat`
`//my/project:cat`.bar()

View File

@@ -0,0 +1,65 @@
/// Test the -module-alias flag with an escaped identifier and the explicit module loader.
// UNSUPPORTED: OS=windows-msvc
// RUN: %empty-directory(%t)
// RUN: mkdir -p %t/inputs
// RUN: mkdir -p %t/outputs
/// Create a module Bar
// RUN: echo 'public func bar() {}' > %t/inputs/FileBar.swift
// RUN: %target-swift-frontend -module-name Bar %t/inputs/FileBar.swift -emit-module -emit-module-path %t/inputs/Bar.swiftmodule
// RUN: %target-swift-emit-pcm -module-name SwiftShims %swift-lib-dir/swift/shims/module.modulemap -o %t/inputs/SwiftShims.pcm
// RUN: %target-swift-emit-pcm -module-name _SwiftConcurrencyShims %swift-lib-dir/swift/shims/module.modulemap -o %t/inputs/_SwiftConcurrencyShims.pcm
/// Check Bar.swiftmodule is created
// RUN: test -f %t/inputs/Bar.swiftmodule
/// Next create an explicit module dependency map to build module Foo
// RUN: echo 'import `//my/project:cat`' > %t/inputs/FileFoo.swift
// RUN: echo "[{" > %/t/inputs/map.json
// RUN: echo "\"moduleName\": \"Bar\"," >> %/t/inputs/map.json
// RUN: echo "\"modulePath\": \"%/t/inputs/Bar.swiftmodule\"," >> %/t/inputs/map.json
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
// RUN: echo "}," >> %/t/inputs/map.json
// RUN: echo "{" >> %/t/inputs/map.json
// RUN: echo "\"moduleName\": \"Swift\"," >> %/t/inputs/map.json
// RUN: echo "\"modulePath\": \"%/stdlib_module\"," >> %/t/inputs/map.json
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
// RUN: echo "}," >> %/t/inputs/map.json
// RUN: echo "{" >> %/t/inputs/map.json
// RUN: echo "\"moduleName\": \"SwiftOnoneSupport\"," >> %/t/inputs/map.json
// RUN: echo "\"modulePath\": \"%/ononesupport_module\"," >> %/t/inputs/map.json
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
// RUN: echo "}," >> %/t/inputs/map.json
// RUN: echo "{" >> %/t/inputs/map.json
// RUN: echo "\"moduleName\": \"_Concurrency\"," >> %/t/inputs/map.json
// RUN: echo "\"modulePath\": \"%/concurrency_module\"," >> %/t/inputs/map.json
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
// RUN: echo "}," >> %/t/inputs/map.json
// RUN: echo "{" >> %/t/inputs/map.json
// RUN: echo "\"moduleName\": \"SwiftShims\"," >> %/t/inputs/map.json
// RUN: echo "\"isFramework\": false," >> %/t/inputs/map.json
// RUN: echo "\"clangModuleMapPath\": \"%swift-lib-dir/swift/shims/module.modulemap\"," >> %/t/inputs/map.json
// RUN: echo "\"clangModulePath\": \"%t/inputs/SwiftShims.pcm\"" >> %/t/inputs/map.json
// RUN: echo "}," >> %/t/inputs/map.json
// RUN: echo "{" >> %/t/inputs/map.json
// RUN: echo "\"moduleName\": \"_SwiftConcurrencyShims\"," >> %/t/inputs/map.json
// RUN: echo "\"isFramework\": false," >> %/t/inputs/map.json
// RUN: echo "\"clangModuleMapPath\": \"%swift-lib-dir/swift/shims/module.modulemap\"," >> %/t/inputs/map.json
// RUN: echo "\"clangModulePath\": \"%t/inputs/_SwiftConcurrencyShims.pcm\"" >> %/t/inputs/map.json
// RUN: echo "}," >> %/t/inputs/map.json
// RUN: echo "{" >> %/t/inputs/map.json
// RUN: echo "\"moduleName\": \"_StringProcessing\"," >> %/t/inputs/map.json
// RUN: echo "\"modulePath\": \"%/string_processing_module\"," >> %/t/inputs/map.json
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
// RUN: echo "}]" >> %/t/inputs/map.json
/// Create a module Foo that imports `//my/project:cat` with -module-alias "//my/project:cat=Bar" with an explicit module loader
// RUN: %target-swift-frontend -module-name Foo %t/inputs/FileFoo.swift -module-alias "//my/project:cat=Bar" -I %t/inputs -emit-module -emit-module-path %t/outputs/Foo.swiftmodule -disable-implicit-swift-modules -explicit-swift-module-map-file %t/inputs/map.json -Rmodule-loading 2> %t/outputs/load-result.output
// RUN: test -f %t/outputs/Foo.swiftmodule
// RUN: test -f %t/inputs/Bar.swiftmodule
// RUN: not test -f %t/inputs/*cat.swiftmodule
// RUN: %FileCheck %s -input-file %t/outputs/load-result.output -check-prefix CHECK
// CHECK: remark: loaded module {{.*}}Bar.swiftmodule