diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dfc7cd..34892ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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 diff --git a/Package.resolved b/Package.resolved index ddf0806..0c7ecd1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -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" } } ], diff --git a/Package.swift b/Package.swift index 2fcfe5e..45aef30 100644 --- a/Package.swift +++ b/Package.swift @@ -1,6 +1,7 @@ // swift-tools-version: 5.9 import CompilerPluginSupport +import Foundation import PackageDescription let package = Package( @@ -23,7 +24,13 @@ 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( @@ -68,3 +75,55 @@ let package = Package( ), ] ) + +extension Package.Dependency { + /// Creates a dependency based on an environment variable or a default version range. + /// + /// This function allows dynamically setting the version range of a package dependency via an environment variable. + /// If the environment variable is not set, it falls back to a specified default version range. + /// + /// - Parameters: + /// - url: The URL of the package repository. + /// - envVar: The name of the environment variable that contains the version range. + /// - versionExpression: The default version range in case the environment variable is not set. + /// Example format: `"509.0.0..<511.0.0"` or `"509.0.0...510.0.0"`. + /// - Returns: A `Package.Dependency` configured with the specified or default version range. + /// - Throws: A fatal error if the version expression format is invalid or the range operator is unsupported. + /// + 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.. (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)") + } +} diff --git a/Sources/MemberwiseInitMacros/Macros/Support/ExprTypeInference.swift b/Sources/MemberwiseInitMacros/Macros/Support/ExprTypeInference.swift index 240adbe..c8238bb 100644 --- a/Sources/MemberwiseInitMacros/Macros/Support/ExprTypeInference.swift +++ b/Sources/MemberwiseInitMacros/Macros/Support/ExprTypeInference.swift @@ -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, diff --git a/Tests/MacroTestingTests/DictionaryStorageMacroTests.swift b/Tests/MacroTestingTests/DictionaryStorageMacroTests.swift index b0bf67c..0b11b30 100644 --- a/Tests/MacroTestingTests/DictionaryStorageMacroTests.swift +++ b/Tests/MacroTestingTests/DictionaryStorageMacroTests.swift @@ -14,38 +14,73 @@ final class DictionaryStorageMacroTests: BaseTestCase { } func testExpansionConvertsStoredProperties() { - assertMacro { - """ - @DictionaryStorage - struct Point { - var x: Int = 1 - var y: Int = 2 - } - """ - } expansion: { - """ - struct Point { - var x: Int = 1 { - get { - _storage["x", default: 1] as! Int + #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 + } } - 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 + struct Point { + var x: Int = 1 + var y: Int = 2 } - var y: Int = 2 { - get { - _storage["y", default: 2] as! Int + """ + } expansion: { + """ + struct Point { + var x: Int = 1 { + get { + _storage["x", default: 1] as! Int + } + set { + _storage["x"] = newValue + } } - set { - _storage["y"] = newValue + var y: Int = 2 { + get { + _storage["y", default: 2] as! Int + } + set { + _storage["y"] = newValue + } } - } - var _storage: [String: Any] = [:] + var _storage: [String: Any] = [:] + } + """ } - """ - } + #endif } func testExpansionWithoutInitializersEmitsError() { diff --git a/Tests/MacroTestingTests/MacroExamples/AddBlocker.swift b/Tests/MacroTestingTests/MacroExamples/AddBlocker.swift index b0db043..58dfd7b 100644 --- a/Tests/MacroTestingTests/MacroExamples/AddBlocker.swift +++ b/Tests/MacroTestingTests/MacroExamples/AddBlocker.swift @@ -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 } } diff --git a/Tests/MacroTestingTests/MacroExamples/FontLiteralMacro.swift b/Tests/MacroTestingTests/MacroExamples/FontLiteralMacro.swift index 97aedda..0559ace 100644 --- a/Tests/MacroTestingTests/MacroExamples/FontLiteralMacro.swift +++ b/Tests/MacroTestingTests/MacroExamples/FontLiteralMacro.swift @@ -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))" diff --git a/Tests/MacroTestingTests/MacroExamples/StringifyMacro.swift b/Tests/MacroTestingTests/MacroExamples/StringifyMacro.swift index e351b6b..5d0d7c8 100644 --- a/Tests/MacroTestingTests/MacroExamples/StringifyMacro.swift +++ b/Tests/MacroTestingTests/MacroExamples/StringifyMacro.swift @@ -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") } diff --git a/Tests/MacroTestingTests/MacroExamples/SwiftSyntaxCompatibilityExtensions.swift b/Tests/MacroTestingTests/MacroExamples/SwiftSyntaxCompatibilityExtensions.swift new file mode 100644 index 0000000..909cba7 --- /dev/null +++ b/Tests/MacroTestingTests/MacroExamples/SwiftSyntaxCompatibilityExtensions.swift @@ -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 diff --git a/Tests/MacroTestingTests/MacroExamples/URLMacro.swift b/Tests/MacroTestingTests/MacroExamples/URLMacro.swift index 44054a2..5b5ccf8 100644 --- a/Tests/MacroTestingTests/MacroExamples/URLMacro.swift +++ b/Tests/MacroTestingTests/MacroExamples/URLMacro.swift @@ -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 diff --git a/Tests/MacroTestingTests/MacroExamples/WarningMacro.swift b/Tests/MacroTestingTests/MacroExamples/WarningMacro.swift index 35a0c9d..9f2163c 100644 --- a/Tests/MacroTestingTests/MacroExamples/WarningMacro.swift +++ b/Tests/MacroTestingTests/MacroExamples/WarningMacro.swift @@ -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, diff --git a/Tests/MemberwiseInitTests/MemberwiseInitTests.swift b/Tests/MemberwiseInitTests/MemberwiseInitTests.swift index 89ff084..e86ae6d 100644 --- a/Tests/MemberwiseInitTests/MemberwiseInitTests.swift +++ b/Tests/MemberwiseInitTests/MemberwiseInitTests.swift @@ -2046,84 +2046,188 @@ final class MemberwiseInitTests: XCTestCase { } } + // TODO: regress MemberwiseInit to match swift-syntax 5.10 limitation func testCustomInitEscapingWithMultipleBindings() { - assertMacro { - """ - @MemberwiseInit - struct S { - @Init(escaping: true) - let v, r: T + #if canImport(SwiftSyntax510) + 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 + } + } + """ + } diagnostics: { + """ + @MemberwiseInit + struct S { + @Init(escaping: true) + ┬──────────────────── + ╰─ 🛑 peer macro can only be applied to a single variable + let v, r: T + } + """ } - """ - } expansion: { - """ - struct S { - 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 + 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() { - 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() { + #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) + 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 + } + """ } - """ - } diagnostics: { - """ - @MemberwiseInit(.public) - public struct Person { - @Init(label: "with") public let firstName, lastName: String - ┬──────────── - ╰─ 🛑 Custom 'label' can't be applied to multiple bindings - } - """ - } + #endif } + // TODO: Consider regressing MemberwiseInit to match swift-syntax 5.10 limitation (or leave redundant error, but that has a fix-it) func testLabellessCustomInitForMultipleBindings() { - 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 + #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) + 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 + } } + """ } - """ - } + #endif } func testCustomInitIgnore() {