Skip to content

Commit 533ae59

Browse files
committed
Support module selectors in scoped imports
1 parent e5233b2 commit 533ae59

File tree

9 files changed

+134
-14
lines changed

9 files changed

+134
-14
lines changed

CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public let DECL_NODES: [Node] = [
2828
),
2929
Child(
3030
name: "trailingPeriod",
31-
kind: .token(choices: [.token(.period)]),
31+
kind: .token(choices: [.token(.period), .token(.colonColon)]),
3232
isOptional: true
3333
),
3434
],

CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,6 @@ class ValidateSyntaxNodes: XCTestCase {
357357
"child 'leadingComma' has a comma keyword as its only token choice and should thus be named 'comma' or 'trailingComma'"
358358
),
359359
// This is similar to `TrailingComma`
360-
ValidationFailure(
361-
node: .importPathComponent,
362-
message: "child 'trailingPeriod' has a token as its only token choice and should thus be named 'period'"
363-
),
364360
// `~` is the only operator that’s allowed here
365361
ValidationFailure(
366362
node: .suppressedType,

Sources/SwiftParser/Declarations.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ extension Parser {
431431
) -> RawImportDeclSyntax {
432432
let (unexpectedBeforeImportKeyword, importKeyword) = self.eat(handle)
433433
let kind = self.parseImportKind()
434-
let path = self.parseImportPath()
434+
let path = self.parseImportPath(hasImportKind: kind != nil)
435435
return RawImportDeclSyntax(
436436
attributes: attrs.attributes,
437437
modifiers: attrs.modifiers,
@@ -447,16 +447,25 @@ extension Parser {
447447
return self.consume(ifAnyIn: ImportDeclSyntax.ImportKindSpecifierOptions.self)
448448
}
449449

450-
mutating func parseImportPath() -> RawImportPathComponentListSyntax {
450+
mutating func parseImportPath(hasImportKind: Bool) -> RawImportPathComponentListSyntax {
451451
var elements = [RawImportPathComponentSyntax]()
452452
var keepGoing: RawTokenSyntax? = nil
453453
var loopProgress = LoopProgressCondition()
454454
repeat {
455455
let name = self.parseAnyIdentifier()
456-
keepGoing = self.consume(if: .period)
456+
keepGoing = self.consume(ifAnyIn: ImportPathComponentSyntax.TrailingPeriodOptions.self)
457+
458+
// If we got `::` when it's not allowed, reject it
459+
var unexpectedBeforeTrailingPeriod: RawUnexpectedNodesSyntax?
460+
if keepGoing?.tokenKind == .colonColon && !hasImportKind {
461+
unexpectedBeforeTrailingPeriod = RawUnexpectedNodesSyntax([keepGoing], arena: self.arena)
462+
keepGoing = missingToken(.period)
463+
}
464+
457465
elements.append(
458466
RawImportPathComponentSyntax(
459467
name: name,
468+
unexpectedBeforeTrailingPeriod,
460469
trailingPeriod: keepGoing,
461470
arena: self.arena
462471
)

Sources/SwiftParser/generated/Parser+TokenSpecSet.swift

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,40 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
12491249
return .visitChildren
12501250
}
12511251

1252+
public override func visit(_ node: ImportPathComponentSyntax) -> SyntaxVisitorContinueKind {
1253+
if shouldSkip(node) {
1254+
return .skipChildren
1255+
}
1256+
1257+
if let colonColon = node.unexpectedBetweenNameAndTrailingPeriod?.last?.as(TokenSyntax.self),
1258+
colonColon.tokenKind == .colonColon,
1259+
colonColon.isPresent,
1260+
let trailingPeriod = node.trailingPeriod,
1261+
trailingPeriod.tokenKind == .period,
1262+
trailingPeriod.isMissing
1263+
{
1264+
addDiagnostic(
1265+
colonColon,
1266+
.submoduleCannotBeImportedUsingModuleSelector,
1267+
fixIts: [
1268+
FixIt(
1269+
message: ReplaceTokensFixIt(replaceTokens: [colonColon], replacements: [trailingPeriod]),
1270+
changes: [
1271+
.makeMissing(colonColon),
1272+
.makePresent(trailingPeriod),
1273+
]
1274+
)
1275+
],
1276+
handledNodes: [
1277+
colonColon.id,
1278+
trailingPeriod.id,
1279+
]
1280+
)
1281+
}
1282+
1283+
return .visitChildren
1284+
}
1285+
12521286
public override func visit(_ node: InitializerClauseSyntax) -> SyntaxVisitorContinueKind {
12531287
if shouldSkip(node) {
12541288
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ extension DiagnosticMessage where Self == StaticParserError {
233233
public static var subscriptsCannotHaveNames: Self {
234234
.init("subscripts cannot have a name")
235235
}
236+
public static var submoduleCannotBeImportedUsingModuleSelector: Self {
237+
.init("submodule cannot be imported using module selector")
238+
}
236239
public static var tooManyClosingPoundDelimiters: Self {
237240
.init("too many '#' characters in closing delimiter")
238241
}

Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/SwiftParserTest/translated/ModuleSelectorTests.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,37 @@ final class ModuleSelectorTests: ParserTestCase {
2222
}
2323

2424
func testModuleSelectorImports() {
25-
XCTExpectFailure("imports not yet implemented")
26-
2725
assertParse(
2826
"""
29-
import ctypes::bits // FIXME: ban using :: with submodules?
3027
import struct ModuleSelectorTestingKit::A
28+
""",
29+
substructure: ImportDeclSyntax(
30+
importKindSpecifier: .keyword(.struct),
31+
path: [
32+
ImportPathComponentSyntax(
33+
name: .identifier("ModuleSelectorTestingKit"),
34+
trailingPeriod: .colonColonToken()
35+
),
36+
ImportPathComponentSyntax(
37+
name: .identifier("A")
38+
),
39+
]
40+
)
41+
)
42+
43+
assertParse(
3144
"""
45+
import ctypes1️⃣::bits
46+
""",
47+
diagnostics: [
48+
DiagnosticSpec(
49+
message: "submodule cannot be imported using module selector",
50+
fixIts: ["replace '::' with '.'"]
51+
)
52+
],
53+
fixedSource: """
54+
import ctypes.bits
55+
"""
3256
)
3357
}
3458

0 commit comments

Comments
 (0)