Skip to content

Commit 6b72ff0

Browse files
committed
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.
1 parent f778bee commit 6b72ff0

File tree

7 files changed

+190
-17
lines changed

7 files changed

+190
-17
lines changed

include/swift/Parse/Lexer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,10 @@ class Lexer {
400400
// its name into runtime metadata.
401401
static bool identifierMustAlwaysBeEscaped(StringRef str);
402402

403+
/// Determines if the given string is a valid non-operator
404+
/// identifier if it were surrounded by backticks.
405+
static bool isValidAsEscapedIdentifier(StringRef identifier);
406+
403407
/// Determine the token kind of the string, given that it is a valid
404408
/// non-operator identifier. Return tok::identifier if the string is not a
405409
/// reserved word.

lib/Frontend/ArgsToFrontendOptionsConverter.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -936,9 +936,12 @@ bool ModuleAliasesConverter::computeModuleAliases(std::vector<std::string> args,
936936
// it should be called only once
937937
options.ModuleAliasMap.clear();
938938

939-
auto validate = [&options, &diags](StringRef value, bool allowModuleName) -> bool
940-
{
941-
if (!allowModuleName) {
939+
// validatingModuleName should be true if validating the alias target (an
940+
// actual module name), or true if validating the alias name (which can be
941+
// an escaped identifier).
942+
auto validate = [&options, &diags](StringRef value,
943+
bool validatingModuleName) -> bool {
944+
if (!validatingModuleName) {
942945
if (value == options.ModuleName ||
943946
value == options.ModuleABIName ||
944947
value == options.ModuleLinkName ||
@@ -947,7 +950,8 @@ bool ModuleAliasesConverter::computeModuleAliases(std::vector<std::string> args,
947950
return false;
948951
}
949952
}
950-
if (!Lexer::isIdentifier(value)) {
953+
if ((validatingModuleName && !Lexer::isIdentifier(value)) ||
954+
!Lexer::isValidAsEscapedIdentifier(value)) {
951955
diags.diagnose(SourceLoc(), diag::error_bad_module_name, value, false);
952956
return false;
953957
}

lib/Parse/Lexer.cpp

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -581,26 +581,40 @@ static bool isValidIdentifierStartCodePoint(uint32_t c) {
581581
return true;
582582
}
583583

584+
static bool isForbiddenRawIdentifierWhitespace(uint32_t c) {
585+
if ((c >= 0x0009 && c <= 0x000D) ||
586+
c == 0x0085 ||
587+
c == 0x00A0 ||
588+
c == 0x1680 ||
589+
(c >= 0x2000 && c <= 0x200A) ||
590+
(c >= 0x2028 && c <= 0x2029) ||
591+
c == 0x202F ||
592+
c == 0x205F ||
593+
c == 0x3000)
594+
return true;
595+
596+
return false;
597+
}
598+
599+
static bool isPermittedRawIdentifierWhitespace(uint32_t c) {
600+
return c == 0x0020 || c == 0x200E || c == 0x200F;
601+
}
602+
584603
static bool isValidIdentifierEscapedCodePoint(uint32_t c) {
585604
// An escaped identifier is terminated by a backtick, and the backslash is
586605
// reserved for possible future escaping.
587606
if (c == '`' || c == '\\')
588607
return false;
589608

609+
if ((c >= 0x0000 && c <= 0x001F) || c == 0x007F)
610+
return false;
611+
590612
// This is the set of code points satisfying the `White_Space` property,
591613
// excluding the set satisfying the `Pattern_White_Space` property, and
592614
// excluding any other ASCII non-printables and Unicode separators. In
593615
// other words, the only whitespace code points allowed in a raw
594616
// identifier are U+0020, and U+200E/200F (LTR/RTL marks).
595-
if ((c >= 0x0009 && c <= 0x000D) ||
596-
c == 0x0085 ||
597-
c == 0x00A0 ||
598-
c == 0x1680 ||
599-
(c >= 0x2000 && c <= 0x200A) ||
600-
(c >= 0x2028 && c <= 0x2029) ||
601-
c == 0x202F ||
602-
c == 0x205F ||
603-
c == 0x3000)
617+
if (isForbiddenRawIdentifierWhitespace(c))
604618
return false;
605619

606620
return true;
@@ -644,6 +658,17 @@ static bool advanceIfValidContinuationOfOperator(char const *&ptr,
644658
return advanceIf(ptr, end, Identifier::isOperatorContinuationCodePoint);
645659
}
646660

661+
/// Returns true if the given string is entirely whitespace (considering only
662+
/// those whitespace code points permitted in raw identifiers).
663+
static bool isEntirelyWhitespace(StringRef string) {
664+
if (string.empty()) return false;
665+
char const *p = string.data(), *end = string.end();
666+
if (!advanceIf(p, end, isPermittedRawIdentifierWhitespace))
667+
return false;
668+
while (p < end && advanceIf(p, end, isPermittedRawIdentifierWhitespace));
669+
return p == end;
670+
}
671+
647672
bool Lexer::isIdentifier(StringRef string) {
648673
if (string.empty()) return false;
649674
char const *p = string.data(), *end = string.end();
@@ -665,6 +690,19 @@ bool Lexer::identifierMustAlwaysBeEscaped(StringRef str) {
665690
return mustEscape;
666691
}
667692

693+
bool Lexer::isValidAsEscapedIdentifier(StringRef string) {
694+
if (string.empty())
695+
return false;
696+
char const *p = string.data(), *end = string.end();
697+
if (!advanceIfValidEscapedIdentifier(p, end))
698+
return false;
699+
while (p < end && advanceIfValidEscapedIdentifier(p, end))
700+
;
701+
if (p != end)
702+
return false;
703+
return !isEntirelyWhitespace(string);
704+
}
705+
668706
/// Determines if the given string is a valid operator identifier,
669707
/// without escaping characters.
670708
bool Lexer::isOperator(StringRef string) {
@@ -2271,10 +2309,10 @@ void Lexer::lexEscapedIdentifier() {
22712309
while (advanceIfValidEscapedIdentifier(CurPtr, BufferEnd))
22722310
;
22732311

2274-
// If we have the terminating "`", it's an escaped identifier, unless it
2275-
// contained only operator characters.
2276-
if (*CurPtr == '`' &&
2277-
!isOperator(StringRef(IdentifierStart, CurPtr - IdentifierStart))) {
2312+
// If we have the terminating "`", it's an escaped/raw identifier, unless it
2313+
// contained only operator characters or was entirely whitespace.
2314+
StringRef IdStr(IdentifierStart, CurPtr - IdentifierStart);
2315+
if (*CurPtr == '`' && !isOperator(IdStr) && !isEntirelyWhitespace(IdStr)) {
22782316
++CurPtr;
22792317
formEscapedIdentifierToken(Quote);
22802318
return;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: %empty-directory(%t)
2+
// 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
3+
4+
// RUN: %FileCheck %s -input-file %t/load-result.output -check-prefix CHECK-FOO
5+
// CHECK-FOO: import `//my/project:uncommon_name`
6+
// CHECK-FOO-NEXT: remark: loaded module 'CommonName'
7+
8+
import `//my/project:uncommon_name`
9+
10+
_ = MyStruct()
11+
_ = `//my/project:uncommon_name`.MyStruct()

test/ClangImporter/module-alias.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: %empty-directory(%t)
2+
// 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
3+
4+
// RUN: %FileCheck %s -input-file %t/load-result.output -check-prefix CHECK-FOO
5+
// CHECK-FOO: import UncommonName
6+
// CHECK-FOO-NEXT: remark: loaded module 'CommonName'
7+
8+
import UncommonName
9+
10+
_ = MyStruct()
11+
_ = UncommonName.MyStruct()
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/// Test the -module-alias flag with an escaped identifier alias.
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %{python} %utils/split_file.py -o %t %s
5+
6+
/// Create a module Bar
7+
// RUN: %target-swift-frontend -module-name Bar %t/FileBar.swift -emit-module -emit-module-path %t/Bar.swiftmodule
8+
9+
/// Check Bar.swiftmodule is created
10+
// RUN: test -f %t/Bar.swiftmodule
11+
12+
/// Create a module Foo that imports `//my/project:cat` with -module-alias "//my/project:cat=Bar" with a serialized module loader
13+
// 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
14+
15+
/// Check Foo.swiftmodule is created and Bar.swiftmodule is loaded
16+
// RUN: test -f %t/Foo.swiftmodule
17+
// RUN: test -f %t/Bar.swiftmodule
18+
// RUN: not test -f %t/*cat.swiftmodule
19+
20+
// RUN: %FileCheck %s -input-file %t/load-result-foo.output -check-prefix CHECK-FOO
21+
// CHECK-FOO: remark: loaded module {{.*}}Bar.swiftmodule
22+
23+
/// Create a module Zoo that imports `//my/project:cat` with -module-alias "//my/project:cat=Bar" with a source loader
24+
// 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
25+
26+
// RUN: test -f %t/Zoo.swiftmodule
27+
// RUN: test -f %t/Bar.swiftmodule
28+
// RUN: not test -f %t/*cat.swiftmodule
29+
30+
// RUN: %FileCheck %s -input-file %t/load-result-zoo.output -check-prefix CHECK-ZOO
31+
// CHECK-ZOO: remark: loaded module {{.*}}Bar.swiftmodule
32+
33+
34+
// BEGIN FileBar.swift
35+
public func bar() {}
36+
37+
// BEGIN FileFoo.swift
38+
import `//my/project:cat`
39+
40+
`//my/project:cat`.bar()
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/// Test the -module-alias flag with an escaped identifier and the explicit module loader.
2+
// UNSUPPORTED: OS=windows-msvc
3+
// RUN: %empty-directory(%t)
4+
// RUN: mkdir -p %t/inputs
5+
// RUN: mkdir -p %t/outputs
6+
7+
/// Create a module Bar
8+
// RUN: echo 'public func bar() {}' > %t/inputs/FileBar.swift
9+
// RUN: %target-swift-frontend -module-name Bar %t/inputs/FileBar.swift -emit-module -emit-module-path %t/inputs/Bar.swiftmodule
10+
// RUN: %target-swift-emit-pcm -module-name SwiftShims %swift-lib-dir/swift/shims/module.modulemap -o %t/inputs/SwiftShims.pcm
11+
// RUN: %target-swift-emit-pcm -module-name _SwiftConcurrencyShims %swift-lib-dir/swift/shims/module.modulemap -o %t/inputs/_SwiftConcurrencyShims.pcm
12+
13+
/// Check Bar.swiftmodule is created
14+
// RUN: test -f %t/inputs/Bar.swiftmodule
15+
16+
/// Next create an explicit module dependency map to build module Foo
17+
// RUN: echo 'import `//my/project:cat`' > %t/inputs/FileFoo.swift
18+
19+
// RUN: echo "[{" > %/t/inputs/map.json
20+
// RUN: echo "\"moduleName\": \"Bar\"," >> %/t/inputs/map.json
21+
// RUN: echo "\"modulePath\": \"%/t/inputs/Bar.swiftmodule\"," >> %/t/inputs/map.json
22+
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
23+
// RUN: echo "}," >> %/t/inputs/map.json
24+
// RUN: echo "{" >> %/t/inputs/map.json
25+
// RUN: echo "\"moduleName\": \"Swift\"," >> %/t/inputs/map.json
26+
// RUN: echo "\"modulePath\": \"%/stdlib_module\"," >> %/t/inputs/map.json
27+
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
28+
// RUN: echo "}," >> %/t/inputs/map.json
29+
// RUN: echo "{" >> %/t/inputs/map.json
30+
// RUN: echo "\"moduleName\": \"SwiftOnoneSupport\"," >> %/t/inputs/map.json
31+
// RUN: echo "\"modulePath\": \"%/ononesupport_module\"," >> %/t/inputs/map.json
32+
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
33+
// RUN: echo "}," >> %/t/inputs/map.json
34+
// RUN: echo "{" >> %/t/inputs/map.json
35+
// RUN: echo "\"moduleName\": \"_Concurrency\"," >> %/t/inputs/map.json
36+
// RUN: echo "\"modulePath\": \"%/concurrency_module\"," >> %/t/inputs/map.json
37+
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
38+
// RUN: echo "}," >> %/t/inputs/map.json
39+
// RUN: echo "{" >> %/t/inputs/map.json
40+
// RUN: echo "\"moduleName\": \"SwiftShims\"," >> %/t/inputs/map.json
41+
// RUN: echo "\"isFramework\": false," >> %/t/inputs/map.json
42+
// RUN: echo "\"clangModuleMapPath\": \"%swift-lib-dir/swift/shims/module.modulemap\"," >> %/t/inputs/map.json
43+
// RUN: echo "\"clangModulePath\": \"%t/inputs/SwiftShims.pcm\"" >> %/t/inputs/map.json
44+
// RUN: echo "}," >> %/t/inputs/map.json
45+
// RUN: echo "{" >> %/t/inputs/map.json
46+
// RUN: echo "\"moduleName\": \"_SwiftConcurrencyShims\"," >> %/t/inputs/map.json
47+
// RUN: echo "\"isFramework\": false," >> %/t/inputs/map.json
48+
// RUN: echo "\"clangModuleMapPath\": \"%swift-lib-dir/swift/shims/module.modulemap\"," >> %/t/inputs/map.json
49+
// RUN: echo "\"clangModulePath\": \"%t/inputs/_SwiftConcurrencyShims.pcm\"" >> %/t/inputs/map.json
50+
// RUN: echo "}," >> %/t/inputs/map.json
51+
// RUN: echo "{" >> %/t/inputs/map.json
52+
// RUN: echo "\"moduleName\": \"_StringProcessing\"," >> %/t/inputs/map.json
53+
// RUN: echo "\"modulePath\": \"%/string_processing_module\"," >> %/t/inputs/map.json
54+
// RUN: echo "\"isFramework\": false" >> %/t/inputs/map.json
55+
// RUN: echo "}]" >> %/t/inputs/map.json
56+
57+
/// Create a module Foo that imports `//my/project:cat` with -module-alias "//my/project:cat=Bar" with an explicit module loader
58+
// 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
59+
60+
// RUN: test -f %t/outputs/Foo.swiftmodule
61+
// RUN: test -f %t/inputs/Bar.swiftmodule
62+
// RUN: not test -f %t/inputs/*cat.swiftmodule
63+
64+
// RUN: %FileCheck %s -input-file %t/outputs/load-result.output -check-prefix CHECK
65+
// CHECK: remark: loaded module {{.*}}Bar.swiftmodule

0 commit comments

Comments
 (0)