Skip to content

Commit 885bf06

Browse files
committed
Add experimental colonColon token
And make sure it doesn’t break Objective-C selector lexing.
1 parent 078450b commit 885bf06

File tree

16 files changed

+182
-7
lines changed

16 files changed

+182
-7
lines changed

CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public enum ExperimentalFeature: String, CaseIterable {
2222
case keypathWithMethodMembers
2323
case oldOwnershipOperatorSpellings
2424
case inlineArrayTypeSugar
25+
case moduleSelector
2526

2627
/// The name of the feature as it is written in the compiler's `Features.def` file.
2728
public var featureName: String {
@@ -44,6 +45,8 @@ public enum ExperimentalFeature: String, CaseIterable {
4445
return "OldOwnershipOperatorSpellings"
4546
case .inlineArrayTypeSugar:
4647
return "InlineArrayTypeSugar"
48+
case .moduleSelector:
49+
return "ModuleSelector"
4750
}
4851
}
4952

@@ -68,6 +71,8 @@ public enum ExperimentalFeature: String, CaseIterable {
6871
return "`_move` and `_borrow` as ownership operators"
6972
case .inlineArrayTypeSugar:
7073
return "sugar type for InlineArray"
74+
case .moduleSelector:
75+
return "Module selector syntax (`ModName::identifier`)"
7176
}
7277
}
7378

CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ public enum Token: CaseIterable {
126126
case backtick
127127
case binaryOperator
128128
case colon
129+
case colonColon
129130
case comma
130131
case dollarIdentifier
131132
case ellipsis
@@ -185,6 +186,8 @@ public enum Token: CaseIterable {
185186
return .other(name: "binaryOperator", nameForDiagnostics: "binary operator")
186187
case .colon:
187188
return .punctuator(name: "colon", text: ":")
189+
case .colonColon:
190+
return .punctuator(name: "colonColon", text: "::")
188191
case .comma:
189192
return .punctuator(name: "comma", text: ",")
190193
case .dollarIdentifier:

Sources/SwiftIDEUtils/SyntaxClassification.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ extension RawTokenKind {
114114
return .operator
115115
case .colon:
116116
return .none
117+
case .colonColon:
118+
return .operator
117119
case .comma:
118120
return .none
119121
case .dollarIdentifier:

Sources/SwiftParser/Attributes.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,8 @@ extension Parser {
602602
var elements = [RawObjCSelectorPieceSyntax]()
603603
var loopProgress = LoopProgressCondition()
604604
while self.hasProgressed(&loopProgress) {
605-
// Empty selector piece.
606-
if let colon = self.consume(if: .colon) {
605+
// Empty selector piece, splitting `::` into two colons.
606+
if let colon = self.consume(ifPrefix: ":", as: .colon) {
607607
elements.append(
608608
RawObjCSelectorPieceSyntax(
609609
name: nil,
@@ -627,7 +627,8 @@ extension Parser {
627627
break
628628
}
629629

630-
let (unexpectedBeforeColon, colon) = self.expect(.colon)
630+
// Match ending colon, spliting `::` into two colons.
631+
let (unexpectedBeforeColon, colon) = self.expect(prefix: ":", as: .colon)
631632
elements.append(
632633
RawObjCSelectorPieceSyntax(
633634
name: name,

Sources/SwiftParser/Lexer/Cursor.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -943,9 +943,17 @@ extension Lexer.Cursor {
943943

944944
case ",": _ = self.advance(); return Lexer.Result(.comma)
945945
case ";": _ = self.advance(); return Lexer.Result(.semicolon)
946-
case ":": _ = self.advance(); return Lexer.Result(.colon)
947946
case "\\": _ = self.advance(); return Lexer.Result(.backslash)
948947

948+
case ":":
949+
_ = self.advance()
950+
guard self.experimentalFeatures.contains(.moduleSelector) && self.peek() == ":" else {
951+
return Lexer.Result(.colon)
952+
}
953+
954+
_ = self.advance()
955+
return Lexer.Result(.colonColon)
956+
949957
case "#":
950958
// Try lex shebang.
951959
if self.isAtStartOfFile, self.peek(at: 1) == "!" {

Sources/SwiftParser/Lexer/RegexLiteralLexer.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,10 @@ extension Lexer.Cursor {
643643
case .identifier, .dollarIdentifier, .wildcard:
644644
return false
645645

646+
// Module selectors are allowed before an operator, but not a regex.
647+
case .colonColon:
648+
return false
649+
646650
// Literals are themselves expressions and therefore don't sequence expressions.
647651
case .floatLiteral, .integerLiteral:
648652
return false
@@ -737,7 +741,7 @@ extension Lexer.Cursor {
737741
// an unapplied operator is legal, and we should prefer to lex as that
738742
// instead.
739743
switch previousTokenKind {
740-
case .leftParen, .leftSquare, .comma, .colon:
744+
case .leftParen, .leftSquare, .comma, .colon, .colonColon:
741745
break
742746
default:
743747
mustBeRegex = true

Sources/SwiftParser/Parser.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,25 @@ extension Parser {
645645
)
646646
}
647647

648+
649+
/// Attempts to consume a token starting with the given `prefix` and forming it into `tokenKind`.
650+
/// If it cannot be found, the parser tries
651+
/// 1. To eat unexpected tokens that have lower ``TokenPrecedence`` than
652+
/// specified by `TokenSpec(tokenKind)` and see if the token occurs after that unexpected.
653+
/// 2. If the token couldn't be found after skipping unexpected, it synthesizes
654+
/// a missing token of the requested kind.
655+
@inline(__always)
656+
mutating func expect(
657+
prefix: SyntaxText, as tokenKind: RawTokenKind
658+
) -> (unexpected: RawUnexpectedNodesSyntax?, token: RawTokenSyntax) {
659+
let spec = TokenSpec(tokenKind)
660+
return expectImpl(
661+
consume: { $0.consume(ifPrefix: prefix, as: tokenKind) },
662+
canRecoverTo: { $0.canRecoverTo(spec) },
663+
makeMissing: { $0.missingToken(spec) }
664+
)
665+
}
666+
648667
/// If the current token starts with the given prefix, consume the prefis as the given token kind.
649668
///
650669
/// Otherwise, synthesize a missing token of the given kind.

Sources/SwiftParser/Statements.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,9 @@ extension TokenConsumer {
969969
return false
970970
case .semicolon, .endOfFile, .poundElse, .poundElseif, .poundEndif:
971971
return false
972+
case .colonColon:
973+
// E.g. <word> :: <word>
974+
return false
972975

973976
case .leftAngle, .rightAngle:
974977
// Lexer never produce these token kinds.

Sources/SwiftParser/TokenPrecedence.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ enum TokenPrecedence: Comparable {
128128
// Pound literals
129129
.poundAvailable, .poundSourceLocation, .poundUnavailable,
130130
// Identifiers
131-
.dollarIdentifier, .identifier,
131+
.dollarIdentifier, .identifier, .colonColon,
132132
// '_' can occur in types to replace a type identifier
133133
.wildcard,
134134
// String segment, string interpolation anchor, pound, shebang and regex pattern don't really fit anywhere else

Sources/SwiftParser/generated/ExperimentalFeatures.swift

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

Sources/SwiftParser/generated/TokenSpecStaticMembers.swift

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

Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift

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

Sources/SwiftSyntax/generated/TokenKind.swift

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

Sources/SwiftSyntax/generated/Tokens.swift

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

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,23 @@ final class AttributeTests: ParserTestCase {
158158
@objc(zeroArg)
159159
class A { }
160160
161-
@objc(:::::)
161+
@objc(:::x::)
162162
func f(_: Int, _: Int, _: Int, _: Int, _: Int) { }
163163
"""
164164
)
165165

166+
// Same as above, but with module selectors, which introduce a token for adjacent colons.
167+
assertParse(
168+
"""
169+
@objc(zeroArg)
170+
class A { }
171+
172+
@objc(:::x::)
173+
func f(_: Int, _: Int, _: Int, _: Int, _: Int) { }
174+
""",
175+
experimentalFeatures: [.moduleSelector]
176+
)
177+
166178
assertParse(
167179
"""
168180
@objc(_:)

0 commit comments

Comments
 (0)