Skip to content

Commit 8311d8d

Browse files
authored
Merge pull request #1574 from StevenWong12/canImport_diagnostic
Add diagnostic for `canImport` expression
2 parents da4add8 + 809fe52 commit 8311d8d

29 files changed

+1940
-247
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 -> '.' integer-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: nil,
125+
description: "An element to represent a single component in a version, like `.1`.",
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 version component"
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 version number of this component"
146137
),
138+
]
139+
),
140+
141+
// version-list -> version-tuple-element version-list?
142+
Node(
143+
name: "VersionComponentList",
144+
nameForDiagnostics: nil,
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 like `1.2.0`. Only the first version component is required. There might be an arbitrary number of following components.",
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+
description: "Any version components that are not the major version . For example, for `1.2.0`, this will contain `.2.0`",
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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,60 @@ 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",
192+
kind: "Expr",
193+
children: [
194+
Child(
195+
name: "CanImportKeyword",
196+
kind: .token(choices: [.keyword(text: "canImport")])
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: "VersionInfo",
208+
kind: .node(kind: "CanImportVersionInfo"),
209+
isOptional: true
210+
),
211+
Child(
212+
name: "RightParen",
213+
kind: .token(choices: [.token(tokenKind: "RightParenToken")])
214+
),
215+
]
216+
),
217+
218+
Node(
219+
name: "CanImportVersionInfo",
220+
nameForDiagnostics: nil,
221+
kind: "Expr",
222+
children: [
223+
Child(
224+
name: "Comma",
225+
kind: .token(choices: [.token(tokenKind: "CommaToken")])
226+
),
227+
Child(
228+
name: "Label",
229+
kind: .token(choices: [.keyword(text: "_version"), .keyword(text: "_underlyingVersion")])
230+
),
231+
Child(
232+
name: "Colon",
233+
kind: .token(choices: [.token(tokenKind: "ColonToken")])
234+
),
235+
Child(
236+
name: "VersionTuple",
237+
kind: .node(kind: "VersionTuple")
238+
),
239+
]
240+
),
241+
188242
// case-item -> pattern where-clause? ','?
189243
Node(
190244
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: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ extension Parser {
123123
(.obsoleted, let handle)?:
124124
let argumentLabel = self.eat(handle)
125125
let (unexpectedBeforeColon, colon) = self.expect(.colon)
126-
let version = self.parseVersionTuple()
126+
let version = self.parseVersionTuple(maxComponentCount: 3)
127127
entry = .availabilityLabeledArgument(
128128
RawAvailabilityLabeledArgumentSyntax(
129129
label: argumentLabel,
@@ -136,7 +136,7 @@ extension Parser {
136136
case (.deprecated, let handle)?:
137137
let argumentLabel = self.eat(handle)
138138
if let colon = self.consume(if: .colon) {
139-
let version = self.parseVersionTuple()
139+
let version = self.parseVersionTuple(maxComponentCount: 3)
140140
entry = .availabilityLabeledArgument(
141141
RawAvailabilityLabeledArgumentSyntax(
142142
label: argumentLabel,
@@ -227,7 +227,7 @@ extension Parser {
227227

228228
let version: RawVersionTupleSyntax?
229229
if self.at(.integerLiteral, .floatingLiteral) {
230-
version = self.parseVersionTuple()
230+
version = self.parseVersionTuple(maxComponentCount: 3)
231231
} else {
232232
version = nil
233233
}
@@ -258,47 +258,47 @@ extension Parser {
258258
/// Grammar
259259
/// =======
260260
///
261-
/// platform-version → decimal-digits
262-
/// platform-version → decimal-digits '.' decimal-digits
263-
/// platform-version → decimal-digits '.' decimal-digits '.' decimal-digits
264-
mutating func parseVersionTuple() -> RawVersionTupleSyntax {
261+
/// version-tuple -> integer-literal version-list?
262+
/// version-list -> version-tuple-element version-list?
263+
/// version-tuple-element -> '.' interger-literal
264+
mutating func parseVersionTuple(maxComponentCount: Int) -> 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+
var trailingComponents: [RawVersionComponentSyntax] = []
275+
276+
for i in 1... {
277+
guard let period = self.consume(if: .period) else {
278+
break
279+
}
280+
let version = self.expectDecimalIntegerWithoutRecovery()
281+
282+
let versionComponent = RawVersionComponentSyntax(period: period, number: version, arena: self.arena)
283+
284+
if i < maxComponentCount {
285+
components.append(versionComponent)
286+
} else {
287+
trailingComponents.append(versionComponent)
288+
}
282289
}
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-
)
290+
291+
var unexpectedTrailingComponents: RawUnexpectedNodesSyntax?
292+
293+
if !trailingComponents.isEmpty {
294+
unexpectedTrailingComponents = RawUnexpectedNodesSyntax(elements: trailingComponents.compactMap { $0.as(RawSyntax.self) }, arena: self.arena)
295+
}
296+
297+
return RawVersionTupleSyntax(major: major, components: RawVersionComponentListSyntax(elements: components, arena: self.arena), unexpectedTrailingComponents, arena: self.arena)
298+
292299
} else {
293300
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-
)
301+
return RawVersionTupleSyntax(major: major, components: nil, arena: self.arena)
302302
}
303303
}
304304
}

Sources/SwiftParser/Expressions.swift

Lines changed: 58 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)
@@ -1316,6 +1323,18 @@ extension Parser {
13161323
return RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
13171324
}
13181325
}
1326+
1327+
// try to parse a primary expression for a directive
1328+
mutating func parsePrimaryExprForDirective() -> RawExprSyntax? {
1329+
switch self.at(anyIn: CompilationCondition.self) {
1330+
case (.canImportKeyword, let handle)?:
1331+
return RawExprSyntax(self.parseCanImportExpression(handle))
1332+
1333+
// TODO: add case `swift` and `compiler` here
1334+
default:
1335+
return nil
1336+
}
1337+
}
13191338
}
13201339

13211340
extension Parser {
@@ -2569,6 +2588,44 @@ extension Parser {
25692588
}
25702589
}
25712590

2591+
// MARK: Platform Condition
2592+
extension Parser {
2593+
mutating func parseCanImportExpression(_ handle: TokenConsumptionHandle) -> RawExprSyntax {
2594+
let canImportKeyword = self.eat(handle)
2595+
2596+
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
2597+
2598+
let (unexpectedBeforeImportPath, importPath) = self.expect(.identifier)
2599+
2600+
var versionInfo: RawCanImportVersionInfoSyntax?
2601+
2602+
if let comma = self.consume(if: .comma) {
2603+
let (unexpectedBeforeLabel, label) = self.expect(.keyword(._version), .keyword(._underlyingVersion), default: .keyword(._version))
2604+
let (unexpectedBeforeColon, colon) = self.expect(.colon)
2605+
2606+
let version = self.parseVersionTuple(maxComponentCount: 4)
2607+
2608+
versionInfo = RawCanImportVersionInfoSyntax(comma: comma, unexpectedBeforeLabel, label: label, unexpectedBeforeColon, colon: colon, versionTuple: version, arena: self.arena)
2609+
}
2610+
2611+
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
2612+
2613+
return RawExprSyntax(
2614+
RawCanImportExprSyntax(
2615+
canImportKeyword: canImportKeyword,
2616+
unexpectedBeforeLeftParen,
2617+
leftParen: leftParen,
2618+
unexpectedBeforeImportPath,
2619+
importPath: importPath,
2620+
versionInfo: versionInfo,
2621+
unexpectedBeforeRightParen,
2622+
rightParen: rightParen,
2623+
arena: self.arena
2624+
)
2625+
)
2626+
}
2627+
}
2628+
25722629
// MARK: Lookahead
25732630

25742631
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)