Skip to content

Commit c543645

Browse files
committed
Add diagnostic for canImport in ifconfig
Add `forDirective` parameter for `parsePrimaryExpression` to branch into directive expression parsing for `canImport`, `compiler` and `swift` keywords Refactor version tuple parse and `VersionTuple` node and add diagnostics for it Add assertion for the name of `Child` to make its first character uppercase
1 parent af14609 commit c543645

30 files changed

+1698
-243
lines changed

CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -118,42 +118,51 @@ public let AVAILABILITY_NODES: [Node] = [
118118
]
119119
),
120120

121-
// version-tuple -> integer-literal
122-
// | float-literal
123-
// | float-literal '.' integer-literal
121+
// version-tuple-element -> '.' interger-literal
124122
Node(
125-
name: "VersionTuple",
126-
nameForDiagnostics: "version tuple",
127-
description: "A version number of the form major.minor.patch in which the minor and patch part may be omitted.",
123+
name: "VersionComponent",
124+
nameForDiagnostics: "version tuple element",
125+
description: "An element to represent a dot and number pair",
128126
kind: "Syntax",
129127
children: [
130128
Child(
131-
name: "Major",
132-
kind: .token(choices: [.token(tokenKind: "IntegerLiteralToken")]),
133-
description: "The major version."
134-
),
135-
Child(
136-
name: "MinorPeriod",
129+
name: "Period",
137130
kind: .token(choices: [.token(tokenKind: "PeriodToken")]),
138-
description: "If the version contains a minor number, the period separating the major from the minor number.",
139-
isOptional: true
131+
description: "The period of this pair"
140132
),
141133
Child(
142-
name: "Minor",
134+
name: "Number",
143135
kind: .token(choices: [.token(tokenKind: "IntegerLiteralToken")]),
144-
description: "The minor version if specified.",
145-
isOptional: true
136+
description: "The number of this pair"
146137
),
138+
]
139+
),
140+
141+
// version-list -> version-tuple-element version-list?
142+
Node(
143+
name: "VersionComponentList",
144+
nameForDiagnostics: "version list",
145+
kind: "SyntaxCollection",
146+
element: "VersionComponent",
147+
omitWhenEmpty: true
148+
),
149+
150+
// version-tuple -> integer-literal version-list?
151+
Node(
152+
name: "VersionTuple",
153+
nameForDiagnostics: "version tuple",
154+
description: "A version number of the form major.minor.patch in which the minor and patch part may be omitted.",
155+
kind: "Syntax",
156+
children: [
147157
Child(
148-
name: "PatchPeriod",
149-
kind: .token(choices: [.token(tokenKind: "PeriodToken")]),
150-
description: "If the version contains a patch number, the period separating the minor from the patch number.",
151-
isOptional: true
158+
name: "Major",
159+
kind: .token(choices: [.token(tokenKind: "IntegerLiteralToken")]),
160+
description: "The major version."
152161
),
153162
Child(
154-
name: "Patch",
155-
kind: .token(choices: [.token(tokenKind: "IntegerLiteralToken")]),
156-
description: "The patch version if specified.",
163+
name: "Components",
164+
kind: .collection(kind: "VersionComponentList", collectionElementName: "VersionComponent"),
165+
nameForDiagnostics: "components",
157166
isOptional: true
158167
),
159168
]

CodeGeneration/Sources/SyntaxSupport/Child.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ public class Child {
161161
isIndented: Bool = false,
162162
requiresLeadingNewline: Bool = false
163163
) {
164+
if let firstCharInName = name.first {
165+
precondition(firstCharInName.isUppercase == true, "The first letter of a child’s name should be uppercase")
166+
}
164167
self.name = name
165168
self.kind = kind
166169
self.nameForDiagnostics = nameForDiagnostics

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,51 @@ public let EXPR_NODES: [Node] = [
185185
]
186186
),
187187

188+
// the canImport expr in if config expression
189+
Node(
190+
name: "CanImportExpr",
191+
nameForDiagnostics: "'canImport' expression in if config expression",
192+
kind: "Expr",
193+
children: [
194+
Child(
195+
name: "CanImportKeyword",
196+
kind: .token(choices: [.token(tokenKind: "IdentifierToken")])
197+
),
198+
Child(
199+
name: "LeftParen",
200+
kind: .token(choices: [.token(tokenKind: "LeftParenToken")])
201+
),
202+
Child(
203+
name: "ImportPath",
204+
kind: .token(choices: [.token(tokenKind: "IdentifierToken")])
205+
),
206+
Child(
207+
name: "Comma",
208+
kind: .token(choices: [.token(tokenKind: "CommaToken")]),
209+
isOptional: true
210+
),
211+
Child(
212+
name: "Label",
213+
kind: .token(choices: [.keyword(text: "_version"), .keyword(text: "_underlyingVersion")]),
214+
isOptional: true
215+
),
216+
Child(
217+
name: "Colon",
218+
kind: .token(choices: [.keyword(text: "ColonToken")]),
219+
isOptional: true
220+
),
221+
Child(
222+
name: "VersionTuple",
223+
kind: .node(kind: "VersionTuple"),
224+
isOptional: true
225+
),
226+
Child(
227+
name: "RightParen",
228+
kind: .token(choices: [.token(tokenKind: "RightParenToken")])
229+
),
230+
]
231+
),
232+
188233
// case-item -> pattern where-clause? ','?
189234
Node(
190235
name: "CaseItem",

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ public let KEYWORDS: [KeywordSpec] = [
8282
KeywordSpec("_TrivialAtMost"),
8383
KeywordSpec("_typeEraser"),
8484
KeywordSpec("_unavailableFromAsync"),
85+
KeywordSpec("_underlyingVersion"),
8586
KeywordSpec("_UnknownLayout"),
87+
KeywordSpec("_version"),
8688
KeywordSpec("actor"),
8789
KeywordSpec("addressWithNativeOwner"),
8890
KeywordSpec("addressWithOwner"),
@@ -102,9 +104,11 @@ public let KEYWORDS: [KeywordSpec] = [
102104
KeywordSpec("block"),
103105
KeywordSpec("borrowing"),
104106
KeywordSpec("break", isLexerClassified: true, requiresTrailingSpace: true),
107+
KeywordSpec("canImport"),
105108
KeywordSpec("case", isLexerClassified: true, requiresTrailingSpace: true),
106109
KeywordSpec("catch", isLexerClassified: true, requiresLeadingSpace: true),
107110
KeywordSpec("class", isLexerClassified: true, requiresTrailingSpace: true),
111+
KeywordSpec("compiler"),
108112
KeywordSpec("consume"),
109113
KeywordSpec("consuming"),
110114
KeywordSpec("continue", isLexerClassified: true, requiresTrailingSpace: true),

Sources/SwiftParser/Availability.swift

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -261,44 +261,46 @@ extension Parser {
261261
/// platform-version → decimal-digits
262262
/// platform-version → decimal-digits '.' decimal-digits
263263
/// platform-version → decimal-digits '.' decimal-digits '.' decimal-digits
264-
mutating func parseVersionTuple() -> RawVersionTupleSyntax {
264+
mutating func parseVersionTuple(maxComponentCount: Int = 3) -> RawVersionTupleSyntax {
265265
if self.at(.floatingLiteral),
266266
let periodIndex = self.currentToken.tokenText.firstIndex(of: UInt8(ascii: ".")),
267267
self.currentToken.tokenText[0..<periodIndex].allSatisfy({ Unicode.Scalar($0).isDigit })
268268
{
269269
// The lexer generates a float literal '1.2' for the major and minor version.
270270
// Split it into two integers if possible
271271
let major = self.consumePrefix(SyntaxText(rebasing: self.currentToken.tokenText[0..<periodIndex]), as: .integerLiteral)
272-
let (unexpectedBeforeMinorPeriod, minorPeriod) = self.expect(.period)
273-
let minor = self.expectDecimalIntegerWithoutRecovery()
274-
let patchPeriod: RawTokenSyntax?
275-
let patch: RawTokenSyntax?
276-
if let period = self.consume(if: .period) {
277-
patchPeriod = period
278-
patch = self.expectDecimalIntegerWithoutRecovery()
279-
} else {
280-
patchPeriod = nil
281-
patch = nil
272+
273+
var components: [RawVersionComponentSyntax] = []
274+
for _ in 1..<maxComponentCount {
275+
guard let period = self.consume(if: .period) else {
276+
break
277+
}
278+
let version = self.expectDecimalIntegerWithoutRecovery()
279+
280+
components.append(RawVersionComponentSyntax(period: period, number: version, arena: self.arena))
282281
}
283-
return RawVersionTupleSyntax(
284-
major: major,
285-
unexpectedBeforeMinorPeriod,
286-
minorPeriod: minorPeriod,
287-
minor: minor,
288-
patchPeriod: patchPeriod,
289-
patch: patch,
290-
arena: self.arena
291-
)
282+
283+
var trailingComponents: [RawVersionComponentSyntax] = []
284+
var unexpectedTrailingComponents: RawUnexpectedNodesSyntax?
285+
286+
repeat {
287+
guard let period = self.consume(if: .period) else {
288+
break
289+
}
290+
let version = self.expectDecimalIntegerWithoutRecovery()
291+
trailingComponents.append(RawVersionComponentSyntax(period: period, number: version, arena: self.arena))
292+
293+
} while true
294+
295+
if !trailingComponents.isEmpty {
296+
unexpectedTrailingComponents = RawUnexpectedNodesSyntax(elements: trailingComponents.compactMap { $0.as(RawSyntax.self) }, arena: self.arena)
297+
}
298+
299+
return RawVersionTupleSyntax(major: major, components: RawVersionComponentListSyntax(elements: components, arena: self.arena), unexpectedTrailingComponents, arena: self.arena)
300+
292301
} else {
293302
let major = self.expectDecimalIntegerWithoutRecovery()
294-
return RawVersionTupleSyntax(
295-
major: major,
296-
minorPeriod: nil,
297-
minor: nil,
298-
patchPeriod: nil,
299-
patch: nil,
300-
arena: self.arena
301-
)
303+
return RawVersionTupleSyntax(major: major, components: nil, arena: self.arena)
302304
}
303305
}
304306
}

Sources/SwiftParser/Expressions.swift

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ extension Parser {
601601
forDirective: Bool,
602602
pattern: PatternContext
603603
) -> RawExprSyntax {
604-
let head = self.parsePrimaryExpression(pattern: pattern, flavor: flavor)
604+
let head = self.parsePrimaryExpression(pattern: pattern, forDirective: forDirective, flavor: flavor)
605605
guard !head.is(RawMissingExprSyntax.self) else {
606606
return head
607607
}
@@ -1151,8 +1151,15 @@ extension Parser {
11511151
@_spi(RawSyntax)
11521152
public mutating func parsePrimaryExpression(
11531153
pattern: PatternContext,
1154+
forDirective: Bool,
11541155
flavor: ExprFlavor
11551156
) -> RawExprSyntax {
1157+
if forDirective == true,
1158+
let directiveExpr = self.parsePrimaryExprForDirective()
1159+
{
1160+
return RawExprSyntax(directiveExpr)
1161+
}
1162+
11561163
switch self.at(anyIn: PrimaryExpressionStart.self) {
11571164
case (.integerLiteral, let handle)?:
11581165
let digits = self.eat(handle)
@@ -1291,6 +1298,18 @@ extension Parser {
12911298
return RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
12921299
}
12931300
}
1301+
1302+
// try to parse a primary expression for a directive
1303+
mutating func parsePrimaryExprForDirective() -> RawExprSyntax? {
1304+
switch self.at(anyIn: CompilationCondition.self) {
1305+
case (.canImportKeyword, let handle)?:
1306+
return RawExprSyntax(self.parseCanImportExpression(handle))
1307+
1308+
// TODO: add case `swift` and `compiler` here
1309+
default:
1310+
return nil
1311+
}
1312+
}
12941313
}
12951314

12961315
extension Parser {
@@ -2544,6 +2563,54 @@ extension Parser {
25442563
}
25452564
}
25462565

2566+
// MARK: Platform Condition
2567+
extension Parser {
2568+
mutating func parseCanImportExpression(_ handle: TokenConsumptionHandle) -> RawExprSyntax {
2569+
let canImportKeyword = self.eat(handle)
2570+
2571+
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
2572+
2573+
let (unexpectedBeforeImportPath, importPath) = self.expect(.identifier)
2574+
2575+
let comma = self.consume(if: .comma)
2576+
2577+
var unexpectedBeforeLabel: RawUnexpectedNodesSyntax?
2578+
var label: RawTokenSyntax?
2579+
2580+
var unexpectedBeforeColon: RawUnexpectedNodesSyntax?
2581+
var colon: RawTokenSyntax?
2582+
var version: RawVersionTupleSyntax?
2583+
2584+
if comma != nil {
2585+
(unexpectedBeforeLabel, label) = self.expect(.keyword(._version), .keyword(._underlyingVersion), default: .keyword(._version))
2586+
(unexpectedBeforeColon, colon) = self.expect(.colon)
2587+
2588+
version = self.parseVersionTuple(maxComponentCount: 4)
2589+
}
2590+
2591+
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
2592+
2593+
return RawExprSyntax(
2594+
RawCanImportExprSyntax(
2595+
canImportKeyword: canImportKeyword,
2596+
unexpectedBeforeLeftParen,
2597+
leftParen: leftParen,
2598+
unexpectedBeforeImportPath,
2599+
importPath: importPath,
2600+
comma: comma,
2601+
unexpectedBeforeLabel,
2602+
label: label,
2603+
unexpectedBeforeColon,
2604+
colon: colon,
2605+
versionTuple: version,
2606+
unexpectedBeforeRightParen,
2607+
rightParen: rightParen,
2608+
arena: self.arena
2609+
)
2610+
)
2611+
}
2612+
}
2613+
25472614
// MARK: Lookahead
25482615

25492616
extension Parser.Lookahead {

Sources/SwiftParser/TokenSpecSet.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,30 @@ enum CanBeStatementStart: TokenSpecSet {
132132
}
133133
}
134134

135+
enum CompilationCondition: TokenSpecSet {
136+
case swiftKeyword
137+
case compilerKeyword
138+
case canImportKeyword
139+
140+
init?(lexeme: Lexer.Lexeme) {
141+
switch PrepareForKeywordMatch(lexeme) {
142+
case TokenSpec(.swift): self = .swiftKeyword
143+
case TokenSpec(.compiler): self = .compilerKeyword
144+
case TokenSpec(.canImport): self = .canImportKeyword
145+
default: return nil
146+
}
147+
}
148+
149+
var spec: TokenSpec {
150+
switch self {
151+
case .swiftKeyword: return .keyword(.swift)
152+
case .compilerKeyword: return .keyword(.compiler)
153+
case .canImportKeyword: return .keyword(.canImport)
154+
}
155+
}
156+
157+
}
158+
135159
enum ContextualDeclKeyword: TokenSpecSet {
136160
case __consuming
137161
case _compilerInitialized

0 commit comments

Comments
 (0)