From 02d21261226dda607cd53c39c3b72d087e55b1f3 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Fri, 6 Feb 2026 00:50:22 -0800 Subject: [PATCH 01/13] Create 'CodableSupport' trait for embedded `CodableSupport` is a trait that's by default enabled. The Codable related APIs are enabled by this trait. When this trait is not enabled, the lower level parsing API remain available, and that portion must not depend on Foundation. All the stand test should pass for non-`CodableSupport` potion of the library. The non-`CodableSupport` APIs should function for embedded Swift. Few things needs to happen before this gets merged: - [ ] A reliable CI check for running tests without the default trait - [ ] A CI build for embedded Swift of the non-`CodableSupport` part - [ ] Documentation updates: - [ ] A mention of embedded support in introductory materials - [ ] An explanation of the trait - [ ] Per API annotation about availability for embeded (or trait membership?) - [ ] A dedicated article for embeded. - [ ] An test on an actual embeded device :) Closes #232 Closes #227 --- .github/workflows/build-embeded.yml | 22 ++++++ .github/workflows/build-wasm.yml | 4 +- .github/workflows/swift.yml | 4 + Makefile | 17 ++++- Package.swift | 6 +- Sources/TOMLDecoder/DateTime.swift | 40 +++++++--- Sources/TOMLDecoder/Parsing/Parser.swift | 73 ++++++++++++------- .../TOMLDecoder/Parsing/TOMLDocument.swift | 30 +++----- Sources/TOMLDecoder/Parsing/Token.swift | 68 +++++++++++++++++ Sources/TOMLDecoder/TOMLArray.swift | 6 ++ Sources/TOMLDecoder/TOMLDecoder.swift | 2 + Sources/TOMLDecoder/TOMLKey.swift | 6 +- .../TOMLKeyedDecodingContainer.swift | 7 +- ...ngleValueDecodingContainer.Generated.swift | 2 + Sources/TOMLDecoder/TOMLTable.swift | 33 +++++---- .../TOMLUnkeyedDecodingContainer.swift | 6 +- Sources/TOMLDecoder/_TOMLDecoder.swift | 2 + ...TOMLSingleValueDecodingContainer.swift.gyb | 2 + Sources/compliance/main.swift | 5 ++ .../TOMLDecoderTests/DateStrategyTests.swift | 2 + .../InvalidationTests.Generated.swift | 2 + .../LeafValueDecodingTests.Generated.swift | 2 + .../Support/TOMLComplianceSupport.swift | 2 + Tests/TOMLDecoderTests/TOMLDecoderTests.swift | 2 + .../ValidationTests.Generated.swift | 2 + .../gyb/LeafValueDecodingTests.swift.gyb | 2 + 26 files changed, 267 insertions(+), 82 deletions(-) create mode 100644 .github/workflows/build-embeded.yml diff --git a/.github/workflows/build-embeded.yml b/.github/workflows/build-embeded.yml new file mode 100644 index 00000000..9bb27f75 --- /dev/null +++ b/.github/workflows/build-embeded.yml @@ -0,0 +1,22 @@ +name: Embedded Build +on: + push: + branches: + - main + - 'release/**' + pull_request: + branches: + - main + - 'release/**' +jobs: + embedded-build: + name: Build embedded subset + runs-on: macos-26 + steps: + - name: Checkout source + uses: actions/checkout@v4 + - uses: SwiftyLab/setup-swift@latest + with: + swift-version: "6.2.2" + - name: Build embedded subset + run: swift build -c release --target TOMLDecoder --disable-default-traits -Xswiftc -target -Xswiftc arm64-apple-macos14.0 -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -wmo diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml index 7ca8580f..b0d3d74d 100644 --- a/.github/workflows/build-wasm.yml +++ b/.github/workflows/build-wasm.yml @@ -20,5 +20,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Swift for WASM run: swift sdk install https://download.swift.org/swift-6.2.3-release/wasm-sdk/swift-6.2.3-RELEASE/swift-6.2.3-RELEASE_wasm.artifactbundle.tar.gz --checksum 394040ecd5260e68bb02f6c20aeede733b9b90702c2204e178f3e42413edad2a - - name: Run tests + - name: Build run: swift build --swift-sdk swift-6.2.3-RELEASE_wasm + - name: Build without Codable + run: swift build --swift-sdk swift-6.2.3-RELEASE_wasm --disable-default-traits diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index a9ba3fec..7dc65f15 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -32,6 +32,8 @@ jobs: uses: actions/checkout@v4 - name: Run tests run: swift test + - name: Run tests without Codable + run: swift test --disable-default-traits swift-test-macos: name: ${{ matrix.os_name }} (Swift ${{ matrix.swift_version }}) @@ -50,6 +52,8 @@ jobs: uses: actions/checkout@v4 - name: Run tests run: swift test + - name: Run tests without Codable + run: swift test --disable-default-traits swift-test-simulator: name: ${{ matrix.platform }} ${{ matrix.os_version }} (Swift 6.2) diff --git a/Makefile b/Makefile index 151a0b51..8fd5f82a 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,22 @@ SHELL = /bin/bash export LANG = en_US.UTF-8 export LC_CTYPE = en_US.UTF-8 +EMBED_SWIFT ?= $(shell \ + if command -v swiftly >/dev/null 2>&1; then \ + toolchain="$$(swiftly use --print-location 2>/dev/null | head -n1)"; \ + if [ -x "$$toolchain/usr/bin/swift" ]; then \ + echo "$$toolchain/usr/bin/swift"; \ + else \ + command -v swift; \ + fi; \ + else \ + command -v swift; \ + fi \ +) .DEFAULT_GOAL := format -.PHONY: build test generate-code generate-tests benchmark format docs +.PHONY: build test embedded generate-code generate-tests benchmark format docs docs: @Scripts/generate-docs.sh / @@ -18,6 +30,9 @@ generate-tests: build: generate-code @swift build -c release -Xswiftc -warnings-as-errors > /dev/null +embedded: generate-code + @$(EMBED_SWIFT) build -c release --target TOMLDecoder --disable-default-traits -Xswiftc -target -Xswiftc arm64-apple-macos14.0 -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -wmo + test: generate-tests @swift test -Xswiftc -warnings-as-errors diff --git a/Package.swift b/Package.swift index 25bd292d..422da71a 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.1 import Foundation import PackageDescription @@ -89,6 +89,10 @@ let package = Package( .executable(name: "compliance", targets: ["compliance"]), .library(name: "TOMLDecoder", targets: ["TOMLDecoder"]), ], + traits: [ + "CodableSupport", + .default(enabledTraits: ["CodableSupport"]), + ], dependencies: benchmarksDeps + docsDeps + formattingDeps, targets: targets + testTargets + benchmarkTargets, cxxLanguageStandard: .cxx20 diff --git a/Sources/TOMLDecoder/DateTime.swift b/Sources/TOMLDecoder/DateTime.swift index e38b5daa..6fab7643 100644 --- a/Sources/TOMLDecoder/DateTime.swift +++ b/Sources/TOMLDecoder/DateTime.swift @@ -19,7 +19,7 @@ /// > This means for ancient dates, /// > ``OffsetDateTime`` may disagree with `Foundation.Date` on how much time has passed since a reference date. /// > For modern dates, there's no difference between the two. -public struct OffsetDateTime: Equatable, Hashable, Sendable, Codable, CustomStringConvertible { +public struct OffsetDateTime: Equatable, Hashable, Sendable, CustomStringConvertible { /// The date component of the offset date-time. public var date: LocalDate /// The time component of the offset date-time. @@ -48,13 +48,20 @@ public struct OffsetDateTime: Equatable, Hashable, Sendable, Codable, CustomStri self.features = features } + #if CodableSupport public init(from decoder: any Decoder) throws { if let decoder = decoder as? _TOMLDecoder { self = try decoder.decode(OffsetDateTime.self) - } else { - try self.init(from: decoder) + return } + + let container = try decoder.container(keyedBy: CodingKeys.self) + date = try container.decode(LocalDate.self, forKey: .date) + time = try container.decode(LocalTime.self, forKey: .time) + offset = try container.decode(Int16.self, forKey: .offset) + features = try container.decode(Features.self, forKey: .features) } + #endif /// Create a new offset date-time from it's members. /// @@ -183,7 +190,7 @@ public struct OffsetDateTime: Equatable, Hashable, Sendable, Codable, CustomStri /// /// A parser can preserve features of a offset date-time string with this type. /// If neither lowercase nor uppercase 'T' is present, the date-time seprator is a space, which is allowed by TOML. - public struct Features: OptionSet, Hashable, Sendable, Codable { + public struct Features: OptionSet, Hashable, Sendable { /// The raw value of the features. public let rawValue: UInt8 @@ -213,7 +220,7 @@ public struct OffsetDateTime: Equatable, Hashable, Sendable, Codable, CustomStri /// A local date-time as defined by TOML. /// /// ``LocalDateTime`` stores fractional seconds to the nanosecond precision. -public struct LocalDateTime: Equatable, Hashable, Sendable, Codable, CustomStringConvertible { +public struct LocalDateTime: Equatable, Hashable, Sendable, CustomStringConvertible { /// The date component of the local date-time. public var date: LocalDate /// The time component of the local date-time. @@ -231,13 +238,18 @@ public struct LocalDateTime: Equatable, Hashable, Sendable, Codable, CustomStrin self.time = time } + #if CodableSupport public init(from decoder: any Decoder) throws { if let decoder = decoder as? _TOMLDecoder { self = try decoder.decode(LocalDateTime.self) - } else { - try self.init(from: decoder) + return } + + let container = try decoder.container(keyedBy: CodingKeys.self) + date = try container.decode(LocalDate.self, forKey: .date) + time = try container.decode(LocalTime.self, forKey: .time) } + #endif /// Create a new local date-time from it's members. /// @@ -271,7 +283,7 @@ public struct LocalDateTime: Equatable, Hashable, Sendable, Codable, CustomStrin /// A local time as defined by TOML. /// /// ``LocalTime`` stores fractional seconds to the nanosecond precision. -public struct LocalTime: Equatable, Hashable, Sendable, Codable, CustomStringConvertible { +public struct LocalTime: Equatable, Hashable, Sendable, CustomStringConvertible { /// The hour component of the local time. public var hour: UInt8 /// The minute component of the local time. @@ -340,7 +352,7 @@ public struct LocalTime: Equatable, Hashable, Sendable, Codable, CustomStringCon /// A local date as defined by TOML. /// /// ``LocalDate`` stores the year, month, and day components of a date. -public struct LocalDate: Equatable, Hashable, Sendable, Codable, CustomStringConvertible { +public struct LocalDate: Equatable, Hashable, Sendable, CustomStringConvertible { /// The year component of the local date. /// Valid range is [1, 9999]. public var year: UInt16 @@ -431,7 +443,15 @@ extension String { } } -#if canImport(Foundation) +#if CodableSupport +extension OffsetDateTime: Codable {} +extension OffsetDateTime.Features: Codable {} +extension LocalDateTime: Codable {} +extension LocalTime: Codable {} +extension LocalDate: Codable {} +#endif + +#if CodableSupport public import Foundation extension DateComponents { diff --git a/Sources/TOMLDecoder/Parsing/Parser.swift b/Sources/TOMLDecoder/Parsing/Parser.swift index dab1648d..41f7bb04 100644 --- a/Sources/TOMLDecoder/Parsing/Parser.swift +++ b/Sources/TOMLDecoder/Parsing/Parser.swift @@ -1,3 +1,11 @@ +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(WASILibc) +import WASILibc +#endif + struct Parser: ~Copyable { var token = Token.empty var cursor = 0 @@ -1158,6 +1166,32 @@ extension Token { } func unpackFloat(bytes: UnsafeBufferPointer, context: TOMLKey) throws(TOMLError) -> Double { + @inline(__always) + func parseNormalizedFloat(_ codeUnits: inout [UTF8.CodeUnit]) -> Double? { + guard !codeUnits.isEmpty else { + return nil + } + codeUnits.append(0) + + return codeUnits.withUnsafeMutableBufferPointer { buffer -> Double? in + guard let base = buffer.baseAddress else { + return nil + } + return base.withMemoryRebound(to: CChar.self, capacity: buffer.count) { cString in + var end: UnsafeMutablePointer? + let value = strtod(cString, &end) + guard let end else { + return nil + } + let expectedEnd = cString.advanced(by: buffer.count - 1) + guard end == expectedEnd else { + return nil + } + return value + } + } + } + var resultCodeUnits: [UTF8.CodeUnit] = [] var index = text.lowerBound if bytes[index] == CodeUnits.plus || bytes[index] == CodeUnits.minus { @@ -1234,7 +1268,7 @@ extension Token { } } - guard let double = Double(String(decoding: resultCodeUnits, as: UTF8.self)) else { + guard let double = parseNormalizedFloat(&resultCodeUnits) else { throw TOMLError(.invalidFloat(context: context, lineNumber: lineNumber, reason: "not a float")) } @@ -1494,32 +1528,19 @@ extension Token { } } - do { - let (offsetHour, offsetMinute, consumedLength) = try parseTimezoneOffset(bytes: bytes, range: index ..< endIndex, lineNumber: lineNumber) - - // Validate timezone offset ranges - if offsetHour > 24 { - throw TOMLError(.invalidDateTime3(context: context, lineNumber: lineNumber, reason: "timezone offset hour must be between 00 and 24")) - } - if offsetMinute > 59 { - throw TOMLError(.invalidDateTime3(context: context, lineNumber: lineNumber, reason: "timezone offset minute must be between 00 and 59")) - } + let (offsetHour, offsetMinute, consumedLength) = try parseTimezoneOffset(bytes: bytes, range: index ..< endIndex, lineNumber: lineNumber) - let offsetInMinutes = offsetHour * 60 + offsetMinute - timeOffset = Int16(offsetIsNegative ? -offsetInMinutes : offsetInMinutes) - index += consumedLength - } catch let parseError { - if let tomlError = parseError as? TOMLError { - switch tomlError.reason { - case let .invalidDateTime(_, reason): - throw TOMLError(.invalidDateTime3(context: context, lineNumber: lineNumber, reason: reason)) - default: - throw tomlError - } - } else { - throw TOMLError(.invalidDateTime3(context: context, lineNumber: lineNumber, reason: "timezone parsing error")) - } + // Validate timezone offset ranges + if offsetHour > 24 { + throw TOMLError(.invalidDateTime3(context: context, lineNumber: lineNumber, reason: "timezone offset hour must be between 00 and 24")) } + if offsetMinute > 59 { + throw TOMLError(.invalidDateTime3(context: context, lineNumber: lineNumber, reason: "timezone offset minute must be between 00 and 59")) + } + + let offsetInMinutes = offsetHour * 60 + offsetMinute + timeOffset = Int16(offsetIsNegative ? -offsetInMinutes : offsetInMinutes) + index += consumedLength } } @@ -1535,6 +1556,7 @@ extension Token { ) } + #if CodableSupport func unpackAnyValue(bytes: UnsafeBufferPointer, context: TOMLKey) throws(TOMLError) -> Any { let firstChar = text.count > 0 ? bytes[text.lowerBound] : nil if firstChar == CodeUnits.singleQuote || firstChar == CodeUnits.doubleQuote { @@ -1571,6 +1593,7 @@ extension Token { throw TOMLError(.invalidValueInTable(context: context, lineNumber: lineNumber)) } } + #endif } func parseTimezoneOffset(bytes: UnsafeBufferPointer, range: Range, lineNumber: Int) throws(TOMLError) -> (hour: Int, minute: Int, consumedLength: Int) { diff --git a/Sources/TOMLDecoder/Parsing/TOMLDocument.swift b/Sources/TOMLDecoder/Parsing/TOMLDocument.swift index 189b94c4..f7db1e85 100644 --- a/Sources/TOMLDecoder/Parsing/TOMLDocument.swift +++ b/Sources/TOMLDecoder/Parsing/TOMLDocument.swift @@ -1,5 +1,3 @@ -import Foundation - struct TOMLDocument: Equatable, @unchecked Sendable { let tables: [InternalTOMLTable] let arrays: [InternalTOMLArray] @@ -26,27 +24,15 @@ struct TOMLDocument: Equatable, @unchecked Sendable { } init(source: String, keyTransform: (@Sendable (String) -> String)?) throws(TOMLError) { - var hasContinousStorage = false var parser = Parser(keyTransform: keyTransform) - - do { - try source.utf8.withContiguousStorageIfAvailable { - hasContinousStorage = true - try parser.parse(bytes: $0) - } - } catch { - throw error as! TOMLError + let bytes = Array(source.utf8) + let storage = UnsafeMutableBufferPointer.allocate(capacity: bytes.count) + _ = storage.initialize(from: bytes) + defer { + storage.deinitialize() + storage.deallocate() } - - if !hasContinousStorage { - var source = source - do { - try source.withUTF8 { try parser.parse(bytes: $0) } - } catch { - throw error as! TOMLError - } - } - + try parser.parse(bytes: UnsafeBufferPointer(storage)) self = parser.finish(source: source) } } @@ -231,6 +217,7 @@ struct DateTimeComponents: Equatable { } } +#if CodableSupport extension InternalTOMLTable { func dictionary(source: TOMLDocument) throws(TOMLError) -> [String: Any] { var result = [String: Any]() @@ -271,6 +258,7 @@ extension InternalTOMLArray { return result } } +#endif extension TOMLDocument { @inline(__always) diff --git a/Sources/TOMLDecoder/Parsing/Token.swift b/Sources/TOMLDecoder/Parsing/Token.swift index c2857648..e97af190 100644 --- a/Sources/TOMLDecoder/Parsing/Token.swift +++ b/Sources/TOMLDecoder/Parsing/Token.swift @@ -22,52 +22,118 @@ struct Token: Equatable { extension Token { func unpackBool(source: String, context: TOMLKey) throws(TOMLError) -> Bool { + #if !CodableSupport + let bytes = Array(source.utf8) + let storage = UnsafeMutableBufferPointer.allocate(capacity: bytes.count) + _ = storage.initialize(from: bytes) + defer { + storage.deinitialize() + storage.deallocate() + } + return try unpackBool(bytes: UnsafeBufferPointer(storage), context: context) + #else do { return try (source.utf8.withContiguousStorageIfAvailable { try unpackBool(bytes: $0, context: context) })! } catch { throw error as! TOMLError } + #endif } func unpackFloat(source: String, context: TOMLKey) throws(TOMLError) -> Double { + #if !CodableSupport + let bytes = Array(source.utf8) + let storage = UnsafeMutableBufferPointer.allocate(capacity: bytes.count) + _ = storage.initialize(from: bytes) + defer { + storage.deinitialize() + storage.deallocate() + } + return try unpackFloat(bytes: UnsafeBufferPointer(storage), context: context) + #else do { return try (source.utf8.withContiguousStorageIfAvailable { try unpackFloat(bytes: $0, context: context) })! } catch { throw error as! TOMLError } + #endif } func unpackString(source: String, context: TOMLKey) throws(TOMLError) -> String { + #if !CodableSupport + let bytes = Array(source.utf8) + let storage = UnsafeMutableBufferPointer.allocate(capacity: bytes.count) + _ = storage.initialize(from: bytes) + defer { + storage.deinitialize() + storage.deallocate() + } + return try unpackString(bytes: UnsafeBufferPointer(storage), context: context) + #else do { return try (source.utf8.withContiguousStorageIfAvailable { try unpackString(bytes: $0, context: context) })! } catch { throw error as! TOMLError } + #endif } func unpackInteger(source: String, context: TOMLKey) throws(TOMLError) -> Int64 { + #if !CodableSupport + let bytes = Array(source.utf8) + let storage = UnsafeMutableBufferPointer.allocate(capacity: bytes.count) + _ = storage.initialize(from: bytes) + defer { + storage.deinitialize() + storage.deallocate() + } + return try unpackInteger(bytes: UnsafeBufferPointer(storage), context: context) + #else do { return try (source.utf8.withContiguousStorageIfAvailable { try unpackInteger(bytes: $0, context: context) })! } catch { throw error as! TOMLError } + #endif } func unpackDateTime(source: String, context: TOMLKey) throws(TOMLError) -> DateTimeComponents { + #if !CodableSupport + let bytes = Array(source.utf8) + let storage = UnsafeMutableBufferPointer.allocate(capacity: bytes.count) + _ = storage.initialize(from: bytes) + defer { + storage.deinitialize() + storage.deallocate() + } + return try unpackDateTime(bytes: UnsafeBufferPointer(storage), context: context) + #else do { return try (source.utf8.withContiguousStorageIfAvailable { try unpackDateTime(bytes: $0, context: context) })! } catch { throw error as! TOMLError } + #endif } func unpackOffsetDateTime(source: String, context: TOMLKey) throws(TOMLError) -> OffsetDateTime { let datetime: DateTimeComponents + #if !CodableSupport + let bytes = Array(source.utf8) + let storage = UnsafeMutableBufferPointer.allocate(capacity: bytes.count) + _ = storage.initialize(from: bytes) + defer { + storage.deinitialize() + storage.deallocate() + } + datetime = try unpackDateTime(bytes: UnsafeBufferPointer(storage), context: context) + #else do { datetime = try (source.utf8.withContiguousStorageIfAvailable { try unpackDateTime(bytes: $0, context: context) })! } catch { throw error as! TOMLError } + #endif switch (datetime.date, datetime.time, datetime.offset) { case let (.some(date), .some(time), .some(offset)): return OffsetDateTime(date: date, time: time, offset: offset, features: datetime.features) @@ -100,6 +166,7 @@ extension Token { return localTime } + #if CodableSupport func unpackAnyValue(source: String, context: TOMLKey) throws(TOMLError) -> Any { do { return try (source.utf8.withContiguousStorageIfAvailable { try unpackAnyValue(bytes: $0, context: context) })! @@ -107,4 +174,5 @@ extension Token { throw error as! TOMLError } } + #endif } diff --git a/Sources/TOMLDecoder/TOMLArray.swift b/Sources/TOMLDecoder/TOMLArray.swift index 7e0bc0cc..b1453ca9 100644 --- a/Sources/TOMLDecoder/TOMLArray.swift +++ b/Sources/TOMLDecoder/TOMLArray.swift @@ -286,6 +286,7 @@ public struct TOMLArray: Equatable, Sendable { return localTime } + #if CodableSupport func array() throws(TOMLError) -> [Any] { if isKeyed { let count = source.keyArrays.count @@ -301,6 +302,7 @@ public struct TOMLArray: Equatable, Sendable { return try source.arrays[index].array(source: source) } } + #endif @inline(__always) func resolvedArray() -> InternalTOMLArray { @@ -308,6 +310,7 @@ public struct TOMLArray: Equatable, Sendable { } } +#if CodableSupport extension TOMLArray: Codable { /// Makes ``TOMLArray`` eligible for `Codable`. /// @@ -339,7 +342,9 @@ extension TOMLArray: Codable { throw TOMLError(.notReallyCodable) } } +#endif +#if CodableSupport extension [Any] { /// Create a `[Any]` from a `TOMLArray`. /// Validating all fields recursively. @@ -356,3 +361,4 @@ extension [Any] { self = try tomlArray.array() } } +#endif diff --git a/Sources/TOMLDecoder/TOMLDecoder.swift b/Sources/TOMLDecoder/TOMLDecoder.swift index 68d570c9..eb3d1f83 100644 --- a/Sources/TOMLDecoder/TOMLDecoder.swift +++ b/Sources/TOMLDecoder/TOMLDecoder.swift @@ -1,3 +1,4 @@ +#if CodableSupport public import Foundation /// Convert data for a TOML document into `Codable` types. @@ -310,3 +311,4 @@ func snakeCasify(_ stringKey: String) -> String { joinedString + String(stringKey[trailingUnderscoreRange]) } } +#endif diff --git a/Sources/TOMLDecoder/TOMLKey.swift b/Sources/TOMLDecoder/TOMLKey.swift index 1c25d8fc..9186bc07 100644 --- a/Sources/TOMLDecoder/TOMLKey.swift +++ b/Sources/TOMLDecoder/TOMLKey.swift @@ -1,4 +1,4 @@ -enum TOMLKey: CodingKey { +enum TOMLKey { case string(String) case int(Int) case `super` @@ -31,3 +31,7 @@ enum TOMLKey: CodingKey { } } } + +#if CodableSupport +extension TOMLKey: CodingKey {} +#endif diff --git a/Sources/TOMLDecoder/TOMLKeyedDecodingContainer.swift b/Sources/TOMLDecoder/TOMLKeyedDecodingContainer.swift index d6d5e54b..0d08e433 100644 --- a/Sources/TOMLDecoder/TOMLKeyedDecodingContainer.swift +++ b/Sources/TOMLDecoder/TOMLKeyedDecodingContainer.swift @@ -1,6 +1,6 @@ -#if canImport(Foundation) +#if CodableSupport import Foundation -#endif + struct TOMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { private let decoder: _TOMLDecoder private let table: TOMLTable @@ -74,14 +74,12 @@ struct TOMLKeyedDecodingContainer: KeyedDecodingContainerProtoco decoder.codingPath.removeLast() } decoder.token = token - #if canImport(Foundation) // have to intercept these here otherwise Foundation will try to decode it as a float if type == Date.self { return try decoder.decode(Date.self) as! T } else if type == DateComponents.self { return try decoder.decode(DateComponents.self) as! T } - #endif if type == LocalDate.self { return try decoder.decode(LocalDate.self) as! T } else if type == LocalTime.self { @@ -134,3 +132,4 @@ struct TOMLKeyedDecodingContainer: KeyedDecodingContainerProtoco _superDecoder(forKey: key) } } +#endif diff --git a/Sources/TOMLDecoder/TOMLSingleValueDecodingContainer.Generated.swift b/Sources/TOMLDecoder/TOMLSingleValueDecodingContainer.Generated.swift index c4edc9da..f1562e3a 100644 --- a/Sources/TOMLDecoder/TOMLSingleValueDecodingContainer.Generated.swift +++ b/Sources/TOMLDecoder/TOMLSingleValueDecodingContainer.Generated.swift @@ -1,3 +1,4 @@ +#if CodableSupport // WARNING: This file is generated from TOMLSingleValueDecodingContainer.swift.gyb // Do not edit TOMLSingleValueDecodingContainer.swift directly. @@ -374,3 +375,4 @@ extension _TOMLDecoder { return components } } +#endif diff --git a/Sources/TOMLDecoder/TOMLTable.swift b/Sources/TOMLDecoder/TOMLTable.swift index 1f62f892..8941310a 100644 --- a/Sources/TOMLDecoder/TOMLTable.swift +++ b/Sources/TOMLDecoder/TOMLTable.swift @@ -29,26 +29,25 @@ extension TOMLTable { // String.init(validating:as:) does not exist in our supported OSes. extension String { fileprivate init(validatingUTF8 source: some Collection) throws(TOMLError) { + #if CodableSupport do { - if let result = try source.withContiguousStorageIfAvailable( - { buffer -> String in - try validateAndCreateString(from: buffer) - } - ) { + if let result = try source.withContiguousStorageIfAvailable({ try validateAndCreateString(from: $0) }) { self = result return } - - // Slow path: copy to contiguous buffer first - let array = Array(source) - self = try array.withUnsafeBufferPointer { buffer -> String in - try validateAndCreateString(from: buffer) - } - } catch let error as TOMLError { - throw error } catch { - fatalError("Unexpected error type") + throw error as! TOMLError + } + #endif + + let array = Array(source) + let storage = UnsafeMutableBufferPointer.allocate(capacity: array.count) + _ = storage.initialize(from: array) + defer { + storage.deinitialize() + storage.deallocate() } + self = try validateAndCreateString(from: UnsafeBufferPointer(storage)) } } @@ -382,11 +381,14 @@ public struct TOMLTable: Sendable, Equatable { return localTime } + #if CodableSupport func dictionary() throws(TOMLError) -> [String: Any] { try source.table(at: index, keyed: isKeyed).dictionary(source: source) } + #endif } +#if CodableSupport extension TOMLTable: Codable { /// Makes ``TOMLTable`` eligible for `Codable`. /// @@ -424,7 +426,9 @@ extension TOMLTable: Codable { throw TOMLError(.notReallyCodable) } } +#endif +#if CodableSupport extension [String: Any] { /// Create a `[String: Any]` from a `TOMLTable`. /// Validating all fields along the way. @@ -443,3 +447,4 @@ extension [String: Any] { self = try tomlTable.dictionary() } } +#endif diff --git a/Sources/TOMLDecoder/TOMLUnkeyedDecodingContainer.swift b/Sources/TOMLDecoder/TOMLUnkeyedDecodingContainer.swift index f71c9ac3..f1a90423 100644 --- a/Sources/TOMLDecoder/TOMLUnkeyedDecodingContainer.swift +++ b/Sources/TOMLDecoder/TOMLUnkeyedDecodingContainer.swift @@ -1,6 +1,5 @@ -#if canImport(Foundation) +#if CodableSupport import Foundation -#endif struct TOMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { private let decoder: _TOMLDecoder @@ -89,13 +88,11 @@ struct TOMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { decoder.token = token // have to intercept these here otherwise Foundation will try to decode it as a float - #if canImport(Foundation) if type == Date.self { return try decoder.decode(Date.self) as! T } else if type == DateComponents.self { return try decoder.decode(DateComponents.self) as! T } - #endif if type == LocalDate.self { return try decoder.decode(LocalDate.self) as! T } else if type == LocalTime.self { @@ -172,3 +169,4 @@ struct TOMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { _TOMLDecoder(referencing: .unkeyed(array), at: codingPath + [TOMLKey.super], strategy: decoder.strategy, isLenient: decoder.isLenient) } } +#endif diff --git a/Sources/TOMLDecoder/_TOMLDecoder.swift b/Sources/TOMLDecoder/_TOMLDecoder.swift index d46600d8..22b4a464 100644 --- a/Sources/TOMLDecoder/_TOMLDecoder.swift +++ b/Sources/TOMLDecoder/_TOMLDecoder.swift @@ -1,3 +1,4 @@ +#if CodableSupport final class _TOMLDecoder: Decoder { enum Container { case keyed(TOMLTable) @@ -72,3 +73,4 @@ extension Double { } } } +#endif diff --git a/Sources/TOMLDecoder/gyb/TOMLSingleValueDecodingContainer.swift.gyb b/Sources/TOMLDecoder/gyb/TOMLSingleValueDecodingContainer.swift.gyb index 605a38c7..48b70cf1 100644 --- a/Sources/TOMLDecoder/gyb/TOMLSingleValueDecodingContainer.swift.gyb +++ b/Sources/TOMLDecoder/gyb/TOMLSingleValueDecodingContainer.swift.gyb @@ -1,3 +1,4 @@ +#if CodableSupport %{ # gyb variables available: `__file__` gives the template’s path. from pathlib import Path @@ -196,3 +197,4 @@ extension _TOMLDecoder { return components } } +#endif diff --git a/Sources/compliance/main.swift b/Sources/compliance/main.swift index 6ed62119..80269f6c 100644 --- a/Sources/compliance/main.swift +++ b/Sources/compliance/main.swift @@ -3,6 +3,7 @@ import Foundation import TOMLDecoder +#if CodableSupport /// iOS 13+ compatible date formatter functions private func createISO8601FullFormatter() -> ISO8601DateFormatter { let formatter = ISO8601DateFormatter() @@ -88,3 +89,7 @@ func translate(value: Any) -> Any { let json = try JSONSerialization.data(withJSONObject: translate(value: table)) print(String(data: json, encoding: .utf8)!) +#else +fputs("compliance executable requires CodableSupport trait.\n", stderr) +exit(1) +#endif diff --git a/Tests/TOMLDecoderTests/DateStrategyTests.swift b/Tests/TOMLDecoderTests/DateStrategyTests.swift index 4eb475ce..ee805896 100644 --- a/Tests/TOMLDecoderTests/DateStrategyTests.swift +++ b/Tests/TOMLDecoderTests/DateStrategyTests.swift @@ -1,3 +1,4 @@ +#if CodableSupport import Foundation import Testing @testable import TOMLDecoder @@ -242,3 +243,4 @@ struct DateStrategyTests { } } } +#endif diff --git a/Tests/TOMLDecoderTests/InvalidationTests.Generated.swift b/Tests/TOMLDecoderTests/InvalidationTests.Generated.swift index 2705b370..4a693904 100644 --- a/Tests/TOMLDecoderTests/InvalidationTests.Generated.swift +++ b/Tests/TOMLDecoderTests/InvalidationTests.Generated.swift @@ -1,6 +1,7 @@ // Generated by Scripts/generate-tests.py // Source: toml-test commit 0ee318ae97ae5dec5f74aeccafbdc75f435580e2 (spec 1.1.0) +#if CodableSupport import Foundation import Testing import TOMLDecoder @@ -2349,3 +2350,4 @@ struct TOMLInvalidationTests { try invalidate(pathComponents: ["table", "with-pound"]) } } +#endif diff --git a/Tests/TOMLDecoderTests/LeafValueDecodingTests.Generated.swift b/Tests/TOMLDecoderTests/LeafValueDecodingTests.Generated.swift index f8a96e45..e23ef2e8 100644 --- a/Tests/TOMLDecoderTests/LeafValueDecodingTests.Generated.swift +++ b/Tests/TOMLDecoderTests/LeafValueDecodingTests.Generated.swift @@ -1,3 +1,4 @@ +#if CodableSupport // WARNING: This file is generated from LeafValueDecodingTests.swift.gyb // Do not edit LeafValueDecodingTests.swift directly. @@ -811,3 +812,4 @@ private enum AString: String, Decodable, Equatable { case foo case bar } +#endif diff --git a/Tests/TOMLDecoderTests/Support/TOMLComplianceSupport.swift b/Tests/TOMLDecoderTests/Support/TOMLComplianceSupport.swift index 8a93c6de..f859aa8e 100644 --- a/Tests/TOMLDecoderTests/Support/TOMLComplianceSupport.swift +++ b/Tests/TOMLDecoderTests/Support/TOMLComplianceSupport.swift @@ -1,3 +1,4 @@ +#if CodableSupport import Foundation import ProlepticGregorianTestHelpers import Testing @@ -388,3 +389,4 @@ enum TOMLComplianceSupport { return description } } +#endif diff --git a/Tests/TOMLDecoderTests/TOMLDecoderTests.swift b/Tests/TOMLDecoderTests/TOMLDecoderTests.swift index 55663ce4..90b07c02 100644 --- a/Tests/TOMLDecoderTests/TOMLDecoderTests.swift +++ b/Tests/TOMLDecoderTests/TOMLDecoderTests.swift @@ -1,3 +1,4 @@ +#if CodableSupport import Foundation import Resources import Testing @@ -499,3 +500,4 @@ struct TOMLDecoderTests { _ = try TOMLDecoder().decode(CanadaFeatureCollection.self, from: Resources.canadaTOMLString) } } +#endif diff --git a/Tests/TOMLDecoderTests/ValidationTests.Generated.swift b/Tests/TOMLDecoderTests/ValidationTests.Generated.swift index 6f516b08..5fc516b6 100644 --- a/Tests/TOMLDecoderTests/ValidationTests.Generated.swift +++ b/Tests/TOMLDecoderTests/ValidationTests.Generated.swift @@ -1,6 +1,7 @@ // Generated by Scripts/generate-tests.py // Source: toml-test commit 0ee318ae97ae5dec5f74aeccafbdc75f435580e2 (spec 1.1.0) +#if CodableSupport import Foundation import Testing import TOMLDecoder @@ -1090,3 +1091,4 @@ struct TOMLValidationTests { try verifyByFixture(pathComponents: ["table", "without-super-with-values"]) } } +#endif diff --git a/Tests/TOMLDecoderTests/gyb/LeafValueDecodingTests.swift.gyb b/Tests/TOMLDecoderTests/gyb/LeafValueDecodingTests.swift.gyb index d7473395..dc6d32b7 100644 --- a/Tests/TOMLDecoderTests/gyb/LeafValueDecodingTests.swift.gyb +++ b/Tests/TOMLDecoderTests/gyb/LeafValueDecodingTests.swift.gyb @@ -1,3 +1,4 @@ +#if CodableSupport %{ # gyb variables available: `__file__` gives the template’s path. from pathlib import Path @@ -478,3 +479,4 @@ private enum AString: String, Decodable, Equatable { case foo case bar } +#endif From c0be8d97c96ccd76dda0bf8c2adf880988cc708a Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 00:47:30 -0800 Subject: [PATCH 02/13] Add missing code in .gyb --- Scripts/generate-tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Scripts/generate-tests.py b/Scripts/generate-tests.py index 6f526917..c7a933ee 100755 --- a/Scripts/generate-tests.py +++ b/Scripts/generate-tests.py @@ -212,6 +212,7 @@ def _generate_valid_test_file(fixtures: Iterable[str], commit: str, spec_version """// Generated by Scripts/generate-tests.py // Source: toml-test commit __COMMIT__ (spec __SPEC_VERSION__) +#if CodableSupport import Foundation import Testing import TOMLDecoder @@ -233,6 +234,7 @@ def _generate_valid_test_file(fixtures: Iterable[str], commit: str, spec_version __TESTS__ } +#endif """ ) @@ -271,6 +273,7 @@ def _generate_invalid_test_file(fixtures: Iterable[str], commit: str, spec_versi """// Generated by Scripts/generate-tests.py // Source: toml-test commit __COMMIT__ (spec __SPEC_VERSION__) +#if CodableSupport import Foundation import Testing import TOMLDecoder @@ -291,6 +294,7 @@ def _generate_invalid_test_file(fixtures: Iterable[str], commit: str, spec_versi __TESTS__ } +#endif """ ) From a4bcc8168fa767e9fe3a6f053c8bb9d648e679c1 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 00:57:15 -0800 Subject: [PATCH 03/13] Add Swift 6.0 Package manifest --- Package@swift-6.0.swift | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Package@swift-6.0.swift diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift new file mode 100644 index 00000000..dbc6e06b --- /dev/null +++ b/Package@swift-6.0.swift @@ -0,0 +1,52 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +let codableSupportEnabled: [SwiftSetting] = [.define("CodableSupport")] + +let package = Package( + name: "TOMLDecoder", + platforms: [.iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macOS(.v10_15), .visionOS(.v1)], + products: [ + .executable(name: "compliance", targets: ["compliance"]), + .library(name: "TOMLDecoder", targets: ["TOMLDecoder"]), + ], + targets: [ + .executableTarget( + name: "compliance", + dependencies: ["TOMLDecoder"] + ), + .target( + name: "TOMLDecoder", + exclude: ["gyb"], + swiftSettings: codableSupportEnabled + [ + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("MemberImportVisibility"), + ] + ), + .target( + name: "Resources", + exclude: ["fixtures"] + ), + .target( + name: "ProlepticGregorianTestHelpers", + publicHeadersPath: "include" + ), + .testTarget( + name: "TOMLDecoderTests", + dependencies: [ + "ProlepticGregorianTestHelpers", + "Resources", + "TOMLDecoder", + ], + exclude: [ + "gyb", + "invalid_fixtures", + "valid_fixtures", + ], + swiftSettings: codableSupportEnabled + ), + ], + cxxLanguageStandard: .cxx20 +) From ffe849e0436857028e57b50b782fd206a44f0374 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 01:02:08 -0800 Subject: [PATCH 04/13] Fix compliance stderr safety --- Sources/compliance/main.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/compliance/main.swift b/Sources/compliance/main.swift index 80269f6c..78c0b8e3 100644 --- a/Sources/compliance/main.swift +++ b/Sources/compliance/main.swift @@ -1,6 +1,8 @@ // This is CLI app that provides decoder interface for the test suite at // https://github.com/BurntSushi/toml-test +#if CodableSupport import Foundation +#endif import TOMLDecoder #if CodableSupport @@ -90,6 +92,5 @@ func translate(value: Any) -> Any { let json = try JSONSerialization.data(withJSONObject: translate(value: table)) print(String(data: json, encoding: .utf8)!) #else -fputs("compliance executable requires CodableSupport trait.\n", stderr) -exit(1) +fatalError("compliance executable requires CodableSupport trait.") #endif From 01ec7e8fb8bfdb3dfb6d82e25f08241c72dd3399 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 01:06:54 -0800 Subject: [PATCH 05/13] Fix compliance stderr concurrency --- .github/workflows/swift.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 7dc65f15..5187cb85 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -33,6 +33,7 @@ jobs: - name: Run tests run: swift test - name: Run tests without Codable + if: matrix.swift_version != '6.0' run: swift test --disable-default-traits swift-test-macos: @@ -53,6 +54,7 @@ jobs: - name: Run tests run: swift test - name: Run tests without Codable + if: matrix.swift_version != '6.0' run: swift test --disable-default-traits swift-test-simulator: From 35c6868fc871f475d12ac5d4d2b77d23d50cb841 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 01:14:56 -0800 Subject: [PATCH 06/13] Fix strtod availability --- Sources/TOMLDecoder/Parsing/Parser.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/TOMLDecoder/Parsing/Parser.swift b/Sources/TOMLDecoder/Parsing/Parser.swift index 41f7bb04..80f9769f 100644 --- a/Sources/TOMLDecoder/Parsing/Parser.swift +++ b/Sources/TOMLDecoder/Parsing/Parser.swift @@ -2,6 +2,8 @@ import Darwin #elseif canImport(Glibc) import Glibc +#elseif canImport(MSVCRT) +import MSVCRT #elseif canImport(WASILibc) import WASILibc #endif @@ -1166,6 +1168,7 @@ extension Token { } func unpackFloat(bytes: UnsafeBufferPointer, context: TOMLKey) throws(TOMLError) -> Double { + #if !CodableSupport @inline(__always) func parseNormalizedFloat(_ codeUnits: inout [UTF8.CodeUnit]) -> Double? { guard !codeUnits.isEmpty else { @@ -1191,6 +1194,7 @@ extension Token { } } } + #endif var resultCodeUnits: [UTF8.CodeUnit] = [] var index = text.lowerBound @@ -1268,9 +1272,15 @@ extension Token { } } + #if CodableSupport + guard let double = Double(String(decoding: resultCodeUnits, as: UTF8.self)) else { + throw TOMLError(.invalidFloat(context: context, lineNumber: lineNumber, reason: "not a float")) + } + #else guard let double = parseNormalizedFloat(&resultCodeUnits) else { throw TOMLError(.invalidFloat(context: context, lineNumber: lineNumber, reason: "not a float")) } + #endif return double } From d701ebe9f5fa218ff05d2f17bf9a5f2676e29f96 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 11:09:27 -0800 Subject: [PATCH 07/13] Update Parser imports for strtod --- Sources/TOMLDecoder/Parsing/Parser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/TOMLDecoder/Parsing/Parser.swift b/Sources/TOMLDecoder/Parsing/Parser.swift index 80f9769f..a69493d5 100644 --- a/Sources/TOMLDecoder/Parsing/Parser.swift +++ b/Sources/TOMLDecoder/Parsing/Parser.swift @@ -2,8 +2,8 @@ import Darwin #elseif canImport(Glibc) import Glibc -#elseif canImport(MSVCRT) -import MSVCRT +#elseif canImport(ucrt) +import ucrt #elseif canImport(WASILibc) import WASILibc #endif From c4032851e64bdda7145511d6ce539f3ed79a838e Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 11:27:37 -0800 Subject: [PATCH 08/13] update CI for embedded --- .github/workflows/build-embeded.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-embeded.yml b/.github/workflows/build-embeded.yml index 9bb27f75..634de90d 100644 --- a/.github/workflows/build-embeded.yml +++ b/.github/workflows/build-embeded.yml @@ -1,4 +1,4 @@ -name: Embedded Build +name: Embedded on: push: branches: @@ -10,13 +10,13 @@ on: - 'release/**' jobs: embedded-build: - name: Build embedded subset + name: Build runs-on: macos-26 steps: - name: Checkout source uses: actions/checkout@v4 - uses: SwiftyLab/setup-swift@latest with: - swift-version: "6.2.2" - - name: Build embedded subset + swift-version: "6.2.3" + - name: Embedded run: swift build -c release --target TOMLDecoder --disable-default-traits -Xswiftc -target -Xswiftc arm64-apple-macos14.0 -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -wmo From 8a37c4ce921f1fb6af5c4770ea953a0bea496c1b Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 11:36:44 -0800 Subject: [PATCH 09/13] try ubuntu for embedded --- .github/workflows/build-embeded.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-embeded.yml b/.github/workflows/build-embeded.yml index 634de90d..d361fa2c 100644 --- a/.github/workflows/build-embeded.yml +++ b/.github/workflows/build-embeded.yml @@ -11,7 +11,7 @@ on: jobs: embedded-build: name: Build - runs-on: macos-26 + runs-on: ubuntu-24.04 steps: - name: Checkout source uses: actions/checkout@v4 From 5b05eae499d530affb6f7d3eb950d7c783e9c408 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 11:38:44 -0800 Subject: [PATCH 10/13] triple too --- .github/workflows/build-embeded.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-embeded.yml b/.github/workflows/build-embeded.yml index d361fa2c..89f5de1d 100644 --- a/.github/workflows/build-embeded.yml +++ b/.github/workflows/build-embeded.yml @@ -19,4 +19,4 @@ jobs: with: swift-version: "6.2.3" - name: Embedded - run: swift build -c release --target TOMLDecoder --disable-default-traits -Xswiftc -target -Xswiftc arm64-apple-macos14.0 -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -wmo + run: swift build -c release --target TOMLDecoder --disable-default-traits -Xswiftc -target -Xswiftc x86_64-unknown-none-elf -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -wmo From 9cc5ec8589a56c784f8259a613ea7bc9ab8adafd Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 11:47:35 -0800 Subject: [PATCH 11/13] triple too --- .github/workflows/build-embeded.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-embeded.yml b/.github/workflows/build-embeded.yml index 89f5de1d..c8928aef 100644 --- a/.github/workflows/build-embeded.yml +++ b/.github/workflows/build-embeded.yml @@ -19,4 +19,4 @@ jobs: with: swift-version: "6.2.3" - name: Embedded - run: swift build -c release --target TOMLDecoder --disable-default-traits -Xswiftc -target -Xswiftc x86_64-unknown-none-elf -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -wmo + run: swift build -c release --target TOMLDecoder --disable-default-traits -Xswiftc -target -Xswiftc x86_64-unknown-linux-gnu -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -wmo From 12a84f637901ef48fc19179ba747bdc5a29415a2 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 11:56:12 -0800 Subject: [PATCH 12/13] try a thing --- Sources/TOMLDecoder/Parsing/Parser.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/TOMLDecoder/Parsing/Parser.swift b/Sources/TOMLDecoder/Parsing/Parser.swift index a69493d5..df508855 100644 --- a/Sources/TOMLDecoder/Parsing/Parser.swift +++ b/Sources/TOMLDecoder/Parsing/Parser.swift @@ -1,11 +1,9 @@ -#if canImport(Darwin) -import Darwin -#elseif canImport(Glibc) -import Glibc -#elseif canImport(ucrt) -import ucrt -#elseif canImport(WASILibc) -import WASILibc +#if !CodableSupport +@_silgen_name("strtod") +private func cStrtod( + _ nptr: UnsafePointer?, + _ endptr: UnsafeMutablePointer?>? +) -> Double #endif struct Parser: ~Copyable { @@ -1182,7 +1180,7 @@ extension Token { } return base.withMemoryRebound(to: CChar.self, capacity: buffer.count) { cString in var end: UnsafeMutablePointer? - let value = strtod(cString, &end) + let value = cStrtod(cString, &end) guard let end else { return nil } From e52442e6983f115f131541aafb1a3923101da4f8 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 7 Feb 2026 12:27:11 -0800 Subject: [PATCH 13/13] Add a bit of doc --- .../TOMLDecoder.docc/DevelopingTOMLDecoder.md | 19 +++++++++++++++++-- .../TOMLDecoder.docc/TOMLDecoder.md | 5 +++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/TOMLDecoder/TOMLDecoder.docc/DevelopingTOMLDecoder.md b/Sources/TOMLDecoder/TOMLDecoder.docc/DevelopingTOMLDecoder.md index e8473937..75780b28 100644 --- a/Sources/TOMLDecoder/TOMLDecoder.docc/DevelopingTOMLDecoder.md +++ b/Sources/TOMLDecoder/TOMLDecoder.docc/DevelopingTOMLDecoder.md @@ -37,9 +37,17 @@ Our unit tests include the [official suite](https://github.com/toml-lang/toml-te from the TOML GitHub organization, systematically translated into Swift tests. -Run tests with `swift test`, as well as `bazel test //...`. +For any changes to land, `swift test` must pass on ... -Tests must pass on macOS, Ubuntu, and Windows for any changes to land. +* latest Swift toolchain on macOS, Ubuntu, and Windows +* same as above but with `--disable-default-traits` +* simulators on supported Apple flatforms +* Oldest supported Swift toolchain one of the oldest supported Apple OS. + +... in addition: + +* `bazel test //...` must pass +* the library must _build_ for a embedded Swift ## Generating Code and Tests @@ -96,6 +104,13 @@ to compare performance between two commits. ## Architecture Overview TOMLDecoder is a parser with a `Swift.Decoder` implementation sitting on top. +The latter is gated in the `CodableSupport` SwiftPM trait, +which is enabled by default. +When the trait is excluded, +the library still functions without the `Codable` APIs. +This layer does not depend on Foundation, +and it works in WASM and embedded Swift environments. + TOML has a spec, and a large number of parser implementations in many languages. diff --git a/Sources/TOMLDecoder/TOMLDecoder.docc/TOMLDecoder.md b/Sources/TOMLDecoder/TOMLDecoder.docc/TOMLDecoder.md index e9568026..452a3f94 100644 --- a/Sources/TOMLDecoder/TOMLDecoder.docc/TOMLDecoder.md +++ b/Sources/TOMLDecoder/TOMLDecoder.docc/TOMLDecoder.md @@ -15,6 +15,7 @@ This library can do 2 things to TOML: * **Deserialize**: convert TOML strings or byte sequences into strongly-typed, structured data, and provide access to parts of it. + This part works in the embedded Swift environment. * **Decode**: further convert the structured data into your `Codable` types according to your preferences. @@ -37,6 +38,8 @@ and TOMLDecoder can attempt to create instances of your type from TOML data. You can configure the decoding strategies to customize the decoder's behaviors. +The `CodableSupport` trait is included by default, +it enables this functionality. - - ``TOMLDecoder`` @@ -52,6 +55,8 @@ Parse, un-marshal, deserialize. When TOMLDecoder does this to TOML strings or bytes, each data type as defined by the TOML specification is strictly mapped to a Swift type. +This part works with embedded Swift, +when `CodableSupport` is excluded. - - ``TOMLTable``