diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 11c836b..f6d600b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -125,3 +125,20 @@ jobs: run: swift build --build-tests - name: Test run: swift test --skip-build + + windows_swift_6_1: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Swift + uses: SwiftyLab/setup-swift@latest + with: + swift-version: "6.1.2" + visual-studio-components: Microsoft.VisualStudio.Component.Windows11SDK.22621 + - name: Version + run: swift --version + - name: Build + run: swift build --build-tests + - name: Test + run: swift test --skip-build diff --git a/Sources/KeyValueDecoder.swift b/Sources/KeyValueDecoder.swift index 5d0794d..6cef1a4 100644 --- a/Sources/KeyValueDecoder.swift +++ b/Sources/KeyValueDecoder.swift @@ -29,7 +29,6 @@ // SOFTWARE. // -import CoreFoundation import Foundation /// Top level encoder that converts `[String: Any]`, `[Any]` or `Any` into `Codable` types. @@ -779,22 +778,44 @@ extension NSNumber { } } - func getNumberTypeID() -> CFNumberType? { - guard CFGetTypeID(self as CFTypeRef) == CFNumberGetTypeID() else { return nil } -#if canImport(Darwin) - return CFNumberGetType(self as CFNumber) -#else - guard type(of: self) != type(of: NSNumber(true)) else { return nil } + enum NumberTypeID: Int, Sendable { + case sInt8Type = 1 + case sInt16Type = 2 + case sInt32Type = 3 + case sInt64Type = 4 + case float32Type = 5 + case float64Type = 6 + case charType = 7 + case shortType = 8 + case intType = 9 + case longType = 10 + case longLongType = 11 + case floatType = 12 + case doubleType = 13 + case cfIndexType = 14 + case nsIntegerType = 15 + case cgFloatType = 16 + } + + func getNumberTypeID() -> NumberTypeID? { + // Prevent misclassifying Bool as charType + if type(of: self) == type(of: NSNumber(value: true)) { return nil } + switch String(cString: objCType) { - case "c": return CFNumberType.charType - case "s": return CFNumberType.shortType - case "i": return CFNumberType.intType - case "q": return CFNumberType.longLongType - case "d": return CFNumberType.doubleType - case "f": return CFNumberType.floatType - case "Q": return CFNumberType.longLongType - default: return nil + case "c": return .charType + case "C": return .sInt8Type + case "s": return .shortType + case "S": return .sInt16Type + case "i": return .intType + case "I": return .sInt32Type + case "l": return .longType + case "L": return .sInt32Type + case "q": return .longLongType + case "Q": return .sInt64Type + case "f": return .floatType + case "d": return .doubleType + case "B": return nil + default: return nil } -#endif } } diff --git a/Tests/KeyValueDecoderTests.swift b/Tests/KeyValueDecoderTests.swift index 09e94a7..a0d58cf 100644 --- a/Tests/KeyValueDecoderTests.swift +++ b/Tests/KeyValueDecoderTests.swift @@ -892,7 +892,47 @@ struct KeyValueDecoderTests { #expect((true as NSNumber).getDoubleValue() == nil) } - @Test +#if compiler(>=6.1) + @Test() + func decodingErrors() throws { + + var error = #expect(throws: DecodingError.self) { + try KeyValueDecoder.decode(Seafood.self, from: 10) + } + #expect(error?.debugDescription == "Expected String at SELF, found Int") + + error = #expect(throws: DecodingError.self) { + try KeyValueDecoder.decode(Seafood.self, from: 10) + } + #expect(error?.debugDescription == "Expected String at SELF, found Int") + + error = #expect(throws: DecodingError.self) { + try KeyValueDecoder.decode(Int.self, from: NSNull()) + } + #expect(error?.debugDescription == "Expected BinaryInteger at SELF, found NSNull") + + error = #expect(throws: DecodingError.self) { + try KeyValueDecoder.decode(Int.self, from: Optional.none) + } + #expect(error?.debugDescription == "Expected BinaryInteger at SELF, found nil") + + error = #expect(throws: DecodingError.self) { + try KeyValueDecoder.decode([Int].self, from: [0, 1, true] as [Any]) + } + #expect(error?.debugDescription == "Expected BinaryInteger at SELF[2], found Bool") + + error = #expect(throws: DecodingError.self) { + try KeyValueDecoder.decode(AllTypes.self, from: ["tArray": [["tString": 0]]] as [String: Any]) + } + #expect(error?.debugDescription == "Expected String at SELF.tArray[0].tString, found Int") + + error = #expect(throws: DecodingError.self) { + try KeyValueDecoder.decode(AllTypes.self, from: ["tArray": [["tString": 0]]] as [String: Any]) + } + #expect(error?.debugDescription == "Expected String at SELF.tArray[0].tString, found Int") + } +#else + @Test() func decodingErrors() throws { expectDecodingError(try KeyValueDecoder.decode(Seafood.self, from: 10)) { error in #expect(error.debugDescription == "Expected String at SELF, found Int") @@ -913,6 +953,7 @@ struct KeyValueDecoderTests { #expect(error.debugDescription == "Expected String at SELF.tArray[0].tString, found Int") } } +#endif @Test func int_ClampsDoubles() { @@ -997,6 +1038,7 @@ struct KeyValueDecoderTests { #endif } +#if compiler(<6.1) func expectDecodingError(_ expression: @autoclosure () throws -> T, file: String = #filePath, line: Int = #line, @@ -1012,6 +1054,7 @@ func expectDecodingError(_ expression: @autoclosure () throws -> T, Issue.record(error, "Expected DecodingError", sourceLocation: location) } } +#endif extension DecodingError {