Skip to content

Commit 3304526

Browse files
committed
Introduce ExperimentalFeatures
These allow the parser to optionally support new Swift features that haven't yet been enabled by default.
1 parent 5a914f9 commit 3304526

File tree

6 files changed

+69
-12
lines changed

6 files changed

+69
-12
lines changed

Sources/SwiftParser/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_swift_host_library(SwiftParser
1313
CollectionNodes+Parsable.swift
1414
Declarations.swift
1515
Directives.swift
16+
ExperimentalFeatures.swift
1617
Expressions.swift
1718
IncrementalParseTransition.swift
1819
Lookahead.swift
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
extension Parser {
14+
public struct ExperimentalFeatures: OptionSet {
15+
public let rawValue: UInt
16+
public init(rawValue: UInt) {
17+
self.rawValue = rawValue
18+
}
19+
}
20+
}

Sources/SwiftParser/Lookahead.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,36 @@ extension Parser {
2727
/// i.e. how far it looked ahead.
2828
var tokensConsumed: Int = 0
2929

30+
/// The experimental features that have been enabled in the underlying
31+
/// parser.
32+
let experimentalFeatures: ExperimentalFeatures
33+
3034
private init(
3135
lexemes: Lexer.LexemeSequence,
32-
currentToken: Lexer.Lexeme
36+
currentToken: Lexer.Lexeme,
37+
experimentalFeatures: ExperimentalFeatures
3338
) {
3439
self.lexemes = lexemes
3540
self.currentToken = currentToken
41+
self.experimentalFeatures = experimentalFeatures
3642
}
3743

3844
fileprivate init(cloning other: Parser) {
39-
self.init(lexemes: other.lexemes, currentToken: other.currentToken)
45+
self.init(
46+
lexemes: other.lexemes,
47+
currentToken: other.currentToken,
48+
experimentalFeatures: other.experimentalFeatures
49+
)
4050
}
4151

4252
/// Initiates a lookahead session from the current point in this
4353
/// lookahead session.
4454
func lookahead() -> Lookahead {
45-
return Lookahead(lexemes: self.lexemes, currentToken: self.currentToken)
55+
return Lookahead(
56+
lexemes: self.lexemes,
57+
currentToken: self.currentToken,
58+
experimentalFeatures: self.experimentalFeatures
59+
)
4660
}
4761
}
4862

Sources/SwiftParser/ParseSourceFile.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ extension Parser {
2222
return SourceFileSyntax.parse(from: &parser)
2323
}
2424

25+
/// A compiler-only interface that allows the enabling of experimental
26+
/// features.
27+
public static func parse(
28+
source: UnsafeBufferPointer<UInt8>,
29+
experimentalFeatures: ExperimentalFeatures
30+
) -> SourceFileSyntax {
31+
var parser = Parser(source, experimentalFeatures: experimentalFeatures)
32+
return SourceFileSyntax.parse(from: &parser)
33+
}
34+
2535
/// Parse the source code in the given buffer as Swift source file. See
2636
/// `Parser.init` for more details.
2737
public static func parse(

Sources/SwiftParser/Parser.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ public struct Parser {
115115
/// Parser should own a ``LookaheadTracker`` so that we can share one `furthestOffset` in a parse.
116116
let lookaheadTrackerOwner = LookaheadTrackerOwner()
117117

118+
/// The experimental features that have been enabled.
119+
let experimentalFeatures: ExperimentalFeatures
120+
118121
/// A default maximum nesting level that is used if the client didn't
119122
/// explicitly specify one. Debug builds of the parser consume a lot more stack
120123
/// space and thus have a lower default maximum nesting level.
@@ -128,9 +131,11 @@ public struct Parser {
128131
public init(
129132
_ input: String,
130133
maximumNestingLevel: Int? = nil,
131-
parseTransition: IncrementalParseTransition? = nil
134+
parseTransition: IncrementalParseTransition? = nil,
135+
experimentalFeatures: ExperimentalFeatures = []
132136
) {
133137
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
138+
self.experimentalFeatures = experimentalFeatures
134139

135140
self.arena = ParsingSyntaxArena(
136141
parseTriviaFunction: TriviaParser.parseTrivia(_:position:)
@@ -169,9 +174,11 @@ public struct Parser {
169174
_ input: UnsafeBufferPointer<UInt8>,
170175
maximumNestingLevel: Int? = nil,
171176
parseTransition: IncrementalParseTransition? = nil,
172-
arena: ParsingSyntaxArena? = nil
177+
arena: ParsingSyntaxArena? = nil,
178+
experimentalFeatures: ExperimentalFeatures = []
173179
) {
174180
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
181+
self.experimentalFeatures = experimentalFeatures
175182

176183
var sourceBuffer: UnsafeBufferPointer<UInt8>
177184
if let arena {

Tests/SwiftParserTest/Assertions.swift

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ func assertParse(
537537
applyFixIts: [String]? = nil,
538538
fixedSource expectedFixedSource: String? = nil,
539539
options: AssertParseOptions = [],
540+
experimentalFeatures: Parser.ExperimentalFeatures = [],
540541
file: StaticString = #file,
541542
line: UInt = #line
542543
) {
@@ -549,6 +550,7 @@ func assertParse(
549550
applyFixIts: applyFixIts,
550551
fixedSource: expectedFixedSource,
551552
options: options,
553+
experimentalFeatures: experimentalFeatures,
552554
file: file,
553555
line: line
554556
)
@@ -559,14 +561,15 @@ func assertParse(
559561
fileprivate func assertRoundTrip<S: SyntaxProtocol>(
560562
source: [UInt8],
561563
_ parse: (inout Parser) -> S,
564+
experimentalFeatures: Parser.ExperimentalFeatures,
562565
file: StaticString,
563566
line: UInt
564567
) {
565568
source.withUnsafeBufferPointer { buf in
566569
let mutatedSource = String(decoding: buf, as: UTF8.self)
567570
// Check that we don't hit any assertions in the parser while parsing
568571
// the mutated source and that it round-trips
569-
var mutatedParser = Parser(buf)
572+
var mutatedParser = Parser(buf, experimentalFeatures: experimentalFeatures)
570573
let mutatedTree = parse(&mutatedParser)
571574
// Run the diagnostic generator to make sure it doesn’t crash
572575
_ = ParseDiagnosticsGenerator.diagnostics(for: mutatedTree)
@@ -615,6 +618,7 @@ func assertParse<S: SyntaxProtocol>(
615618
applyFixIts: [String]? = nil,
616619
fixedSource expectedFixedSource: String? = nil,
617620
options: AssertParseOptions = [],
621+
experimentalFeatures: Parser.ExperimentalFeatures = [],
618622
file: StaticString = #file,
619623
line: UInt = #line
620624
) {
@@ -624,7 +628,7 @@ func assertParse<S: SyntaxProtocol>(
624628

625629
let enableLongTests = ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] != "1"
626630

627-
var parser = Parser(source)
631+
var parser = Parser(source, experimentalFeatures: experimentalFeatures)
628632
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
629633
if enableLongTests {
630634
parser.enableAlternativeTokenChoices()
@@ -704,14 +708,14 @@ func assertParse<S: SyntaxProtocol>(
704708
}
705709

706710
if expectedDiagnostics.isEmpty {
707-
assertBasicFormat(source: source, parse: parse, file: file, line: line)
711+
assertBasicFormat(source: source, parse: parse, experimentalFeatures: experimentalFeatures, file: file, line: line)
708712
}
709713

710714
if enableLongTests {
711715
DispatchQueue.concurrentPerform(iterations: Array(tree.tokens(viewMode: .all)).count) { tokenIndex in
712716
let flippedTokenTree = TokenPresenceFlipper(flipTokenAtIndex: tokenIndex).rewrite(Syntax(tree))
713717
_ = ParseDiagnosticsGenerator.diagnostics(for: flippedTokenTree)
714-
assertRoundTrip(source: flippedTokenTree.syntaxTextBytes, parse, file: file, line: line)
718+
assertRoundTrip(source: flippedTokenTree.syntaxTextBytes, parse, experimentalFeatures: experimentalFeatures, file: file, line: line)
715719
}
716720

717721
#if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION
@@ -721,7 +725,7 @@ func assertParse<S: SyntaxProtocol>(
721725
DispatchQueue.concurrentPerform(iterations: mutations.count) { index in
722726
let mutation = mutations[index]
723727
let alternateSource = MutatedTreePrinter.print(tree: Syntax(tree), mutations: [mutation.offset: mutation.replacement])
724-
assertRoundTrip(source: alternateSource, parse, file: file, line: line)
728+
assertRoundTrip(source: alternateSource, parse, experimentalFeatures: experimentalFeatures, file: file, line: line)
725729
}
726730
#endif
727731
}
@@ -748,15 +752,16 @@ class TriviaRemover: SyntaxRewriter {
748752
func assertBasicFormat<S: SyntaxProtocol>(
749753
source: String,
750754
parse: (inout Parser) -> S,
755+
experimentalFeatures: Parser.ExperimentalFeatures,
751756
file: StaticString = #file,
752757
line: UInt = #line
753758
) {
754-
var parser = Parser(source)
759+
var parser = Parser(source, experimentalFeatures: experimentalFeatures)
755760
let sourceTree = parse(&parser)
756761
let withoutTrivia = TriviaRemover(viewMode: .sourceAccurate).rewrite(sourceTree)
757762
let formatted = withoutTrivia.formatted()
758763

759-
var formattedParser = Parser(formatted.description)
764+
var formattedParser = Parser(formatted.description, experimentalFeatures: experimentalFeatures)
760765
let formattedReparsed = Syntax(parse(&formattedParser))
761766

762767
do {

0 commit comments

Comments
 (0)