Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version:6.2
import PackageDescription

let package: Package = .init(
Expand Down Expand Up @@ -63,3 +63,20 @@ let package: Package = .init(
),
]
)
package.targets = package.targets.map {
switch $0.type {
case .plugin: return $0
case .binary: return $0
default: break
}
{
var settings: [SwiftSetting] = $0 ?? []

settings.append(.enableUpcomingFeature("ExistentialAny"))
settings.append(.treatWarning("ExistentialAny", as: .error))
settings.append(.treatWarning("MutableGlobalVariable", as: .error))

$0 = settings
} (&$0.swiftSettings)
return $0
}
Comment on lines +66 to +82

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code for adding Swift settings is overly complex and uses an obscure pattern with an immediately-invoked closure and an inout parameter. This makes the code hard to read and maintain. A more straightforward approach using a guard statement and direct property assignment would be much clearer.

package.targets = package.targets.map {
    guard $0.type != .plugin, $0.type != .binary else {
        return $0
    }

    $0.swiftSettings = ($0.swiftSettings ?? []) + [
        .enableUpcomingFeature("ExistentialAny"),
        .treatWarning("ExistentialAny", as: .error),
        .treatWarning("MutableGlobalVariable", as: .error),
    ]
    return $0
}

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A pure Swift JSON parsing and encoding library designed for high-performance, hi

## Requirements

The swift-json library requires Swift 5.9 or later.
The swift-json library requires Swift 6.2 or later.


