Skip to content

Commit fb95cbb

Browse files
authored
Merge pull request #2211 from hamishknight/up-to-spec
2 parents c937cb7 + 4695dc7 commit fb95cbb

27 files changed

+354
-240
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
import SwiftSyntax
14+
15+
public enum ExperimentalFeature: String, CaseIterable {
16+
case referenceBindings
17+
case thenStatements
18+
19+
/// The name of the feature, which is used in the doc comment.
20+
public var featureName: String {
21+
switch self {
22+
case .referenceBindings:
23+
return "reference bindings"
24+
case .thenStatements:
25+
return "'then' statements"
26+
}
27+
}
28+
29+
/// The token that represents the experimental feature case name.
30+
public var token: TokenSyntax {
31+
.identifier(rawValue)
32+
}
33+
}

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ public struct KeywordSpec {
1616
/// The name of the keyword.
1717
public let name: String
1818

19+
/// The experimental feature the keyword is part of, or `nil` if this isn't
20+
/// for an experimental feature.
21+
public let experimentalFeature: ExperimentalFeature?
22+
23+
/// Indicates if the token kind is switched from being an identifier to a keyword in the lexer.
24+
public let isLexerClassified: Bool
25+
1926
/// Indicates if the keyword is part of an experimental language feature.
2027
///
2128
/// If `true`, this keyword is for an experimental language feature, and any public
2229
/// API generated should be marked as SPI
23-
public let isExperimental: Bool
24-
25-
/// Indicates if the token kind is switched from being an identifier to a keyword in the lexer.
26-
public let isLexerClassified: Bool
30+
public var isExperimental: Bool { experimentalFeature != nil }
2731

2832
/// The name of this keyword that's suitable to be used for variable or enum case names.
2933
public var varOrCaseName: TokenSyntax {
@@ -46,15 +50,15 @@ public struct KeywordSpec {
4650
///
4751
/// - Parameters:
4852
/// - name: A name of the keyword.
49-
/// - isExperimental: Indicates if the keyword is part of an experimental language feature.
53+
/// - experimentalFeature: The experimental feature the keyword is part of, or `nil` if this isn't for an experimental feature.
5054
/// - isLexerClassified: Indicates if the token kind is switched from being an identifier to a keyword in the lexer.
5155
init(
5256
_ name: String,
53-
isExperimental: Bool = false,
57+
experimentalFeature: ExperimentalFeature? = nil,
5458
isLexerClassified: Bool = false
5559
) {
5660
self.name = name
57-
self.isExperimental = isExperimental
61+
self.experimentalFeature = experimentalFeature
5862
self.isLexerClassified = isLexerClassified
5963
}
6064
}
@@ -692,11 +696,11 @@ public enum Keyword: CaseIterable {
692696
case .yield:
693697
return KeywordSpec("yield")
694698
case ._borrowing:
695-
return KeywordSpec("_borrowing", isExperimental: true)
699+
return KeywordSpec("_borrowing", experimentalFeature: .referenceBindings)
696700
case ._consuming:
697-
return KeywordSpec("_consuming", isExperimental: true)
701+
return KeywordSpec("_consuming", experimentalFeature: .referenceBindings)
698702
case ._mutating:
699-
return KeywordSpec("_mutating", isExperimental: true)
703+
return KeywordSpec("_mutating", experimentalFeature: .referenceBindings)
700704
}
701705
}
702706
}

