diff --git a/CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift b/CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift index be317e400de..8e1a2470d28 100644 --- a/CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift @@ -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", diff --git a/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift b/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift index 525687a2250..25adea287dd 100644 --- a/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift @@ -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( @@ -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( diff --git a/CodeGeneration/Sources/SyntaxSupport/Node.swift b/CodeGeneration/Sources/SyntaxSupport/Node.swift index 5fec1d979a0..0d77eef567d 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Node.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Node.swift @@ -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? @@ -106,6 +109,9 @@ public class Node { """ experimentalSPI.with(\.trailingTrivia, .newline) } + if let deprecationMessage { + "@available(*, deprecated, message: \(literal: deprecationMessage))" + } if forRaw { "@_spi(RawSyntax)" } @@ -127,6 +133,7 @@ public class Node { init( kind: SyntaxNodeKind, base: SyntaxNodeKind, + deprecationMessage: String? = nil, experimentalFeature: ExperimentalFeature? = nil, nameForDiagnostics: String?, documentation: String? = nil, @@ -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) @@ -271,6 +279,7 @@ public class Node { init( kind: SyntaxNodeKind, base: SyntaxNodeKind, + deprecationMessage: String? = nil, experimentalFeature: ExperimentalFeature? = nil, nameForDiagnostics: String?, documentation: String? = nil, @@ -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) diff --git a/Release Notes/600.md b/Release Notes/600.md index e9e318e5c00..eb9fc8fe80a 100644 --- a/Release Notes/600.md +++ b/Release Notes/600.md @@ -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 @@ -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: diff --git a/Sources/SwiftParser/Availability.swift b/Sources/SwiftParser/Availability.swift index d20b1ca50cc..449448a368e 100644 --- a/Sources/SwiftParser/Availability.swift +++ b/Sources/SwiftParser/Availability.swift @@ -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, @@ -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, @@ -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 } @@ -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.. 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) @@ -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 { @@ -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 { diff --git a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift index 364bcf12c17..1fe2e6b16a2 100644 --- a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift +++ b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift @@ -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 { diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index 5db948f941a..da7a11e201c 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -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 diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index 86430581513..039f311feb0 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -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") } diff --git a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift index 5581f94333a..17eb0b51b3e 100644 --- a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift @@ -272,18 +272,22 @@ open class SyntaxAnyVisitor: SyntaxVisitor { visitAnyPost(node._syntaxNode) } + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") override open func visit(_ node: CanImportExprSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") override open func visitPost(_ node: CanImportExprSyntax) { visitAnyPost(node._syntaxNode) } + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") override open func visit(_ node: CanImportVersionInfoSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") override open func visitPost(_ node: CanImportVersionInfoSyntax) { visitAnyPost(node._syntaxNode) } diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index ddffa06852c..cf24a56b678 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -42,7 +42,9 @@ public enum SyntaxEnum: Sendable { case booleanLiteralExpr(BooleanLiteralExprSyntax) case borrowExpr(BorrowExprSyntax) case breakStmt(BreakStmtSyntax) + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") case canImportExpr(CanImportExprSyntax) + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") case canImportVersionInfo(CanImportVersionInfoSyntax) case catchClauseList(CatchClauseListSyntax) case catchClause(CatchClauseSyntax) @@ -993,7 +995,9 @@ public enum ExprSyntaxEnum { case binaryOperatorExpr(BinaryOperatorExprSyntax) case booleanLiteralExpr(BooleanLiteralExprSyntax) case borrowExpr(BorrowExprSyntax) + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") case canImportExpr(CanImportExprSyntax) + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") case canImportVersionInfo(CanImportVersionInfoSyntax) case closureExpr(ClosureExprSyntax) case consumeExpr(ConsumeExprSyntax) diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index 103e0304705..3724843f407 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -42,7 +42,9 @@ public enum SyntaxKind: Sendable { case booleanLiteralExpr case borrowExpr case breakStmt + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") case canImportExpr + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") case canImportVersionInfo case catchClauseList case catchClause diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index e9f55427e02..e3b3b26d480 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -272,6 +272,7 @@ open class SyntaxRewriter { /// Visit a ``CanImportExprSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") open func visit(_ node: CanImportExprSyntax) -> ExprSyntax { return ExprSyntax(visitChildren(node)) } @@ -279,6 +280,7 @@ open class SyntaxRewriter { /// Visit a ``CanImportVersionInfoSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") open func visit(_ node: CanImportVersionInfoSyntax) -> ExprSyntax { return ExprSyntax(visitChildren(node)) } diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index 3b40df48540..7e39f3bc22b 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -361,24 +361,28 @@ open class SyntaxVisitor { /// Visiting ``CanImportExprSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") open func visit(_ node: CanImportExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } /// The function called after visiting ``CanImportExprSyntax`` and its descendants. /// - node: the node we just finished visiting. + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") open func visitPost(_ node: CanImportExprSyntax) { } /// Visiting ``CanImportVersionInfoSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") open func visit(_ node: CanImportVersionInfoSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } /// The function called after visiting ``CanImportVersionInfoSyntax`` and its descendants. /// - node: the node we just finished visiting. + @available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") open func visitPost(_ node: CanImportVersionInfoSyntax) { } diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesC.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesC.swift index be8f1764629..0f4a38e0889 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesC.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesC.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@_spi(RawSyntax) +@available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") @_spi(RawSyntax) public struct RawCanImportExprSyntax: RawExprSyntaxNodeProtocol { @_spi(RawSyntax) public var layoutView: RawSyntaxLayoutView { @@ -118,7 +118,7 @@ public struct RawCanImportExprSyntax: RawExprSyntaxNodeProtocol { } } -@_spi(RawSyntax) +@available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") @_spi(RawSyntax) public struct RawCanImportVersionInfoSyntax: RawExprSyntaxNodeProtocol { @_spi(RawSyntax) public var layoutView: RawSyntaxLayoutView { diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift index 7fd92ff992c..7f0b314579f 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift @@ -21,6 +21,7 @@ /// - `importPath`: `` /// - `versionInfo`: ``CanImportVersionInfoSyntax``? /// - `rightParen`: `)` +@available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") public struct CanImportExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSyntaxNodeProtocol { public let _syntaxNode: Syntax @@ -230,6 +231,7 @@ public struct CanImportExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExpr /// ### Contained in /// /// - ``CanImportExprSyntax``.``CanImportExprSyntax/versionInfo`` +@available(*, deprecated, message: "'canImport' directives are now represented as a `FunctionCallExpr`") public struct CanImportVersionInfoSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSyntaxNodeProtocol { public let _syntaxNode: Syntax diff --git a/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift b/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift index 1119428842e..9930629cd4f 100644 --- a/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift +++ b/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift @@ -122,6 +122,12 @@ extension TypeSyntax: SyntaxExpressibleByStringInterpolation {} extension TypeSyntax: @retroactive ExpressibleByStringInterpolation {} #endif +extension VersionTupleSyntax: SyntaxExpressibleByStringInterpolation {} + +#if compiler(>=6) +extension VersionTupleSyntax: @retroactive ExpressibleByStringInterpolation {} +#endif + extension AccessorDeclListSyntax: SyntaxExpressibleByStringInterpolation {} #if compiler(>=6) diff --git a/Tests/SwiftParserTest/ExpressionInterpretedAsVersionTupleTests.swift b/Tests/SwiftParserTest/ExpressionInterpretedAsVersionTupleTests.swift new file mode 100644 index 00000000000..7a80a86ffec --- /dev/null +++ b/Tests/SwiftParserTest/ExpressionInterpretedAsVersionTupleTests.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import SwiftParser +import SwiftSyntax +import XCTest +import _SwiftSyntaxTestSupport + +final class ExpressionInterpretedAsVersionTupleTest: XCTestCase { + func testExpressionInterpretedAsVersionTuple() throws { + let expression: ExprSyntax = "1.2.3.4" + let versionTuple = try XCTUnwrap(expression.interpretedAsVersionTuple) + let subtreeMatcher = SubtreeMatcher(versionTuple, markers: [:]) + try subtreeMatcher.assertSameStructure( + VersionTupleSyntax( + major: .integerLiteral("1"), + components: VersionComponentListSyntax([ + VersionComponentSyntax( + period: .periodToken(), + number: .integerLiteral("2") + ), + VersionComponentSyntax( + period: .periodToken(), + number: .integerLiteral("3") + ), + VersionComponentSyntax( + period: .periodToken(), + number: .integerLiteral("4") + ), + ]) + ) + ) + } + + func testExpressionInterpretedAsInvalidVersionTuple() throws { + let expression: ExprSyntax = "2.0 + 1. + 1" + XCTAssertNil(expression.interpretedAsVersionTuple) + } + + func testMissingMajorComponent() throws { + let expression: ExprSyntax = ".1.2" + XCTAssertNil(expression.interpretedAsVersionTuple) + } + + func testMissingSubComponentNumber() throws { + let expression: ExprSyntax = "1.2." + XCTAssertNil(expression.interpretedAsVersionTuple) + } +} diff --git a/Tests/SwiftParserTest/translated/IfconfigExprTests.swift b/Tests/SwiftParserTest/translated/IfconfigExprTests.swift index 30ba480f043..2b578f84610 100644 --- a/Tests/SwiftParserTest/translated/IfconfigExprTests.swift +++ b/Tests/SwiftParserTest/translated/IfconfigExprTests.swift @@ -262,10 +262,7 @@ final class IfconfigExprTests: ParserTestCase { #if canImport(A, _version: 2.2.2.21️⃣.2) let a = 1 #endif - """, - diagnostics: [ - DiagnosticSpec(message: "trailing components of version 2.2.2.2 are ignored", severity: .warning) - ] + """ ) } @@ -315,10 +312,7 @@ final class IfconfigExprTests: ParserTestCase { #if canImport(A, 1️⃣unknown: 2.2) let a = 1 #endif - """, - diagnostics: [ - DiagnosticSpec(message: "2nd parameter of canImport should be labeled as _version or _underlyingVersion") - ] + """ ) } @@ -330,8 +324,13 @@ final class IfconfigExprTests: ParserTestCase { #endif """, diagnostics: [ - DiagnosticSpec(message: "2nd parameter of canImport should be labeled as _version or _underlyingVersion") - ] + DiagnosticSpec(message: "expected value in function call", fixIts: ["insert value"]) + ], + fixedSource: """ + #if canImport(A, <#expression#>) + let a = 1 + #endif + """ ) } @@ -341,10 +340,7 @@ final class IfconfigExprTests: ParserTestCase { #if canImport(A, 1️⃣2.2) let a = 1 #endif - """, - diagnostics: [ - DiagnosticSpec(message: "2nd parameter of canImport should be labeled as _version or _underlyingVersion") - ] + """ ) } @@ -354,11 +350,7 @@ final class IfconfigExprTests: ParserTestCase { #if canImport(A, 1️⃣2.22️⃣, 1.1) let a = 1 #endif - """, - diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "2nd parameter of canImport should be labeled as _version or _underlyingVersion"), - DiagnosticSpec(locationMarker: "2️⃣", message: "canImport can take only two parameters"), - ] + """ ) } @@ -370,10 +362,10 @@ final class IfconfigExprTests: ParserTestCase { #endif """, diagnostics: [ - DiagnosticSpec(message: "expected version tuple in 'canImport' expression", fixIts: ["insert version tuple"]) + DiagnosticSpec(message: "expected value in function call", fixIts: ["insert value"]) ], fixedSource: """ - #if canImport(A, _version: <#integer literal#>) + #if canImport(A, _version: <#expression#>) let a = 1 #endif """ @@ -386,10 +378,7 @@ final class IfconfigExprTests: ParserTestCase { #if canImport(A, _version: 1️⃣"") let a = 1 #endif - """#, - diagnostics: [ - DiagnosticSpec(message: #"cannot parse version component code '""'"#) - ] + """# ) } @@ -399,22 +388,19 @@ final class IfconfigExprTests: ParserTestCase { #if canImport(A, _version: 1️⃣>=2.2) let a = 1 #endif - """, - diagnostics: [ - DiagnosticSpec(message: "cannot parse version component code '>=2.2'") - ] + """ ) } func testIfconfigExpr29() { assertParse( """ - #if canImport(A, _version: 1️⃣20A301) + #if canImport(A, _version: 201️⃣A301) let a = 1 #endif """, diagnostics: [ - DiagnosticSpec(message: "cannot parse version component code '20A301'") + DiagnosticSpec(message: "'A' is not a valid digit in integer literal") ] ) } @@ -425,10 +411,7 @@ final class IfconfigExprTests: ParserTestCase { #if canImport(A, _version: 1️⃣"20A301") let a = 1 #endif - """#, - diagnostics: [ - DiagnosticSpec(message: #"cannot parse version component code '"20A301"'"#) - ] + """# ) } @@ -720,4 +703,13 @@ final class IfconfigExprTests: ParserTestCase { ) ) } + + func testCanImportWithStringVersion() { + assertParse( + """ + #if canImport(MyModule, _version: "1.2.3") + #endif + """ + ) + } }