Skip to content

Commit

Permalink
Add swift-syntax 510 support; matrix testing for swift-syntax versions
Browse files Browse the repository at this point in the history
- Implement matrix strategy for macOS job to test against multiple
  versions of swift-syntax
- Configure SWIFT_SYNTAX_VERSION environment variable to dictate
  swift-syntax version per test run
- Maintain existing setup for Linux CI (only test "natural"
  `Package.swift` swift-syntax version), but update to use
  `swift-actions/setup-swift` and Swift 5.10.
  • Loading branch information
gohanlon committed Apr 12, 2024
1 parent 84f14bc commit 54fca03
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 13 deletions.
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ jobs:
macos:
name: macOS
runs-on: macos-13
strategy:
matrix:
swift-syntax-version: ['509.0.0..<510.0.0', '510.0.0..<511.0.0']

steps:
- uses: actions/checkout@v4
- name: Select Xcode 15
run: sudo xcode-select -s /Applications/Xcode_15.0.app
- name: Set SWIFT_SYNTAX_VERSION environment variable
run: echo "SWIFT_SYNTAX_VERSION=${{ matrix.swift-syntax-version }}" >> $GITHUB_ENV
- name: Resolve Dependencies
run: swift package resolve
- name: Run tests
run: swift test

Expand All @@ -29,9 +37,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install Swift
uses: slashmo/install-swift@v0.4.0
uses: swift-actions/setup-swift@v2
with:
version: 5.9
swift-version: "5.10"
- uses: actions/checkout@v4
- name: Run tests
run: swift test
Expand Down
10 changes: 5 additions & 5 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
"state" : {
"revision" : "59b663f68e69f27a87b45de48cb63264b8194605",
"version" : "1.15.1"
"revision" : "625ccca8570773dd84a34ee51a81aa2bc5a4f97a",
"version" : "1.16.0"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"location" : "https://github.com/apple/swift-syntax",
"state" : {
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
"revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd",
"version" : "510.0.1"
}
}
],
Expand Down
36 changes: 35 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// swift-tools-version: 5.9

import CompilerPluginSupport
import Foundation
import PackageDescription

let package = Package(
Expand All @@ -23,7 +24,9 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.15.0"),
.package(url: "https://github.com/apple/swift-syntax", from: "509.0.0"),
// .conditionalPackage(url: "https://github.com/apple/swift-syntax", envVar: "SWIFT_SYNTAX_VERSION", default: "509.0.0..<510.0.0")
// .conditionalPackage(url: "https://github.com/apple/swift-syntax", envVar: "SWIFT_SYNTAX_VERSION", default: "510.0.0..<511.0.0")
.conditionalPackage(url: "https://github.com/apple/swift-syntax", envVar: "SWIFT_SYNTAX_VERSION", default: "509.0.0..<511.0.0")
],
targets: [
.macro(
Expand Down Expand Up @@ -68,3 +71,34 @@ let package = Package(
),
]
)

