Skip to content

Commit 3cfa2b3

Browse files
authored
Merge pull request #2550 from ahoppen/ahoppen/6.0/canimport
[6.0] Parse `#if canImport` as a function call instead of a specialized expression node
2 parents 6be537c + b019564 commit 3cfa2b3

21 files changed

+197
-162
lines changed

CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public let AVAILABILITY_NODES: [Node] = [
158158
base: .syntax,
159159
nameForDiagnostics: "version tuple",
160160
documentation: "A version number like `1.2.0`. Only the first version component is required. There might be an arbitrary number of following components.",
161+
parserFunction: "parseVersionTuple",
161162
children: [
162163
Child(
163164
name: "major",

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

+2
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ public let EXPR_NODES: [Node] = [
216216
Node(
217217
kind: .canImportExpr,
218218
base: .expr,
219+
deprecationMessage: "'canImport' directives are now represented as a `FunctionCallExpr`",
219220
nameForDiagnostics: "'canImport' expression",
220221
children: [
221222
Child(
@@ -245,6 +246,7 @@ public let EXPR_NODES: [Node] = [
245246
Node(
246247
kind: .canImportVersionInfo,
247248
base: .expr,
249+
deprecationMessage: "'canImport' directives are now represented as a `FunctionCallExpr`",
248250
nameForDiagnostics: nil,
249251
children: [
250252
Child(

CodeGeneration/Sources/SyntaxSupport/Node.swift

+10
Original file line numberDiff line numberDiff line change
@@ -40,6 +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 this syntax node has been deprecated, a message that describes the deprecation.
44+
public let deprecationMessage: String?
45+
4346
/// The experimental feature the node is part of, or `nil` if this isn't
4447
/// for an experimental feature.
4548
public let experimentalFeature: ExperimentalFeature?
@@ -106,6 +109,9 @@ public class Node {
106109
"""
107110
experimentalSPI.with(\.trailingTrivia, .newline)
108111
}
112+
if let deprecationMessage {
113+
"@available(*, deprecated, message: \(literal: deprecationMessage))"
114+
}
109115
if forRaw {
110116
"@_spi(RawSyntax)"
111117
}
@@ -127,6 +133,7 @@ public class Node {
127133
init(
128134
kind: SyntaxNodeKind,
129135
base: SyntaxNodeKind,
136+
deprecationMessage: String? = nil,
130137
experimentalFeature: ExperimentalFeature? = nil,
131138
nameForDiagnostics: String?,
132139
documentation: String? = nil,
@@ -139,6 +146,7 @@ public class Node {
139146

140147
self.kind = kind
141148
self.base = base
149+
self.deprecationMessage = deprecationMessage
142150
self.experimentalFeature = experimentalFeature
143151
self.nameForDiagnostics = nameForDiagnostics
144152
self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
@@ -271,6 +279,7 @@ public class Node {
271279
init(
272280
kind: SyntaxNodeKind,
273281
base: SyntaxNodeKind,
282+
deprecationMessage: String? = nil,
274283
experimentalFeature: ExperimentalFeature? = nil,
275284
nameForDiagnostics: String?,
276285
documentation: String? = nil,
@@ -280,6 +289,7 @@ public class Node {
280289
self.kind = kind
281290
precondition(base == .syntaxCollection)
282291
self.base = base
292+
self.deprecationMessage = deprecationMessage
283293
self.experimentalFeature = experimentalFeature
284294
self.nameForDiagnostics = nameForDiagnostics
285295
self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)

Release Notes/600.md

+8
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
- Issue: https://github.com/apple/sourcekit-lsp/issues/2535
7272
- Pull Request: https://github.com/apple/swift-syntax/pull/2539
7373

74+
- `ExprSyntax.interpretedAsVersionTuple`
75+
- Description: With the change to parse `#if canImport(MyModule, _version: 1.2.3)` as a function call instead of a dedicated syntax node, `1.2.3` natively gets parsed as a member access `3` to the `1.2` float literal. This property allows the reinterpretation of such an expression as a version tuple.
76+
- Pull request: https://github.com/apple/swift-syntax/pull/2025
77+
7478
## API Behavior Changes
7579

7680
## Deprecations
@@ -100,6 +104,10 @@
100104
- Description: Types can have multiple specifiers now and the syntax tree has been modified to reflect that.
101105
- Pull request: https://github.com/apple/swift-syntax/pull/2433
102106

107+
- ` CanImportExprSyntax` and `CanImportVersionInfoSyntax`
108+
- Description: Instead of parsing `canImport` inside `#if` directives as a special expression node, parse it as a functionc call expression. This is in-line with how the `swift(>=6.0)` and `compiler(>=6.0)` directives are parsed.
109+
- Pull request: https://github.com/apple/swift-syntax/pull/2025
110+
103111
## API-Incompatible Changes
104112

105113
- `MacroDefinition` used for expanding macros:

Sources/SwiftParser/Availability.swift

+8-13
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ extension Parser {
117117
(.obsoleted, let handle)?:
118118
let argumentLabel = self.eat(handle)
119119
let (unexpectedBeforeColon, colon) = self.expect(.colon)
120-
let version = self.parseVersionTuple(maxComponentCount: 3)
120+
let version = self.parseVersionTuple()
121121
entry = .availabilityLabeledArgument(
122122
RawAvailabilityLabeledArgumentSyntax(
123123
label: argumentLabel,
@@ -130,7 +130,7 @@ extension Parser {
130130
case (.deprecated, let handle)?:
131131
let argumentLabel = self.eat(handle)
132132
if let colon = self.consume(if: .colon) {
133-
let version = self.parseVersionTuple(maxComponentCount: 3)
133+
let version = self.parseVersionTuple()
134134
entry = .availabilityLabeledArgument(
135135
RawAvailabilityLabeledArgumentSyntax(
136136
label: argumentLabel,
@@ -206,7 +206,7 @@ extension Parser {
206206

207207
let version: RawVersionTupleSyntax?
208208
if self.at(.integerLiteral, .floatLiteral) {
209-
version = self.parseVersionTuple(maxComponentCount: 3)
209+
version = self.parseVersionTuple()
210210
} else {
211211
version = nil
212212
}
@@ -250,7 +250,7 @@ extension Parser {
250250
}
251251

252252
/// Parse a dot-separated list of version numbers.
253-
mutating func parseVersionTuple(maxComponentCount: Int) -> RawVersionTupleSyntax {
253+
mutating func parseVersionTuple() -> RawVersionTupleSyntax {
254254
if self.at(.floatLiteral),
255255
let periodIndex = self.currentToken.tokenText.firstIndex(of: UInt8(ascii: ".")),
256256
self.currentToken.tokenText[0..<periodIndex].allSatisfy({ Unicode.Scalar($0).isDigit })
@@ -260,33 +260,28 @@ extension Parser {
260260
let major = self.consumePrefix(SyntaxText(rebasing: self.currentToken.tokenText[0..<periodIndex]), as: .integerLiteral)
261261

262262
var components: [RawVersionComponentSyntax] = []
263-
var trailingComponents: [RawVersionComponentSyntax] = []
264263

265-
for i in 1... {
264+
var loopProgress = LoopProgressCondition()
265+
while self.hasProgressed(&loopProgress) {
266266
guard let period = self.consume(if: .period) else {
267267
break
268268
}
269269
let version = self.expectDecimalIntegerWithoutRecovery()
270270

271271
let versionComponent = RawVersionComponentSyntax(period: period, number: version, arena: self.arena)
272272

273-
if i < maxComponentCount {
274-
components.append(versionComponent)
275-
} else {
276-
trailingComponents.append(versionComponent)
277-
}
273+
components.append(versionComponent)
278274

279275
if version.isMissing {
280276
break
281277
}
282278
}
283279

284-
let unexpectedTrailingComponents = RawUnexpectedNodesSyntax(trailingComponents, arena: self.arena)
285280
let unexpectedAfterComponents = self.parseUnexpectedVersionTokens()
286281
return RawVersionTupleSyntax(
287282
major: major,
288283
components: RawVersionComponentListSyntax(elements: components, arena: self.arena),
289-
RawUnexpectedNodesSyntax(combining: unexpectedTrailingComponents, unexpectedAfterComponents, arena: self.arena),
284+
unexpectedAfterComponents,
290285
arena: self.arena
291286
)
292287
} else {

Sources/SwiftParser/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_swift_syntax_library(SwiftParser
1313
CollectionNodes+Parsable.swift
1414
Declarations.swift
1515
Directives.swift
16+
ExpressionInterpretedAsVersionTuple.swift
1617
Expressions.swift
1718
IncrementalParseTransition.swift
1819
IsValidIdentifier.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 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+
@_spi(RawSyntax) import SwiftSyntax
14+
15+
extension ExprSyntax {
16+
/// Parse the source code of this node as a `VersionTupleSyntax`.
17+
///
18+
/// In some situations, like the `_version` argument of the `canImport` condition in `#if`, the version gets parsed as a normal expression, which results in
19+
/// - an integer literal for single-component versions
20+
/// - a floating point number for two-component versions
21+
/// - a floating point number with a member accesses for versions with more than three components
22+
///
23+
/// This is done so the parser can parse `canImport` like any other function call, reducing the need for special handling.
24+
///
25+
/// This property re-interprets such an expression as a version tuple in cases where the client know that it should semantically represent a version.
26+
///
27+
/// If the expression cannot be interpreted as a valid version tuple, returns `nil`.
28+
public var interpretedAsVersionTuple: VersionTupleSyntax? {
29+
self.syntaxTextBytes.withUnsafeBufferPointer { bytes in
30+
var parser = Parser(bytes)
31+
let versionTuple = VersionTupleSyntax.parse(from: &parser)
32+
if versionTuple.hasError {
33+
return nil
34+
}
35+
return versionTuple
36+
}
37+
}
38+
}

Sources/SwiftParser/Expressions.swift

-62
Original file line numberDiff line numberDiff line change
@@ -1092,10 +1092,6 @@ extension Parser {
10921092
pattern: PatternContext,
10931093
flavor: ExprFlavor
10941094
) -> RawExprSyntax {
1095-
if flavor == .poundIfDirective, let directiveExpr = self.parsePrimaryExprForDirective() {
1096-
return RawExprSyntax(directiveExpr)
1097-
}
1098-
10991095
switch self.at(anyIn: PrimaryExpressionStart.self) {
11001096
case (.integerLiteral, let handle)?:
11011097
let literal = self.eat(handle)
@@ -1237,18 +1233,6 @@ extension Parser {
12371233
return RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
12381234
}
12391235
}
1240-
1241-
// try to parse a primary expression for a directive
1242-
mutating func parsePrimaryExprForDirective() -> RawExprSyntax? {
1243-
switch self.at(anyIn: CompilationCondition.self) {
1244-
case (.canImport, let handle)?:
1245-
return RawExprSyntax(self.parseCanImportExpression(handle))
1246-
1247-
// TODO: add case `swift` and `compiler` here
1248-
default:
1249-
return nil
1250-
}
1251-
}
12521236
}
12531237

12541238
extension Parser {
@@ -2391,52 +2375,6 @@ extension Parser {
23912375
}
23922376
}
23932377

2394-
// MARK: Platform Condition
2395-
extension Parser {
2396-
mutating func parseCanImportExpression(_ handle: TokenConsumptionHandle) -> RawExprSyntax {
2397-
let canImportKeyword = self.eat(handle)
2398-
2399-
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
2400-
2401-
let (unexpectedBeforeImportPath, importPath) = self.expect(.identifier)
2402-
2403-
var versionInfo: RawCanImportVersionInfoSyntax?
2404-
2405-
if let comma = self.consume(if: .comma) {
2406-
let (unexpectedBeforeLabel, label) = self.expect(anyIn: CanImportVersionInfoSyntax.LabelOptions.self, default: ._version)
2407-
let (unexpectedBeforeColon, colon) = self.expect(.colon)
2408-
2409-
let version = self.parseVersionTuple(maxComponentCount: 4)
2410-
2411-
versionInfo = RawCanImportVersionInfoSyntax(
2412-
comma: comma,
2413-
unexpectedBeforeLabel,
2414-
label: label,
2415-
unexpectedBeforeColon,
2416-
colon: colon,
2417-
version: version,
2418-
arena: self.arena
2419-
)
2420-
}
2421-
2422-
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
2423-
2424-
return RawExprSyntax(
2425-
RawCanImportExprSyntax(
2426-
canImportKeyword: canImportKeyword,
2427-
unexpectedBeforeLeftParen,
2428-
leftParen: leftParen,
2429-
unexpectedBeforeImportPath,
2430-
importPath: importPath,
2431-
versionInfo: versionInfo,
2432-
unexpectedBeforeRightParen,
2433-
rightParen: rightParen,
2434-
arena: self.arena
2435-
)
2436-
)
2437-
}
2438-
}
2439-
24402378
// MARK: Lookahead
24412379

24422380
extension Parser.Lookahead {

Sources/SwiftParser/generated/LayoutNodes+Parsable.swift

+18
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,24 @@ extension TypeSyntax: SyntaxParseable {
346346
}
347347
}
348348

349+
extension VersionTupleSyntax: SyntaxParseable {
350+
public static func parse(from parser: inout Parser) -> Self {
351+
// Keep the parser alive so that the arena in which `raw` is allocated
352+
// doesn’t get deallocated before we have a chance to create a syntax node
353+
// from it. We can’t use `parser.arena` as the parameter to
354+
// `Syntax(raw:arena:)` because the node might have been re-used during an
355+
// incremental parse and would then live in a different arena than
356+
// `parser.arena`.
357+
defer {
358+
withExtendedLifetime(parser) {
359+
}
360+
}
361+
let node = parser.parseVersionTuple()
362+
let raw = RawSyntax(parser.parseRemainder(into: node))
363+
return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self)
364+
}
365+
}
366+
349367
fileprivate extension Parser {
350368
mutating func parseNonOptionalCodeBlockItem() -> RawCodeBlockItemSyntax {
351369
guard let node = self.parseCodeBlockItem(isAtTopLevel: false, allowInitDecl: true) else {

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

-44
Original file line numberDiff line numberDiff line change
@@ -595,50 +595,6 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
595595
return .visitChildren
596596
}
597597

598-
public override func visit(_ node: CanImportExprSyntax) -> SyntaxVisitorContinueKind {
599-
if shouldSkip(node) {
600-
return .skipChildren
601-
}
602-
603-
if let versionTuple = node.versionInfo?.version,
604-
let unexpectedVersionTuple = node.unexpectedBetweenVersionInfoAndRightParen
605-
{
606-
if versionTuple.major.isMissing {
607-
addDiagnostic(
608-
versionTuple,
609-
CannotParseVersionTuple(versionTuple: unexpectedVersionTuple),
610-
handledNodes: [versionTuple.id, unexpectedVersionTuple.id]
611-
)
612-
} else {
613-
addDiagnostic(
614-
unexpectedVersionTuple,
615-
.canImportWrongNumberOfParameter,
616-
handledNodes: [unexpectedVersionTuple.id]
617-
)
618-
}
619-
}
620-
621-
return .visitChildren
622-
}
623-
624-
public override func visit(_ node: CanImportVersionInfoSyntax) -> SyntaxVisitorContinueKind {
625-
if shouldSkip(node) {
626-
return .skipChildren
627-
}
628-
629-
if node.label.isMissing {
630-
addDiagnostic(
631-
node.label,
632-
.canImportWrongSecondParameterLabel,
633-
handledNodes: [node.label.id]
634-
)
635-
636-
handledNodes.append(contentsOf: [node.unexpectedBetweenLabelAndColon?.id, node.colon.id, node.version.id].compactMap { $0 })
637-
}
638-
639-
return .visitChildren
640-
}
641-
642598
public override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind {
643599
if shouldSkip(node) {
644600
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

-6
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,6 @@ extension DiagnosticMessage where Self == StaticParserError {
104104
public static var associatedTypeCannotUsePack: Self {
105105
.init("associated types cannot be variadic")
106106
}
107-
public static var canImportWrongSecondParameterLabel: Self {
108-
.init("2nd parameter of canImport should be labeled as _version or _underlyingVersion")
109-
}
110-
public static var canImportWrongNumberOfParameter: Self {
111-
.init("canImport can take only two parameters")
112-
}
113107
public static var caseOutsideOfSwitchOrEnum: Self {
114108
.init("'case' can only appear inside a 'switch' statement or 'enum' declaration")
115109
}

0 commit comments

Comments
 (0)