Skip to content

[6.0] Parse #if canImport as a function call instead of a specialized expression node #2550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public let AVAILABILITY_NODES: [Node] = [
base: .syntax,
nameForDiagnostics: "version tuple",
documentation: "A version number like `1.2.0`. Only the first version component is required. There might be an arbitrary number of following components.",
parserFunction: "parseVersionTuple",
children: [
Child(
name: "major",
Expand Down
2 changes: 2 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ public let EXPR_NODES: [Node] = [
Node(
kind: .canImportExpr,
base: .expr,
deprecationMessage: "'canImport' directives are now represented as a `FunctionCallExpr`",
nameForDiagnostics: "'canImport' expression",
children: [
Child(
Expand Down Expand Up @@ -245,6 +246,7 @@ public let EXPR_NODES: [Node] = [
Node(
kind: .canImportVersionInfo,
base: .expr,
deprecationMessage: "'canImport' directives are now represented as a `FunctionCallExpr`",
nameForDiagnostics: nil,
children: [
Child(
Expand Down
10 changes: 10 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public class Node {
/// The kind of node’s supertype. This kind must have `isBase == true`
public let base: SyntaxNodeKind

/// If this syntax node has been deprecated, a message that describes the deprecation.
public let deprecationMessage: String?

/// The experimental feature the node is part of, or `nil` if this isn't
/// for an experimental feature.
public let experimentalFeature: ExperimentalFeature?
Expand Down Expand Up @@ -106,6 +109,9 @@ public class Node {
"""
experimentalSPI.with(\.trailingTrivia, .newline)
}
if let deprecationMessage {
"@available(*, deprecated, message: \(literal: deprecationMessage))"
}
if forRaw {
"@_spi(RawSyntax)"
}
Expand All @@ -127,6 +133,7 @@ public class Node {
init(
kind: SyntaxNodeKind,
base: SyntaxNodeKind,
deprecationMessage: String? = nil,
experimentalFeature: ExperimentalFeature? = nil,
nameForDiagnostics: String?,
documentation: String? = nil,
Expand All @@ -139,6 +146,7 @@ public class Node {

self.kind = kind
self.base = base
self.deprecationMessage = deprecationMessage
self.experimentalFeature = experimentalFeature
self.nameForDiagnostics = nameForDiagnostics
self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
Expand Down Expand Up @@ -271,6 +279,7 @@ public class Node {
init(
kind: SyntaxNodeKind,
base: SyntaxNodeKind,
deprecationMessage: String? = nil,
experimentalFeature: ExperimentalFeature? = nil,
nameForDiagnostics: String?,
documentation: String? = nil,
Expand All @@ -280,6 +289,7 @@ public class Node {
self.kind = kind
precondition(base == .syntaxCollection)
self.base = base
self.deprecationMessage = deprecationMessage
self.experimentalFeature = experimentalFeature
self.nameForDiagnostics = nameForDiagnostics
self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
Expand Down
8 changes: 8 additions & 0 deletions Release Notes/600.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
- Issue: https://github.com/apple/sourcekit-lsp/issues/2535
- Pull Request: https://github.com/apple/swift-syntax/pull/2539

- `ExprSyntax.interpretedAsVersionTuple`
- 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.
- Pull request: https://github.com/apple/swift-syntax/pull/2025

## API Behavior Changes

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

- ` CanImportExprSyntax` and `CanImportVersionInfoSyntax`
- 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.
- Pull request: https://github.com/apple/swift-syntax/pull/2025

## API-Incompatible Changes

- `MacroDefinition` used for expanding macros:
Expand Down
21 changes: 8 additions & 13 deletions Sources/SwiftParser/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ extension Parser {
(.obsoleted, let handle)?:
let argumentLabel = self.eat(handle)
let (unexpectedBeforeColon, colon) = self.expect(.colon)
let version = self.parseVersionTuple(maxComponentCount: 3)
let version = self.parseVersionTuple()
entry = .availabilityLabeledArgument(
RawAvailabilityLabeledArgumentSyntax(
label: argumentLabel,
Expand All @@ -130,7 +130,7 @@ extension Parser {
case (.deprecated, let handle)?:
let argumentLabel = self.eat(handle)
if let colon = self.consume(if: .colon) {
let version = self.parseVersionTuple(maxComponentCount: 3)
let version = self.parseVersionTuple()
entry = .availabilityLabeledArgument(
RawAvailabilityLabeledArgumentSyntax(
label: argumentLabel,
Expand Down Expand Up @@ -206,7 +206,7 @@ extension Parser {

let version: RawVersionTupleSyntax?
if self.at(.integerLiteral, .floatLiteral) {
version = self.parseVersionTuple(maxComponentCount: 3)
version = self.parseVersionTuple()
} else {
version = nil
}
Expand Down Expand Up @@ -250,7 +250,7 @@ extension Parser {
}

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

var components: [RawVersionComponentSyntax] = []
var trailingComponents: [RawVersionComponentSyntax] = []

for i in 1... {
var loopProgress = LoopProgressCondition()
while self.hasProgressed(&loopProgress) {
guard let period = self.consume(if: .period) else {
break
}
let version = self.expectDecimalIntegerWithoutRecovery()

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

if i < maxComponentCount {
components.append(versionComponent)
} else {
trailingComponents.append(versionComponent)
}
components.append(versionComponent)

if version.isMissing {
break
}
}

let unexpectedTrailingComponents = RawUnexpectedNodesSyntax(trailingComponents, arena: self.arena)
let unexpectedAfterComponents = self.parseUnexpectedVersionTokens()
return RawVersionTupleSyntax(
major: major,
components: RawVersionComponentListSyntax(elements: components, arena: self.arena),
RawUnexpectedNodesSyntax(combining: unexpectedTrailingComponents, unexpectedAfterComponents, arena: self.arena),
unexpectedAfterComponents,
arena: self.arena
)
} else {
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftParser/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_swift_syntax_library(SwiftParser
CollectionNodes+Parsable.swift
Declarations.swift
Directives.swift
ExpressionInterpretedAsVersionTuple.swift
Expressions.swift
IncrementalParseTransition.swift
IsValidIdentifier.swift
Expand Down
38 changes: 38 additions & 0 deletions Sources/SwiftParser/ExpressionInterpretedAsVersionTuple.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

@_spi(RawSyntax) import SwiftSyntax

extension ExprSyntax {
/// Parse the source code of this node as a `VersionTupleSyntax`.
///
/// In some situations, like the `_version` argument of the `canImport` condition in `#if`, the version gets parsed as a normal expression, which results in
/// - an integer literal for single-component versions
/// - a floating point number for two-component versions
/// - a floating point number with a member accesses for versions with more than three components
///
/// This is done so the parser can parse `canImport` like any other function call, reducing the need for special handling.
///
/// This property re-interprets such an expression as a version tuple in cases where the client know that it should semantically represent a version.
///
/// If the expression cannot be interpreted as a valid version tuple, returns `nil`.
public var interpretedAsVersionTuple: VersionTupleSyntax? {
self.syntaxTextBytes.withUnsafeBufferPointer { bytes in
var parser = Parser(bytes)
let versionTuple = VersionTupleSyntax.parse(from: &parser)
if versionTuple.hasError {
return nil
}
return versionTuple
}
}
}
62 changes: 0 additions & 62 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1092,10 +1092,6 @@ extension Parser {
pattern: PatternContext,
flavor: ExprFlavor
) -> RawExprSyntax {
if flavor == .poundIfDirective, let directiveExpr = self.parsePrimaryExprForDirective() {
return RawExprSyntax(directiveExpr)
}

switch self.at(anyIn: PrimaryExpressionStart.self) {
case (.integerLiteral, let handle)?:
let literal = self.eat(handle)
Expand Down Expand Up @@ -1237,18 +1233,6 @@ extension Parser {
return RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
}
}

// try to parse a primary expression for a directive
mutating func parsePrimaryExprForDirective() -> RawExprSyntax? {
switch self.at(anyIn: CompilationCondition.self) {
case (.canImport, let handle)?:
return RawExprSyntax(self.parseCanImportExpression(handle))

// TODO: add case `swift` and `compiler` here
default:
return nil
}
}
}

extension Parser {
Expand Down Expand Up @@ -2391,52 +2375,6 @@ extension Parser {
}
}

// MARK: Platform Condition
extension Parser {
mutating func parseCanImportExpression(_ handle: TokenConsumptionHandle) -> RawExprSyntax {
let canImportKeyword = self.eat(handle)

let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)

let (unexpectedBeforeImportPath, importPath) = self.expect(.identifier)

var versionInfo: RawCanImportVersionInfoSyntax?

if let comma = self.consume(if: .comma) {
let (unexpectedBeforeLabel, label) = self.expect(anyIn: CanImportVersionInfoSyntax.LabelOptions.self, default: ._version)
let (unexpectedBeforeColon, colon) = self.expect(.colon)

let version = self.parseVersionTuple(maxComponentCount: 4)

versionInfo = RawCanImportVersionInfoSyntax(
comma: comma,
unexpectedBeforeLabel,
label: label,
unexpectedBeforeColon,
colon: colon,
version: version,
arena: self.arena
)
}

let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)

return RawExprSyntax(
RawCanImportExprSyntax(
canImportKeyword: canImportKeyword,
unexpectedBeforeLeftParen,
leftParen: leftParen,
unexpectedBeforeImportPath,
importPath: importPath,
versionInfo: versionInfo,
unexpectedBeforeRightParen,
rightParen: rightParen,
arena: self.arena
)
)
}
}

// MARK: Lookahead

extension Parser.Lookahead {
Expand Down
18 changes: 18 additions & 0 deletions Sources/SwiftParser/generated/LayoutNodes+Parsable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,24 @@ extension TypeSyntax: SyntaxParseable {
}
}

extension VersionTupleSyntax: SyntaxParseable {
public static func parse(from parser: inout Parser) -> Self {
// Keep the parser alive so that the arena in which `raw` is allocated
// doesn’t get deallocated before we have a chance to create a syntax node
// from it. We can’t use `parser.arena` as the parameter to
// `Syntax(raw:arena:)` because the node might have been re-used during an
// incremental parse and would then live in a different arena than
// `parser.arena`.
defer {
withExtendedLifetime(parser) {
}
}
let node = parser.parseVersionTuple()
let raw = RawSyntax(parser.parseRemainder(into: node))
return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self)
}
}

fileprivate extension Parser {
mutating func parseNonOptionalCodeBlockItem() -> RawCodeBlockItemSyntax {
guard let node = self.parseCodeBlockItem(isAtTopLevel: false, allowInitDecl: true) else {
Expand Down
44 changes: 0 additions & 44 deletions Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -595,50 +595,6 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
return .visitChildren
}

public override func visit(_ node: CanImportExprSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
}

if let versionTuple = node.versionInfo?.version,
let unexpectedVersionTuple = node.unexpectedBetweenVersionInfoAndRightParen
{
if versionTuple.major.isMissing {
addDiagnostic(
versionTuple,
CannotParseVersionTuple(versionTuple: unexpectedVersionTuple),
handledNodes: [versionTuple.id, unexpectedVersionTuple.id]
)
} else {
addDiagnostic(
unexpectedVersionTuple,
.canImportWrongNumberOfParameter,
handledNodes: [unexpectedVersionTuple.id]
)
}
}

return .visitChildren
}

public override func visit(_ node: CanImportVersionInfoSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
}

if node.label.isMissing {
addDiagnostic(
node.label,
.canImportWrongSecondParameterLabel,
handledNodes: [node.label.id]
)

handledNodes.append(contentsOf: [node.unexpectedBetweenLabelAndColon?.id, node.colon.id, node.version.id].compactMap { $0 })
}

return .visitChildren
}

public override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,6 @@ extension DiagnosticMessage where Self == StaticParserError {
public static var associatedTypeCannotUsePack: Self {
.init("associated types cannot be variadic")
}
public static var canImportWrongSecondParameterLabel: Self {
.init("2nd parameter of canImport should be labeled as _version or _underlyingVersion")
}
public static var canImportWrongNumberOfParameter: Self {
.init("canImport can take only two parameters")
}
public static var caseOutsideOfSwitchOrEnum: Self {
.init("'case' can only appear inside a 'switch' statement or 'enum' declaration")
}
Expand Down
Loading