extension Package.Dependency {
/// Creates a package dependency based on an environment variable or uses a default version expression.
static func conditionalPackage(url: String, envVar: String, default versionExpression: String) -> Package.Dependency {
let versionRangeString = ProcessInfo.processInfo.environment[envVar] ?? versionExpression
let (lower, op, upper) = parseVersionExpression(from: versionRangeString)
if op == "..<" {
return .package(url: url, lower..<upper)
} else if op == "..." {
return .package(url: url, lower...upper)
} else {
fatalError("Unsupported version range operator: \(op)")
}
}

private static func parseVersionExpression(from expression: String) -> (Version, String, Version) {
let rangeOperators = ["..<", "..."]
for op in rangeOperators {
if expression.contains(op) {
let parts = expression.split(separator: op, maxSplits: 1, omittingEmptySubsequences: true).map(String.init)
guard parts.count == 2,
let lower = Version(parts[0]),
let upper = Version(parts[1]) else {
fatalError("Invalid version expression format: \(expression)")
}
return (lower, op, upper)
}
}
fatalError("No valid range operator found in expression: \(expression)")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ extension ExprSyntax {
else { return nil }
return .tuple(elementTypes)

#if canImport(SwiftSyntax510)
case .thenStmt:
return nil
#endif

case .token, .accessorBlock, .accessorDeclList, .accessorDecl, .accessorEffectSpecifiers,
.accessorParameters, .actorDecl, .arrayElementList, .arrayElement, .arrayType, .arrowExpr,
.assignmentExpr, .associatedTypeDecl, .attributeList, .attribute, .attributedType,
Expand Down
35 changes: 35 additions & 0 deletions Tests/MacroTestingTests/DictionaryStorageMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,40 @@ final class DictionaryStorageMacroTests: BaseTestCase {
}

func testExpansionConvertsStoredProperties() {
#if canImport(SwiftSyntax510)
assertMacro {
"""
@DictionaryStorage
struct Point {
var x: Int = 1
var y: Int = 2
}
"""
} expansion: {
"""
struct Point {
var x: Int {
get {
_storage["x", default: 1] as! Int
}
set {
_storage["x"] = newValue
}
}
var y: Int {
get {
_storage["y", default: 2] as! Int
}
set {
_storage["y"] = newValue
}
}
var _storage: [String: Any] = [:]
}
"""
}
#elseif canImport(SwiftSyntax509)
assertMacro {
"""
@DictionaryStorage
Expand Down Expand Up @@ -46,6 +80,7 @@ final class DictionaryStorageMacroTests: BaseTestCase {
}
"""
}
#endif
}

func testExpansionWithoutInitializersEmitsError() {
Expand Down
2 changes: 1 addition & 1 deletion Tests/MacroTestingTests/MacroExamples/AddBlocker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,6 @@ public struct AddBlocker: ExpressionMacro {
context.diagnose(diag)
}

return result.asProtocol(FreestandingMacroExpansionSyntax.self)!.argumentList.first!.expression
return result.asProtocol(FreestandingMacroExpansionSyntax.self)!.arguments.first!.expression
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public enum FontLiteralMacro: ExpressionMacro {
in context: some MacroExpansionContext
) throws -> ExprSyntax {
let argList = replaceFirstLabel(
of: node.argumentList,
of: node.arguments,
with: "fontLiteralName"
)
return ".init(\(argList))"
Expand Down
2 changes: 1 addition & 1 deletion Tests/MacroTestingTests/MacroExamples/StringifyMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public enum StringifyMacro: ExpressionMacro {
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
guard let argument = node.argumentList.first?.expression else {
guard let argument = node.arguments.first?.expression else {
fatalError("compiler bug: the macro does not have any arguments")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import SwiftSyntax
import SwiftSyntaxMacros

#if !canImport(SwiftSyntax510) && canImport(SwiftSyntax509)
extension FreestandingMacroExpansionSyntax {
var arguments: LabeledExprListSyntax {
get { self.argumentList }
set { self.argumentList = newValue }
}
}
#endif
2 changes: 1 addition & 1 deletion Tests/MacroTestingTests/MacroExamples/URLMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public enum URLMacro: ExpressionMacro {
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
guard let argument = node.argumentList.first?.expression,
guard let argument = node.arguments.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first
Expand Down
2 changes: 1 addition & 1 deletion Tests/MacroTestingTests/MacroExamples/WarningMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public enum WarningMacro: ExpressionMacro {
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
guard let firstElement = node.argumentList.first,
guard let firstElement = node.arguments.first,
let stringLiteral = firstElement.expression
.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
Expand Down
104 changes: 104 additions & 0 deletions Tests/MemberwiseInitTests/MemberwiseInitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2046,7 +2046,9 @@ final class MemberwiseInitTests: XCTestCase {
}
}

// TODO: regress MemberwiseInit to match swift-syntax 5.10 limitation
func testCustomInitEscapingWithMultipleBindings() {
#if canImport(SwiftSyntax510)
assertMacro {
"""
@MemberwiseInit
Expand All @@ -2069,10 +2071,76 @@ final class MemberwiseInitTests: XCTestCase {
}
}
"""
} diagnostics: {
"""
@MemberwiseInit
struct S {
@Init(escaping: true)
┬────────────────────
╰─ 🛑 peer macro can only be applied to a single variable
let v, r: T
}
"""
}
#elseif canImport(SwfitSyntax509)
assertMacro {
"""
@MemberwiseInit
struct S {
@Init(escaping: true)
let v, r: T
}
"""
} expansion: {
"""
struct S {
let v, r: T
internal init(
v: @escaping T,
r: @escaping T
) {
self.v = v
self.r = r
}
}
"""
}
#endif
}

// TODO: Consider regressing MemberwiseInit to match swift-syntax 5.10 limitation (or leave redundant error, but that has a fix-it)
func testCustomLabelWithMultipleBindings_FailsWithDiagnostic() {
#if canImport(SwiftSyntax510)
assertMacro {
"""
@MemberwiseInit(.public)
public struct Person {
@Init(label: "with") public let firstName, lastName: String
}
"""
} expansion: {
"""
public struct Person {
public let firstName, lastName: String
public init() {
}
}
"""
} diagnostics: {
"""
@MemberwiseInit(.public)
public struct Person {
@Init(label: "with") public let firstName, lastName: String
┬────────────
│ ╰─ 🛑 Custom 'label' can't be applied to multiple bindings
┬───────────────────
╰─ 🛑 peer macro can only be applied to a single variable
}
"""
}
#elseif canImport(SwfitSyntax509)
assertMacro {
"""
@MemberwiseInit(.public)
Expand All @@ -2099,9 +2167,44 @@ final class MemberwiseInitTests: XCTestCase {
}
"""
}
#endif
}

// TODO: Consider regressing MemberwiseInit to match swift-syntax 5.10 limitation (or leave redundant error, but that has a fix-it)
func testLabellessCustomInitForMultipleBindings() {
#if canImport(SwiftSyntax510)
assertMacro {
"""
@MemberwiseInit(.public)
public struct Person {
@Init(label: "_") public let firstName, lastName: String
}
"""
} expansion: {
"""
public struct Person {
public let firstName, lastName: String
public init(
_ firstName: String,
_ lastName: String
) {
self.firstName = firstName
self.lastName = lastName
}
}
"""
} diagnostics: {
"""
@MemberwiseInit(.public)
public struct Person {
@Init(label: "_") public let firstName, lastName: String
┬────────────────
╰─ 🛑 peer macro can only be applied to a single variable
}
"""
}
#elseif canImport(SwfitSyntax509)
assertMacro {
"""
@MemberwiseInit(.public)
Expand All @@ -2124,6 +2227,7 @@ final class MemberwiseInitTests: XCTestCase {
}
"""
}
#endif
}

func testCustomInitIgnore() {
Expand Down

0 comments on commit 54fca03

Please sign in to comment.