diff --git a/.github/generate-xcode-tests-matrix.sh b/.github/generate-xcode-tests-matrix.sh index 6eaaac9..1518707 100755 --- a/.github/generate-xcode-tests-matrix.sh +++ b/.github/generate-xcode-tests-matrix.sh @@ -5,8 +5,6 @@ set -e platforms='["macOS","iOS","tvOS","watchOS","macCatalyst","visionOS"]' includes='[ - {"os":"macos-14", "xcode":"15.2", "swift": "5.9"}, - {"os":"macos-14", "xcode":"15.3", "swift": "5.10"}, {"os":"macos-15", "xcode":"16.0", "swift": "6.0"}, {"os":"macos-15", "xcode":"16.3", "swift": "6.1"}, {"os":"macos-26", "xcode":"26.0.1", "swift": "6.2"} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b33ef30..f7e1c9f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,8 +3,7 @@ name: Tests on: push: pull_request: - branches: - - "**:**" # PRs from forks have a prefix with `owner:` + pull_request_target: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -18,10 +17,6 @@ jobs: fail-fast: false matrix: include: - - os: "macos-14" - xcode: "15.1" # Swift 5.9. Same as 15.2. - - os: "macos-14" - xcode: "15.3" # Swift 5.10. Same as 15.4. - os: "macos-15" xcode: "16.0" # Swift 6.0. Same as 16.1 and 16.2. - os: "macos-15" @@ -42,9 +37,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.mint - key: ${{ runner.os }}-mint-xcode_${{ matrix.xcode }}-${{ hashFiles('**/Mintfile') }} + key: ${{ runner.os }}-${{ matrix.os }}-mint-xcode_${{ matrix.xcode }}-${{ hashFiles('**/Mintfile') }} restore-keys: | - ${{ runner.os }}-mint-xcode_${{ matrix.xcode }} + ${{ runner.os }}-${{ matrix.os }}-mint-xcode_${{ matrix.xcode }} - run: mint bootstrap @@ -52,24 +47,111 @@ jobs: uses: actions/cache@v3 with: path: .build - key: ${{ runner.os }}-xcode_${{ matrix.xcode }}-swiftpm-${{ github.ref }}-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-${{ matrix.os }}-xcode_${{ matrix.xcode }}-swiftpm-${{ github.ref }}-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-xcode_${{ matrix.xcode }}-swiftpm-${{ github.ref }}- - ${{ runner.os }}-xcode_${{ matrix.xcode }}-swiftpm-main- - ${{ runner.os }}-xcode_${{ matrix.xcode }}-swiftpm- + ${{ runner.os }}-${{ matrix.os }}-xcode_${{ matrix.xcode }}-swiftpm-${{ github.ref }}- + ${{ runner.os }}-${{ matrix.os }}-xcode_${{ matrix.xcode }}-swiftpm-main- + ${{ runner.os }}-${{ matrix.os }}-xcode_${{ matrix.xcode }}-swiftpm- - name: SwiftPM tests - run: swift test --enable-code-coverage | mint run xcbeautify --renderer github-actions + run: | + set -o pipefail + swift test --enable-code-coverage | mint run xcbeautify --renderer github-actions + + - name: Convert coverage for Codecov + id: convert-coverage + uses: sersoft-gmbh/swift-coverage-action@v4 + with: + ignore-conversion-failures: true + fail-on-empty-output: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: ${{ join(fromJSON(steps.convert-coverage.outputs.files), ',') }} + fail_ci_if_error: true + + generate_swift_syntax_version: + name: Generate Swift Syntax Versions + runs-on: macos-26 + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - run: brew install mint + + - name: Cache Mint + uses: actions/cache@v3 + with: + path: ~/.mint + key: macOS-macos-26-mint-swift-dependency-compatibility-checker + + - name: Set up matrix + id: set-matrix + run: | + matrix="$(mint run --silent josephduffy/swift-dependency-compatibility-checker@main swift-syntax --resolve-only --github-actions-matrix)" + echo "${matrix}" + echo "matrix=${matrix}" >> "${GITHUB_OUTPUT}" + + test_swift_syntax_versions: + name: Swift Syntax ${{ matrix.version }} Tests + needs: + - generate_swift_syntax_version + runs-on: macos-26 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate_swift_syntax_version.outputs.matrix) }} + + steps: + - uses: actions/checkout@v4 + + - name: Select Xcode 26.0.1 + run: sudo xcode-select --switch /Applications/Xcode_26.0.1.app + + - name: Install Mint + run: brew install mint + + - name: Cache Mint + uses: actions/cache@v3 + with: + path: ~/.mint + key: macOS-macos-26-mint-xcode_26.0.1-${{ hashFiles('**/Mintfile') }} + restore-keys: | + macOS-macos-26-mint-xcode_26.0.1 + + - run: mint bootstrap + + - run: swift package resolve + + - run: swift package resolve swift-syntax --version ${{ matrix.version }} + + - name: Run Tests + # swift-macro-testing does not compile with swift-syntax 601.0.0 + if: ${{ matrix.version != '601.0.0' }} + run: | + set -o pipefail + swift test --enable-code-coverage | mint run xcbeautify --renderer github-actions + + - name: Compile Project + if: ${{ matrix.version == '601.0.0' }} + run: | + set -o pipefail + swift build | mint run xcbeautify --renderer github-actions - name: Convert coverage for Codecov id: convert-coverage uses: sersoft-gmbh/swift-coverage-action@v4 + if: ${{ matrix.version != '601.0.0' }} with: ignore-conversion-failures: true fail-on-empty-output: true - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 + if: ${{ matrix.version != '601.0.0' }} env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: @@ -113,9 +195,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.mint - key: ${{ runner.os }}-mint-xcode_${{ matrix.xcode }}-${{ hashFiles('**/Mintfile') }} + key: ${{ runner.os }}-${{ matrix.os }}-mint-xcode_${{ matrix.xcode }}-${{ hashFiles('**/Mintfile') }} restore-keys: | - ${{ runner.os }}-mint-xcode_${{ matrix.xcode }} + ${{ runner.os }}-${{ matrix.os }}-mint-xcode_${{ matrix.xcode }} - run: mint bootstrap @@ -123,10 +205,10 @@ jobs: uses: actions/cache@v3 with: path: ~/Library/Developer/Xcode/DerivedData - key: ${{ runner.os }}-xcode_${{ matrix.xcode }}-derived_data-${{ github.ref }} + key: ${{ runner.os }}-${{ matrix.os }}-xcode_${{ matrix.xcode }}-derived_data-${{ github.ref }} restore-keys: | - ${{ runner.os }}-xcode_${{ matrix.xcode }}-derived_data-main - ${{ runner.os }}-xcode_${{ matrix.xcode }}-derived_data- + ${{ runner.os }}-${{ matrix.os }}-xcode_${{ matrix.xcode }}-derived_data-main + ${{ runner.os }}-${{ matrix.os }}-xcode_${{ matrix.xcode }}-derived_data- - name: Run Tests run: | @@ -140,7 +222,7 @@ jobs: fail-on-empty-output: true - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: @@ -156,7 +238,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest"] - swift: ["5.9.2", "5.10", "6.0", "6.1", "6.2"] + swift: ["6.0", "6.1", "6.2"] steps: - uses: actions/checkout@v4 @@ -192,8 +274,8 @@ jobs: - name: Install Swift uses: compnerd/gha-setup-swift@main with: - branch: ${{ matrix.branch }} - tag: ${{ matrix.tag }} + swift-version: ${{ matrix.branch }} + swift-build: ${{ matrix.tag }} - name: swift test run: swift test diff --git a/Mintfile b/Mintfile index 18627cf..ec13f25 100644 --- a/Mintfile +++ b/Mintfile @@ -1,2 +1,2 @@ josephduffy/xcutils@v0.3.0 -tuist/xcbeautify@1.0.1 +cpisciotta/xcbeautify@2.30.1 diff --git a/Package.swift b/Package.swift index 4d33b48..d43f0de 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,8 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", from: "509.1.0"), + // Tests (specifically swift-snapshot-testing) do not support 601.0.0 but the project does build. + .package(url: "https://github.com/swiftlang/swift-syntax", "509.1.0"..<"603.0.0"), // We only really need swift-macro-testing 0.3.0 or newer but 0.4.0 is required to compile due // to breaking changes in swift-snapshot-testing. .package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.4.0"), @@ -45,12 +46,7 @@ let package = Package( .macro( name: "HashableMacroMacros", dependencies: [ - .targetItem( - name: "HashableMacroFoundation", - condition: .when( - platforms: [.macOS, .iOS, .tvOS, .watchOS, .macCatalyst] - ) - ), + "HashableMacroFoundation", .product(name: "SwiftDiagnostics", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift deleted file mode 100644 index ca616c4..0000000 --- a/Package@swift-5.9.swift +++ /dev/null @@ -1,73 +0,0 @@ -// swift-tools-version: 5.9 -import CompilerPluginSupport -import PackageDescription - -let package = Package( - name: "HashableMacro", - platforms: [ - .macOS(.v10_15), - .iOS(.v13), - .tvOS(.v13), - .watchOS(.v6), - .macCatalyst(.v13), - .visionOS(.v1), - ], - products: [ - .library( - name: "HashableMacro", - targets: ["HashableMacro"] - ), - ], - dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", from: "509.1.0"), - // We only really need swift-macro-testing 0.3.0 or newer but 0.4.0 is required to compile due - // to breaking changes in swift-snapshot-testing. - .package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.4.0"), - ], - targets: [ - .target( - name: "HashableMacro", - dependencies: [ - .targetItem( - name: "HashableMacroFoundation", - condition: .when( - platforms: [.macOS, .iOS, .tvOS, .watchOS, .macCatalyst, .visionOS] - ) - ), - "HashableMacroMacros", - ], - swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] - ), - .target( - name: "HashableMacroFoundation", - swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] - ), - .macro( - name: "HashableMacroMacros", - dependencies: [ - .targetItem( - name: "HashableMacroFoundation", - condition: .when( - platforms: [.macOS, .iOS, .tvOS, .watchOS, .macCatalyst] - ) - ), - .product(name: "SwiftDiagnostics", package: "swift-syntax"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), - ], - swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] - ), - .testTarget( - name: "HashableMacroTests", - dependencies: [ - "HashableMacro", - "HashableMacroMacros", - .product(name: "MacroTesting", package: "swift-macro-testing"), - .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), - ], - swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] - ), - ] -) diff --git a/Sources/HashableMacroMacros/Macros/HashableMacro+Hashable.swift b/Sources/HashableMacroMacros/Macros/HashableMacro+Hashable.swift index fbab69e..a5d52d8 100644 --- a/Sources/HashableMacroMacros/Macros/HashableMacro+Hashable.swift +++ b/Sources/HashableMacroMacros/Macros/HashableMacro+Hashable.swift @@ -55,16 +55,29 @@ extension HashableMacro { ) } + let hashFunctionType: AttributedTypeSyntax + + #if canImport(SwiftSyntax600) + hashFunctionType = AttributedTypeSyntax( + specifiers: [ + .simpleTypeSpecifier(SimpleTypeSpecifierSyntax(specifier: .keyword(.inout))) + ], + baseType: TypeSyntax(stringLiteral: "Hasher") + ) + #else + hashFunctionType = AttributedTypeSyntax( + specifier: .keyword(.inout), + baseType: TypeSyntax(stringLiteral: "Hasher") + ) + #endif + let hashFunctionSignature = FunctionSignatureSyntax( parameterClause: FunctionParameterClauseSyntax( parameters: [ FunctionParameterSyntax( firstName: .identifier("into"), secondName: .identifier("hasher"), - type: AttributedTypeSyntax( - specifier: .keyword(.inout), - baseType: TypeSyntax(stringLiteral: "Hasher") - ) + type: hashFunctionType ), ] ) diff --git a/Tests/HashableMacroTests/HashableMacroTests.swift b/Tests/HashableMacroTests/HashableMacroTests.swift index 8c10714..77d34ef 100644 --- a/Tests/HashableMacroTests/HashableMacroTests.swift +++ b/Tests/HashableMacroTests/HashableMacroTests.swift @@ -192,6 +192,13 @@ final class HashableMacroTests: XCTestCase { func testEmbeddedType() throws { #if canImport(HashableMacroMacros) + #if canImport(SwiftSyntax600) + let outputsFullTypeName = true + #else + // This should always out e.g. `extension Outer.InnerStruct` but the tester does not output + // this until Swift Syntax 600.0.0. + let outputsFullTypeName = false + #endif assertMacro(testMacros) { """ enum Outer { @@ -212,7 +219,6 @@ final class HashableMacroTests: XCTestCase { } """ } expansion: { - // This should be e.g. `extension Outer.InnerStruct` but the tester does not output this. """ enum Outer { struct InnerStruct { @@ -227,25 +233,25 @@ final class HashableMacroTests: XCTestCase { } } - extension InnerStruct { + extension \(outputsFullTypeName ? "Outer." : "")InnerStruct { func hash(into hasher: inout Hasher) { hasher.combine(self.hashedProperty) } } - extension InnerStruct { + extension \(outputsFullTypeName ? "Outer." : "")InnerStruct { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.hashedProperty == rhs.hashedProperty } } - extension InnerClass { + extension \(outputsFullTypeName ? "Outer." : "")InnerClass { final func hash(into hasher: inout Hasher) { hasher.combine(self.hashedProperty) } } - extension InnerClass { + extension \(outputsFullTypeName ? "Outer." : "")InnerClass { static func ==(lhs: Outer.InnerClass, rhs: Outer.InnerClass) -> Bool { return lhs.hashedProperty == rhs.hashedProperty } @@ -1001,6 +1007,27 @@ final class HashableMacroTests: XCTestCase { func testHashedAttachedToMultiplePropertyDeclaration() throws { #if canImport(HashableMacroMacros) + #if canImport(SwiftSyntax510) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: true) + struct TestStruct { + @Hashed + var hashedProperty, secondHashedProperty: String + } + """ + } diagnostics: { + """ + @Hashable(_disableNSObjectSubclassSupport: true) + struct TestStruct { + @Hashed + ┬────── + ╰─ 🛑 peer macro can only be applied to a single variable + var hashedProperty, secondHashedProperty: String + } + """ + } + #else assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: true) @@ -1030,6 +1057,7 @@ final class HashableMacroTests: XCTestCase { } """ } + #endif #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif @@ -1516,8 +1544,20 @@ final class HashableMacroTests: XCTestCase { // MARK: - Edge cases func testPropertyAfterIfConfig() throws { + #if canImport(SwiftSyntax602) + let includesCompilerConditionals = true + #else // The `#if os(macOS)` will be parsed an attribute of the // `notHashedProperty` property. + let includesCompilerConditionals = false + #endif + let conditionalNotHashed = + """ + #if os(macOS) + @NotHashed + #endif + + """ #if canImport(HashableMacroMacros) assertMacro(testMacros) { """ @@ -1525,18 +1565,14 @@ final class HashableMacroTests: XCTestCase { struct Test { @Hashed var hashablePropery: String - - #if os(macOS) - @NotHashed - #endif - var notHashedProperty: String + \(conditionalNotHashed)var notHashedProperty: String } """ } expansion: { """ struct Test { var hashablePropery: String - var notHashedProperty: String + \(includesCompilerConditionals ? conditionalNotHashed : "")var notHashedProperty: String } extension Test {