| Platform | Status |
Expand Down
2 changes: 1 addition & 1 deletion Sources/JSONAST/JSON.Array.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extension JSON {
/// A JSON array, which can recursively contain instances of ``JSON``.
/// This type is a transparent wrapper around a native [`[JSON]`]()
/// This type is a transparent wrapper around a native `[JSON]`
/// array.
@frozen public struct Array {
public var elements: [JSON.Node]
Expand Down
32 changes: 15 additions & 17 deletions Sources/JSONAST/JSON.Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ extension JSON {
case string(Literal<String>)
/// A numerical value.
case number(Number)
/// A numerical value that is not a finite number.
case numberExtension_(NumberExtension_)

/// An array container.
case array(Array)
Expand Down Expand Up @@ -42,7 +40,7 @@ extension JSON.Node {
///
/// - Parameters:
/// - string: A string to escape.
/// - Returns: A string literal, which includes the [`""`]() delimiters.
/// - Returns: A string literal, which includes the `""` delimiters.
///
/// This function escapes the following characters: `"`, `\`, `\b`, `\t`, `\n`,
/// `\f`, and `\r`. It does not escape forward slashes (`/`).
Expand Down Expand Up @@ -89,7 +87,6 @@ extension JSON.Node: CustomStringConvertible {
case .bool(false): "false"
case .string(let self): .init(self)
case .number(let self): "\(self)"
case .numberExtension_(let self): "\(self)"
case .array(let self): "\(self)"
case .object(let self): "\(self)"
}
Expand Down Expand Up @@ -163,7 +160,7 @@ extension JSON.Node {
/// matches ``number(_:) [case]``, but it could not be represented exactly by `T`.
///
/// > Note:
/// This type conversion will fail if ``Number.places`` is non-zero, even if
/// This type conversion will fail if ``Number.Inline/places`` is non-zero, even if
/// the fractional part is zero. For example, you can convert `5` to an
/// integer, but not `5.0`. This matches the behavior of
/// ``ExpressibleByIntegerLiteral``.
Expand Down Expand Up @@ -192,7 +189,7 @@ extension JSON.Node {
/// matches ``number(_:) [case]``, but it could not be represented exactly by `T`.
///
/// > Note:
/// This type conversion will fail if ``Number.places`` is non-zero, even if
/// This type conversion will fail if ``Number.Inline/places`` is non-zero, even if
/// the fractional part is zero. For example, you can convert `5` to an
/// integer, but not `5.0`. This matches the behavior of
/// ``ExpressibleByIntegerLiteral``.
Expand Down Expand Up @@ -234,6 +231,16 @@ extension JSON.Node {
@inlinable public func `as`(_: Float.Type) -> Float? {
self.as(JSON.Number.self)?.as(Float.self)
}
/// Attempts to load an instance of ``Float16`` from this variant.
///
/// - Returns:
/// The closest value of ``Float16`` to the payload of this variant if it matches
/// ``number(_:) [case]``, `nil` otherwise.
@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
public func `as`(_: Float16.Type) -> Float16? {
self.as(JSON.Number.self)?.as(Float16.self)
}

/// Attempts to load an instance of ``Number`` from this variant.
///
/// - Returns:
Expand Down Expand Up @@ -299,21 +306,12 @@ extension JSON.Node {
/// source object. For more details about the payload, see the documentation
/// for ``object(_:)``.
///
/// To facilitate interoperability with decimal types, this method will also
/// return a pseudo-object containing the values of ``Number.units`` and
/// ``Number.places``, if this variant is a ``number(_:) [case]``. This function
/// creates the pseudo-object by calling ``Object.init(encoding:)``.
///
/// > Complexity:
/// O(1). This method does *not* perform any elementwise work.
@inlinable public var object: JSON.Object? {
switch self {
case .object(let items):
items
case .number(let number):
.init(encoding: number)
default:
nil
case .object(let items): items
default: nil
}
}
}
6 changes: 3 additions & 3 deletions Sources/JSONAST/JSON.Number.Base10.Inverse.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
extension JSON.Number.Base10 {
/// Negative powers of 10, down to [`1e-19`]().
/// Negative powers of 10, down to `1e-19`.
enum Inverse {
/// Returns the inverse of the given power of 10.
/// - Parameters:
/// - x: A positive exponent. If `x` is [`2`](), this subscript
/// will return [`1e-2`]().
/// - x: A positive exponent. If `x` is `2`, this subscript
/// will return `1e-2`.
/// - _: A ``BinaryFloatingPoint`` type.
static subscript<T>(x: Int, as _: T.Type) -> T
where T: BinaryFloatingPoint {
Expand Down
132 changes: 132 additions & 0 deletions Sources/JSONAST/JSON.Number.Inline.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
extension JSON.Number {
/// This type is memory-efficient, and can store fixed-point numbers with
/// up to 64 bits of precision. It uses all 64 bits to encode its magnitude,
/// which enables it to round-trip integers of width up to ``UInt64``.
@frozen public struct Inline: Hashable, Equatable, Sendable {
// this layout should allow instances of `Number` to fit in 2 words.
// this is backed by an `Int`, but the swift compiler can optimize it
// into a `UInt8`-sized field

/// The sign of this numeric literal.
public var sign: FloatingPointSign
// cannot have an inlinable property wrapper
@usableFromInline internal var _places: UInt32
/// The number of decimal places this numeric literal has.
///
/// > Note:
/// > This property has type ``UInt64`` to facilitate computations with
/// ``units``. It is backed by a ``UInt32`` and can therefore only store
/// 32 bits of precision.
@inlinable public var places: UInt64 {
.init(self._places)
}
/// The magnitude of this numeric literal, expressed in units of ``places``.
///
/// If ``units`` is `123`, and ``places`` is `2`, that would represent
/// a magnitude of `1.23`.
public var units: UInt64
/// Creates a numeric literal.
/// - Parameters:
/// - sign: The sign, positive or negative.
/// - units: The magnitude, in units of `places`.
/// - places: The number of decimal places.
@inlinable public init(sign: FloatingPointSign, units: UInt64, places: UInt32 = 0) {
self.sign = sign
self.units = units
self._places = places
}
}
}
extension JSON.Number.Inline {
@inlinable public init<T>(_ signed: T) where T: SignedInteger {
self.init(sign: signed < 0 ? .minus : .plus, units: UInt64.init(signed.magnitude))
}
@inlinable public init<T>(_ unsigned: T) where T: UnsignedInteger {
self.init(sign: .plus, units: UInt64.init(unsigned))
}
}
extension JSON.Number.Inline {
@inlinable public func `as`<T>(
_: T.Type
) -> T? where T: FixedWidthInteger & UnsignedInteger {
guard self.places == 0 else {
return nil
}
switch self.sign {
case .minus:
return self.units == 0 ? 0 : nil
case .plus:
return T.init(exactly: self.units)
}
}

@inlinable public func `as`<T>(
_: T.Type
) -> T? where T: FixedWidthInteger & SignedInteger {
guard self.places == 0 else {
return nil
}
switch self.sign {
case .minus:
let negated: Int64 = .init(bitPattern: 0 &- self.units)
return negated <= 0 ? T.init(exactly: negated) : nil
case .plus:
return T.init(exactly: self.units)
}
}

@inlinable public func `as`<T>(_: (units: T, places: T).Type) -> (units: T, places: T)?
where T: FixedWidthInteger & SignedInteger {
guard let places: T = T.init(exactly: self.places) else {
return nil
}
switch self.sign {
case .minus:
let negated: Int64 = Int64.init(bitPattern: 0 &- self.units)
guard negated <= 0,
let units: T = T.init(exactly: negated) else {
return nil
}
return (units: units, places: places)
case .plus:
guard let units: T = T.init(exactly: self.units) else {
return nil
}
return (units: units, places: places)
}
}
}
extension JSON.Number.Inline: CustomStringConvertible {
/// Returns a zero-padded string representation of this numeric literal.
///
/// This property always formats the number with full precision.
/// If ``units`` is `100` and ``places`` is `2`, this will return
/// `"1.00"`.
///
/// This string is guaranteed to be round-trippable; reparsing it
/// will always return the same value.
///
/// > Warning:
/// > This string is not necessarily identical to how this literal was
/// written in its original source file. In particular, if it was
/// written with an exponent, the exponent would have been normalized
/// into ``units`` and ``places``.
public var description: String {
guard self.places > 0 else {
switch self.sign {
case .plus: return "\(self.units)"
case .minus: return "-\(self.units)"
}
}
let places: Int = .init(self.places)
let unpadded: String = .init(self.units)
let string: String = """
\(String.init(repeating: "0", count: Swift.max(0, 1 + places - unpadded.count)))\
\(unpadded)
"""
switch self.sign {
case .plus: return "\(string.dropLast(places)).\(string.suffix(places))"
case .minus: return "-\(string.dropLast(places)).\(string.suffix(places))"
}
}
}
Loading