CodeGeneration/Sources/SyntaxSupport/Node.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public class Node {
4040
/// The kind of node’s supertype. This kind must have `isBase == true`
4141
public let base: SyntaxNodeKind
4242

43-
/// If `true`, this is for an experimental language feature, and any public
44-
/// API generated should be SPI.
45-
public let isExperimental: Bool
43+
/// The experimental feature the node is part of, or `nil` if this isn't
44+
/// for an experimental feature.
45+
public let experimentalFeature: ExperimentalFeature?
4646

4747
/// When the node name is printed for diagnostics, this name is used.
4848
/// If `nil`, `nameForDiagnostics` will print the parent node’s name.
@@ -57,6 +57,10 @@ public class Node {
5757
/// function that should be invoked to create this node.
5858
public let parserFunction: TokenSyntax?
5959

60+
/// If `true`, this is for an experimental language feature, and any public
61+
/// API generated should be SPI.
62+
public var isExperimental: Bool { experimentalFeature != nil }
63+
6064
/// A name for this node that is suitable to be used as a variables or enum
6165
/// case's name.
6266
public var varOrCaseName: TokenSyntax {
@@ -107,7 +111,7 @@ public class Node {
107111
init(
108112
kind: SyntaxNodeKind,
109113
base: SyntaxNodeKind,
110-
isExperimental: Bool = false,
114+
experimentalFeature: ExperimentalFeature? = nil,
111115
nameForDiagnostics: String?,
112116
documentation: String? = nil,
113117
parserFunction: TokenSyntax? = nil,
@@ -119,7 +123,7 @@ public class Node {
119123

120124
self.kind = kind
121125
self.base = base
122-
self.isExperimental = isExperimental
126+
self.experimentalFeature = experimentalFeature
123127
self.nameForDiagnostics = nameForDiagnostics
124128
self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
125129
self.parserFunction = parserFunction
@@ -226,7 +230,7 @@ public class Node {
226230
init(
227231
kind: SyntaxNodeKind,
228232
base: SyntaxNodeKind,
229-
isExperimental: Bool = false,
233+
experimentalFeature: ExperimentalFeature? = nil,
230234
nameForDiagnostics: String?,
231235
documentation: String? = nil,
232236
parserFunction: TokenSyntax? = nil,
@@ -235,7 +239,7 @@ public class Node {
235239
self.kind = kind
236240
precondition(base == .syntaxCollection)
237241
self.base = base
238-
self.isExperimental = isExperimental
242+
self.experimentalFeature = experimentalFeature
239243
self.nameForDiagnostics = nameForDiagnostics
240244
self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
241245
self.parserFunction = parserFunction

CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ public let STMT_NODES: [Node] = [
588588
kind: .thenStmt,
589589
base: .stmt,
590590
// FIXME: This should be marked experimental.
591-
isExperimental: false,
591+
experimentalFeature: nil,
592592
nameForDiagnostics: "'then' statement",
593593
documentation: """
594594
A statement used to indicate the produced value from an if/switch

CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@ public struct TokenSpec {
2424
/// The name of the token, suitable for use in variable or enum case names.
2525
public let varOrCaseName: TokenSyntax
2626

27-
/// Indicates if the token is part of an experimental language feature.
28-
///
29-
/// If `true`, this keyword is for an experimental language feature, and any public
30-
/// API generated should be marked as SPI
31-
public let isExperimental: Bool
27+
/// The experimental feature the token is part of, or `nil` if this isn't
28+
/// for an experimental feature.
29+
public let experimentalFeature: ExperimentalFeature?
3230

3331
/// The name of the token that can be shown in diagnostics.
3432
public let nameForDiagnostics: String
@@ -39,6 +37,12 @@ public struct TokenSpec {
3937
/// The kind of the token.
4038
public let kind: Kind
4139

40+
/// Indicates if the token is part of an experimental language feature.
41+
///
42+
/// If `true`, this keyword is for an experimental language feature, and any public
43+
/// API generated should be marked as SPI
44+
public var isExperimental: Bool { experimentalFeature != nil }
45+
4246
/// The attributes that should be printed on any API for the generated keyword.
4347
///
4448
/// This is typically used to mark APIs as SPI when the keyword is part of an experimental language feature.
@@ -51,19 +55,19 @@ public struct TokenSpec {
5155
///
5256
/// - Parameters:
5357
/// - name: A name of the token.
54-
/// - isExperimental: Indicates if the token is part of an experimental language feature.
58+
/// - experimentalFeature: The experimental feature the token is part of, or `nil` if this isn't for an experimental feature.
5559
/// - nameForDiagnostics: A name of the token that can be shown in diagnostics.
5660
/// - text: An actual text of the token, if available.
5761
/// - kind: A kind of the token.
5862
fileprivate init(
5963
name: String,
60-
isExperimental: Bool = false,
64+
experimentalFeature: ExperimentalFeature? = nil,
6165
nameForDiagnostics: String,
6266
text: String? = nil,
6367
kind: Kind
6468
) {
6569
self.varOrCaseName = .identifier(name)
66-
self.isExperimental = isExperimental
70+
self.experimentalFeature = experimentalFeature
6771
self.nameForDiagnostics = nameForDiagnostics
6872
self.text = text
6973
self.kind = kind

CodeGeneration/Sources/generate-swift-syntax/GenerateSwiftSyntax.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ struct GenerateSwiftSyntax: ParsableCommand {
8888

8989
var fileSpecs: [GeneratedFileSpec] = [
9090
// SwiftParser
91+
GeneratedFileSpec(swiftParserGeneratedDir + ["ExperimentalFeatures.swift"], experimentalFeaturesFile),
9192
GeneratedFileSpec(swiftParserGeneratedDir + ["IsLexerClassified.swift"], isLexerClassifiedFile),
9293
GeneratedFileSpec(swiftParserGeneratedDir + ["LayoutNodes+Parsable.swift"], layoutNodesParsableFile),
9394
GeneratedFileSpec(swiftParserGeneratedDir + ["Parser+TokenSpecSet.swift"], parserTokenSpecSetFile),
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
import SwiftSyntax
14+
import SwiftSyntaxBuilder
15+
import SyntaxSupport
16+
import Utils
17+
18+
let experimentalFeaturesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
19+
DeclSyntax(
20+
"""
21+
extension Parser {
22+
@_spi(ExperimentalLanguageFeatures)
23+
public struct ExperimentalFeatures: OptionSet {
24+
public let rawValue: UInt
25+
public init(rawValue: UInt) {
26+
self.rawValue = rawValue
27+
}
28+
}
29+
}
30+
"""
31+
)
32+
33+
try! ExtensionDeclSyntax("extension Parser.ExperimentalFeatures") {
34+
for (i, feature) in ExperimentalFeature.allCases.enumerated() {
35+
DeclSyntax(
36+
"""
37+
/// Whether to enable the parsing of \(raw: feature.featureName).
38+
public static let \(feature.token) = Self(rawValue: 1 << \(raw: i))
39+
"""
40+
)
41+
}
42+
}
43+
}

CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ParserTokenSpecSetFile.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ import SwiftSyntaxBuilder
1515
import SyntaxSupport
1616
import Utils
1717

18+
func tokenCaseMatch(_ caseName: TokenSyntax, experimentalFeature: ExperimentalFeature?) -> SwitchCaseSyntax {
19+
let whereClause =
20+
experimentalFeature.map {
21+
"where experimentalFeatures.contains(.\($0.token))"
22+
} ?? ""
23+
return "case TokenSpec(.\(caseName))\(raw: whereClause): self = .\(caseName)"
24+
}
25+
1826
let parserTokenSpecSetFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
1927
DeclSyntax("@_spi(RawSyntax) @_spi(ExperimentalLanguageFeatures) import SwiftSyntax")
2028

@@ -42,16 +50,20 @@ let parserTokenSpecSetFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
4250
}
4351
}
4452

45-
try InitializerDeclSyntax("init?(lexeme: Lexer.Lexeme)") {
53+
try InitializerDeclSyntax("init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures)") {
4654
try SwitchExprSyntax("switch PrepareForKeywordMatch(lexeme)") {
4755
for choice in choices {
4856
switch choice {
4957
case .keyword(let keyword):
50-
let caseName = keyword.spec.varOrCaseName
51-
SwitchCaseSyntax("case TokenSpec(.\(caseName)): self = .\(caseName)")
58+
tokenCaseMatch(
59+
keyword.spec.varOrCaseName,
60+
experimentalFeature: keyword.spec.experimentalFeature
61+
)
5262
case .token(let token):
53-
let caseName = token.spec.varOrCaseName
54-
SwitchCaseSyntax("case TokenSpec(.\(caseName)): self = .\(caseName)")
63+
tokenCaseMatch(
64+
token.spec.varOrCaseName,
65+
experimentalFeature: token.spec.experimentalFeature
66+
)
5567
}
5668
}
5769
SwitchCaseSyntax("default: return nil")

Sources/SwiftBasicFormat/BasicFormat.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,11 @@ open class BasicFormat: SyntaxRewriter {
307307
(.keyword(.`init`), .leftAngle), // init<T>()
308308
(.keyword(.`init`), .leftParen), // init()
309309
(.keyword(.self), .period), // self.someProperty
310-
(.keyword(.Self), .period), // self.someProperty
310+
(.keyword(.self), .leftParen), // self()
311+
(.keyword(.self), .leftSquare), // self[]
312+
(.keyword(.Self), .period), // Self.someProperty
313+
(.keyword(.Self), .leftParen), // Self()
314+
(.keyword(.Self), .leftSquare), // Self[]
311315
(.keyword(.set), .leftParen), // var mVar: Int { set(value) {} }
312316
(.keyword(.subscript), .leftParen), // subscript(x: Int)
313317
(.keyword(.super), .period), // super.someProperty

Sources/SwiftParser/Attributes.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ extension Parser {
6464
case objc
6565
case transpose
6666

67-
init?(lexeme: Lexer.Lexeme) {
67+
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
6868
switch PrepareForKeywordMatch(lexeme) {
6969
case TokenSpec(._alignment): self = ._alignment
7070
case TokenSpec(._backDeploy): self = ._backDeploy
@@ -223,7 +223,7 @@ extension Parser {
223223
)
224224
}
225225

226-
switch DeclarationAttributeWithSpecialSyntax(lexeme: self.peek()) {
226+
switch peek(isAtAnyIn: DeclarationAttributeWithSpecialSyntax.self) {
227227
case .available, ._spi_available:
228228
return parseAttribute(argumentMode: .required) { parser in
229229
return .availability(parser.parseAvailabilityArgumentSpecList())
@@ -1043,7 +1043,7 @@ extension Parser {
10431043
}
10441044
}
10451045

1046-
init?(lexeme: Lexer.Lexeme) {
1046+
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
10471047
switch PrepareForKeywordMatch(lexeme) {
10481048
case TokenSpec(.private): self = .private
10491049
case TokenSpec(.fileprivate): self = .fileprivate

Sources/SwiftParser/Availability.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ extension Parser {
6060
case star
6161
case identifier
6262

63-
init?(lexeme: Lexer.Lexeme) {
63+
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
6464
switch PrepareForKeywordMatch(lexeme) {
6565
case TokenSpec(.message): self = .message
6666
case TokenSpec(.renamed): self = .renamed

Sources/SwiftParser/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ add_swift_host_library(SwiftParser
1313
CollectionNodes+Parsable.swift
1414
Declarations.swift
1515
Directives.swift
16-
ExperimentalFeatures.swift
1716
Expressions.swift
1817
IncrementalParseTransition.swift
1918
Lookahead.swift
@@ -39,6 +38,7 @@ add_swift_host_library(SwiftParser
3938
TriviaParser.swift
4039
Types.swift
4140

41+
generated/ExperimentalFeatures.swift
4242
generated/IsLexerClassified.swift
4343
generated/LayoutNodes+Parsable.swift
4444
generated/Parser+TokenSpecSet.swift

0 commit comments

Comments
 (0)