From 49f95937d553cb66b3c74b6975bc86bff843c381 Mon Sep 17 00:00:00 2001 From: Jen Basch Date: Thu, 4 Sep 2025 13:53:17 -0700 Subject: [PATCH] Handle the Pair, Regex, and IntSeq types during codegen and evaluation --- Package.swift | 1 + Sources/PklSwift/API/IntSeq.swift | 70 +++++++++++++++++++ Sources/PklSwift/API/Pair.swift | 55 +++++++++++++++ Sources/PklSwift/API/PklRegex.swift | 55 +++++++++++++++ Sources/PklSwift/Decoder/PklDecoder.swift | 57 +++++++++++---- Tests/PklSwiftTests/Fixtures/AnyType.pkl | 6 ++ Tests/PklSwiftTests/Fixtures/ApiTypes.pkl | 24 ++++++- .../Fixtures/Generated/AnyType.pkl.swift | 19 ++++- .../Fixtures/Generated/ApiTypes.pkl.swift | 45 ++++++++++-- Tests/PklSwiftTests/Fixtures/Poly.pkl | 2 +- Tests/PklSwiftTests/FixturesTest.swift | 29 ++++++-- codegen/snippet-tests/input/Pairs.pkl | 13 ++++ .../output/NoCollectThroughHidden.pkl.swift | 2 +- codegen/snippet-tests/output/Pairs.pkl.swift | 57 +++++++++++++++ codegen/src/internal/ClassGen.pkl | 2 +- codegen/src/internal/Type.pkl | 10 +-- codegen/src/internal/typegen.pkl | 21 ++++++ docs/modules/ROOT/pages/codegen.adoc | 7 +- 18 files changed, 437 insertions(+), 38 deletions(-) create mode 100644 Sources/PklSwift/API/IntSeq.swift create mode 100644 Sources/PklSwift/API/Pair.swift create mode 100644 Sources/PklSwift/API/PklRegex.swift create mode 100644 codegen/snippet-tests/input/Pairs.pkl create mode 100644 codegen/snippet-tests/output/Pairs.pkl.swift diff --git a/Package.swift b/Package.swift index e1944c8..d880baf 100644 --- a/Package.swift +++ b/Package.swift @@ -92,6 +92,7 @@ let package = Package( "Fixtures/Collections.pkl", "Fixtures/Poly.pkl", "Fixtures/ApiTypes.pkl", + "Fixtures/Collections2.pkl", ], swiftSettings: [.enableUpcomingFeature("StrictConcurrency")] ), diff --git a/Sources/PklSwift/API/IntSeq.swift b/Sources/PklSwift/API/IntSeq.swift new file mode 100644 index 0000000..24b1712 --- /dev/null +++ b/Sources/PklSwift/API/IntSeq.swift @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import MessagePack + +public struct IntSeq: Decodable, Sendable { + public let start: Int + public let end: Int + public let step: Int + + public let intSeq: StrideThrough + + public init(start: Int, end: Int, step: Int = 1) { + self.start = start + self.end = end + self.step = step + self.intSeq = stride(from: start, through: end, by: step) + } +} + +extension IntSeq: Hashable { + public func hash(into hasher: inout Hasher) { + self.start.hash(into: &hasher) + self.end.hash(into: &hasher) + self.step.hash(into: &hasher) + } + + public static func == (lhs: IntSeq, rhs: IntSeq) -> Bool { + lhs.start == rhs.start + && lhs.end == rhs.end + && lhs.step == rhs.step + } +} + +extension IntSeq: PklSerializableType { + public static var messageTag: PklValueType { .intSeq } + + public static func decode(_ fields: [MessagePackValue], codingPath: [any CodingKey]) throws -> Self { + try checkFieldCount(fields, codingPath: codingPath, min: 4) + return try Self( + start: self.decodeInt(fields[1], codingPath: codingPath, index: 1), + end: self.decodeInt(fields[2], codingPath: codingPath, index: 2), + step: self.decodeInt(fields[3], codingPath: codingPath, index: 3) + ) + } + + private static func decodeInt(_ value: MessagePackValue, codingPath: [any CodingKey], index: Int) throws -> Int { + guard case .int(let intValue) = value else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Expected field \(index) to be an integer but got \(value.debugDataTypeDescription)" + )) + } + return Int(intValue) + } +} diff --git a/Sources/PklSwift/API/Pair.swift b/Sources/PklSwift/API/Pair.swift new file mode 100644 index 0000000..066eab4 --- /dev/null +++ b/Sources/PklSwift/API/Pair.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import MessagePack + +// Must be marked @unchecked Sendable because A or B can be AnyHashable +public struct Pair: Hashable, @unchecked Sendable { + /// The value of the Pair's first element. + public let first: A + + /// The value of the Pair's second element. + public let second: B + + public init(_ first: A, _ second: B) { + self.first = first + self.second = second + } + + public var tupleValue: (A, B) { (self.first, self.second) } +} + +extension Pair { + // this can't be a let because Pair is generic + public static var messageTag: PklValueType { .pair } +} + +extension Pair: PklSerializableType { + public static func decode(_ fields: [MessagePackValue], codingPath: [any CodingKey]) throws -> Self { + try checkFieldCount(fields, codingPath: codingPath, min: 3) + let first: A = try Self.decodeElement(fields[1], codingPath: codingPath) + let second: B = try Self.decodeElement(fields[2], codingPath: codingPath) + return Self(first, second) + } + + private static func decodeElement(_ value: MessagePackValue, codingPath: [any CodingKey]) throws -> T { + try value.decode(T.self) + } + + private static func decodeElement(_ value: MessagePackValue, codingPath: [any CodingKey]) throws -> T { + try (_PklDecoder.decodePolymorphic(value, codingPath: codingPath))?.value as! T + } +} diff --git a/Sources/PklSwift/API/PklRegex.swift b/Sources/PklSwift/API/PklRegex.swift new file mode 100644 index 0000000..9a3121f --- /dev/null +++ b/Sources/PklSwift/API/PklRegex.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import MessagePack + +public struct PklRegex: Sendable { + private let pattern: String + + public var regex: Regex { try! Regex(self.pattern) } + + public init(_ pattern: String) throws { + self.pattern = pattern + // check that this pattern is valid but don't store the regex + _ = try Regex(pattern) + } +} + +extension PklRegex: Hashable { + public func hash(into hasher: inout Hasher) { + self.pattern.hash(into: &hasher) + } + + public static func == (lhs: PklRegex, rhs: PklRegex) -> Bool { + lhs.pattern == rhs.pattern + } +} + +extension PklRegex: PklSerializableType { + public static var messageTag: PklValueType { .regex } + + public static func decode(_ fields: [MessagePackValue], codingPath: [any CodingKey]) throws -> Self { + try checkFieldCount(fields, codingPath: codingPath, min: 2) + guard case .string(let pattern) = fields[1] else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Expected field 0 to be a string but got \(fields[0].debugDataTypeDescription)" + )) + } + return try Self(pattern) + } +} diff --git a/Sources/PklSwift/Decoder/PklDecoder.swift b/Sources/PklSwift/Decoder/PklDecoder.swift index b0acf91..8db46d3 100644 --- a/Sources/PklSwift/Decoder/PklDecoder.swift +++ b/Sources/PklSwift/Decoder/PklDecoder.swift @@ -40,6 +40,20 @@ public enum PklValueType: UInt8, Decodable, Sendable { public protocol PklSerializableType: Decodable { static var messageTag: PklValueType { get } + + static func decode(_ fields: [MessagePackValue], codingPath: [any CodingKey]) throws -> Self +} + +extension PklSerializableType { + static func checkFieldCount(_ fields: [MessagePackValue], codingPath: [any CodingKey], min: Int) throws { + guard fields.count >= min else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Expected at least \(min) fields but got \(fields.count)" + )) + } + } } public protocol PklSerializableValueUnitType: PklSerializableType { @@ -49,35 +63,41 @@ public protocol PklSerializableValueUnitType: PklSerializableType { init(_: ValueType, unit: UnitType) } -extension Decodable where Self: PklSerializableValueUnitType { +extension Decodable where Self: PklSerializableType { public init(from decoder: Decoder) throws { guard let decoder = decoder as? _PklDecoder else { fatalError("\(Self.self) can only be decoded using \(_PklDecoder.self), but was: \(decoder)") } - self = try Self.decodeValueUnitType(from: decoder.value, at: decoder.codingPath) - } -} - -extension PklSerializableValueUnitType { - static func decodeValueUnitType( - from value: MessagePackValue, - at codingPath: [CodingKey] - ) throws -> Self { - guard case .array(let arr) = value else { + let codingPath = decoder.codingPath + guard case .array(let arr) = decoder.value else { throw DecodingError.dataCorrupted( .init( codingPath: codingPath, - debugDescription: "Expected array but got \(value.debugDataTypeDescription)" + debugDescription: "Expected array but got \(decoder.value.debugDataTypeDescription)" )) } let code = try arr[0].decode(PklValueType.self) + guard arr.count > 0 else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Expected non-empty array" + )) + } guard Self.messageTag == code else { throw DecodingError.dataCorrupted( .init(codingPath: codingPath, debugDescription: "Cannot decode \(code) into \(Self.self)")) } - let value = try arr[1].decode(Self.ValueType.self) - let unit = try arr[2].decode(Self.UnitType.self) + self = try Self.decode(arr, codingPath: codingPath) + } +} + +extension Decodable where Self: PklSerializableValueUnitType { + public static func decode(_ fields: [MessagePackValue], codingPath: [any CodingKey]) throws -> Self { + try checkFieldCount(fields, codingPath: codingPath, min: 3) + let value = try fields[1].decode(Self.ValueType.self) + let unit = try fields[2].decode(Self.UnitType.self) return Self(value, unit: unit) } } @@ -237,6 +257,15 @@ extension _PklDecoder { case .dataSize: let decoder = try _PklDecoder(value: propertyValue) return try PklAny(value: DataSize(from: decoder)) + case .pair: + let decoder = try _PklDecoder(value: propertyValue) + return try PklAny(value: Pair(from: decoder)) + case .regex: + let decoder = try _PklDecoder(value: propertyValue) + return try PklAny(value: PklRegex(from: decoder)) + case .intSeq: + let decoder = try _PklDecoder(value: propertyValue) + return try PklAny(value: IntSeq(from: decoder)) case .bytes: guard case .bin(let bytes) = value[1] else { throw DecodingError.dataCorrupted( diff --git a/Tests/PklSwiftTests/Fixtures/AnyType.pkl b/Tests/PklSwiftTests/Fixtures/AnyType.pkl index 1769582..eb6fe0c 100644 --- a/Tests/PklSwiftTests/Fixtures/AnyType.pkl +++ b/Tests/PklSwiftTests/Fixtures/AnyType.pkl @@ -28,3 +28,9 @@ nullable: Any = null duration: Any = 5.min dataSize: Any = 10.mb + +pair: Any = Pair(1, 2) + +regex: Any = Regex("abc") + +seq: Any = IntSeq(0, 10).step(2) diff --git a/Tests/PklSwiftTests/Fixtures/ApiTypes.pkl b/Tests/PklSwiftTests/Fixtures/ApiTypes.pkl index f650731..6d0892e 100644 --- a/Tests/PklSwiftTests/Fixtures/ApiTypes.pkl +++ b/Tests/PklSwiftTests/Fixtures/ApiTypes.pkl @@ -1,2 +1,22 @@ -res1: Duration = 10.h -res2: DataSize = 1.2345.gib \ No newline at end of file +dur: Duration = 20.h + +data: DataSize = 2.4680.gib + +pair1: Pair = Pair("a", "b") +pair2: Pair = Pair("a", 1) +pair3: Pair = Pair("a", 1) +pair4: Pair = Pair("a", null) + +// pairMapping1: Mapping +// pairMapping2: Mapping, Any> + +pairListing1: Listing = new { + pair2 + pair3 + pair4 +} +pairListing2: Listing> = pairListing1 + +regex: Regex = Regex("def") + +seq: IntSeq = IntSeq(0, 20).step(3) diff --git a/Tests/PklSwiftTests/Fixtures/Generated/AnyType.pkl.swift b/Tests/PklSwiftTests/Fixtures/Generated/AnyType.pkl.swift index db34958..b33b920 100644 --- a/Tests/PklSwiftTests/Fixtures/Generated/AnyType.pkl.swift +++ b/Tests/PklSwiftTests/Fixtures/Generated/AnyType.pkl.swift @@ -25,6 +25,12 @@ extension AnyType { public var dataSize: AnyHashable? + public var pair: AnyHashable? + + public var regex: AnyHashable? + + public var seq: AnyHashable? + public init( bird: AnyHashable?, primitive: AnyHashable?, @@ -34,7 +40,10 @@ extension AnyType { mapping: AnyHashable?, nullable: AnyHashable?, duration: AnyHashable?, - dataSize: AnyHashable? + dataSize: AnyHashable?, + pair: AnyHashable?, + regex: AnyHashable?, + seq: AnyHashable? ) { self.bird = bird self.primitive = primitive @@ -45,6 +54,9 @@ extension AnyType { self.nullable = nullable self.duration = duration self.dataSize = dataSize + self.pair = pair + self.regex = regex + self.seq = seq } public init(from decoder: Decoder) throws { @@ -58,7 +70,10 @@ extension AnyType { let nullable = try dec.decode(PklSwift.PklAny.self, forKey: PklCodingKey(string: "nullable")).value let duration = try dec.decode(PklSwift.PklAny.self, forKey: PklCodingKey(string: "duration")).value let dataSize = try dec.decode(PklSwift.PklAny.self, forKey: PklCodingKey(string: "dataSize")).value - self = Module(bird: bird, primitive: primitive, primitive2: primitive2, array: array, set: set, mapping: mapping, nullable: nullable, duration: duration, dataSize: dataSize) + let pair = try dec.decode(PklSwift.PklAny.self, forKey: PklCodingKey(string: "pair")).value + let regex = try dec.decode(PklSwift.PklAny.self, forKey: PklCodingKey(string: "regex")).value + let seq = try dec.decode(PklSwift.PklAny.self, forKey: PklCodingKey(string: "seq")).value + self = Module(bird: bird, primitive: primitive, primitive2: primitive2, array: array, set: set, mapping: mapping, nullable: nullable, duration: duration, dataSize: dataSize, pair: pair, regex: regex, seq: seq) } } diff --git a/Tests/PklSwiftTests/Fixtures/Generated/ApiTypes.pkl.swift b/Tests/PklSwiftTests/Fixtures/Generated/ApiTypes.pkl.swift index c733193..f4fc065 100644 --- a/Tests/PklSwiftTests/Fixtures/Generated/ApiTypes.pkl.swift +++ b/Tests/PklSwiftTests/Fixtures/Generated/ApiTypes.pkl.swift @@ -7,13 +7,48 @@ extension ApiTypes { public struct Module: PklRegisteredType, Decodable, Hashable, Sendable { public static let registeredIdentifier: String = "ApiTypes" - public var res1: Duration + public var dur: Duration - public var res2: DataSize + public var data: DataSize - public init(res1: Duration, res2: DataSize) { - self.res1 = res1 - self.res2 = res2 + public var pair1: Pair + + public var pair2: Pair + + public var pair3: Pair + + public var pair4: Pair + + public var pairListing1: [Pair] + + public var pairListing2: [Pair] + + public var regex: PklRegex + + public var seq: IntSeq + + public init( + dur: Duration, + data: DataSize, + pair1: Pair, + pair2: Pair, + pair3: Pair, + pair4: Pair, + pairListing1: [Pair], + pairListing2: [Pair], + regex: PklRegex, + seq: IntSeq + ) { + self.dur = dur + self.data = data + self.pair1 = pair1 + self.pair2 = pair2 + self.pair3 = pair3 + self.pair4 = pair4 + self.pairListing1 = pairListing1 + self.pairListing2 = pairListing2 + self.regex = regex + self.seq = seq } } diff --git a/Tests/PklSwiftTests/Fixtures/Poly.pkl b/Tests/PklSwiftTests/Fixtures/Poly.pkl index a435255..2d7128f 100644 --- a/Tests/PklSwiftTests/Fixtures/Poly.pkl +++ b/Tests/PklSwiftTests/Fixtures/Poly.pkl @@ -46,4 +46,4 @@ class Bird extends lib1.Being { class Dog extends Animal { barks: Boolean hates: Animal? -} \ No newline at end of file +} diff --git a/Tests/PklSwiftTests/FixturesTest.swift b/Tests/PklSwiftTests/FixturesTest.swift index 175face..04839e5 100644 --- a/Tests/PklSwiftTests/FixturesTest.swift +++ b/Tests/PklSwiftTests/FixturesTest.swift @@ -92,9 +92,25 @@ class FixturesTest: XCTestCase { ) XCTAssertEqual( result, - ApiTypes.Module( - res1: .hours(10), - res2: .gibibytes(1.2345) + try ApiTypes.Module( + dur: .hours(20), + data: .gibibytes(2.4680), + pair1: Pair(AnyHashable?("a"), AnyHashable?("b")), + pair2: Pair("a", 1), + pair3: Pair("a", 1), + pair4: Pair("a", nil), + pairListing1: [ + Pair("a", 1), + Pair("a", 1), + Pair("a", nil), + ], + pairListing2: [ + Pair("a", 1), + Pair("a", 1), + Pair("a", nil), + ], + regex: PklRegex("def"), + seq: IntSeq(start: 0, end: 20, step: 3) ) ) } @@ -146,7 +162,7 @@ class FixturesTest: XCTestCase { ) XCTAssertEqual( result, - AnyType.Module( + try AnyType.Module( bird: AnyType.Bird(species: "Owl"), primitive: "foo", primitive2: 12, @@ -155,7 +171,10 @@ class FixturesTest: XCTestCase { mapping: ["1": 12, 12: "1"] as [AnyHashable: AnyHashable], nullable: nil, duration: Duration(5, unit: DurationUnit.min), - dataSize: DataSize(10, unit: DataSizeUnit.mb) + dataSize: DataSize(10, unit: DataSizeUnit.mb), + pair: Pair(AnyHashable?(1), AnyHashable?(2)), + regex: PklRegex("abc"), + seq: IntSeq(start: 0, end: 10, step: 2) ) ) } diff --git a/codegen/snippet-tests/input/Pairs.pkl b/codegen/snippet-tests/input/Pairs.pkl new file mode 100644 index 0000000..40d6e4f --- /dev/null +++ b/codegen/snippet-tests/input/Pairs.pkl @@ -0,0 +1,13 @@ +module Pairs + +untyped: Pair + +typed: Pair + +aliased: MyPair + +typeArgAliased: OurPair + +typealias MyPair = Pair +typealias OurPair = Pair +typealias Foo = Int diff --git a/codegen/snippet-tests/output/NoCollectThroughHidden.pkl.swift b/codegen/snippet-tests/output/NoCollectThroughHidden.pkl.swift index 5a2e194..a091f56 100644 --- a/codegen/snippet-tests/output/NoCollectThroughHidden.pkl.swift +++ b/codegen/snippet-tests/output/NoCollectThroughHidden.pkl.swift @@ -4,7 +4,7 @@ import PklSwift public enum NoCollectThroughHidden {} extension NoCollectThroughHidden { - public struct Module: PklRegisteredType, Decodable, Hashable { + public struct Module: PklRegisteredType, Decodable, Hashable, Sendable { public static let registeredIdentifier: String = "NoCollectThroughHidden" public init() {} diff --git a/codegen/snippet-tests/output/Pairs.pkl.swift b/codegen/snippet-tests/output/Pairs.pkl.swift new file mode 100644 index 0000000..dc00b77 --- /dev/null +++ b/codegen/snippet-tests/output/Pairs.pkl.swift @@ -0,0 +1,57 @@ +// Code generated from Pkl module `Pairs`. DO NOT EDIT. +import PklSwift + +public enum Pairs {} + +extension Pairs { + public struct Module: PklRegisteredType, Decodable, Hashable, Sendable { + public static let registeredIdentifier: String = "Pairs" + + public var untyped: Pair + + public var typed: Pair + + public var aliased: MyPair + + public var typeArgAliased: OurPair + + public init( + untyped: Pair, + typed: Pair, + aliased: MyPair, + typeArgAliased: OurPair + ) { + self.untyped = untyped + self.typed = typed + self.aliased = aliased + self.typeArgAliased = typeArgAliased + } + } + + public typealias MyPair = Pair + + public typealias OurPair = Pair + + public typealias Foo = Int + + /// Load the Pkl module at the given source and evaluate it into `Pairs.Module`. + /// + /// - Parameter source: The source of the Pkl module. + public static func loadFrom(source: ModuleSource) async throws -> Pairs.Module { + try await PklSwift.withEvaluator { evaluator in + try await loadFrom(evaluator: evaluator, source: source) + } + } + + /// Load the Pkl module at the given source and evaluate it with the given evaluator into + /// `Pairs.Module`. + /// + /// - Parameter evaluator: The evaluator to use for evaluation. + /// - Parameter source: The module to evaluate. + public static func loadFrom( + evaluator: PklSwift.Evaluator, + source: PklSwift.ModuleSource + ) async throws -> Pairs.Module { + try await evaluator.evaluateModule(source: source, as: Module.self) + } +} \ No newline at end of file diff --git a/codegen/src/internal/ClassGen.pkl b/codegen/src/internal/ClassGen.pkl index 588537b..10e3322 100644 --- a/codegen/src/internal/ClassGen.pkl +++ b/codegen/src/internal/ClassGen.pkl @@ -164,7 +164,7 @@ local struct: String = new Listing { "\n" } synthesisedInit - when (properties.values.any((p) -> p.isPolymorphic)) { + when (properties.values.any((p) -> p.isPolymorphic || p is Type.Tuple)) { // need to implement ==, hash and both inits "\n\n" synthesisedEqualsEquals diff --git a/codegen/src/internal/Type.pkl b/codegen/src/internal/Type.pkl index cc4549e..dda1179 100644 --- a/codegen/src/internal/Type.pkl +++ b/codegen/src/internal/Type.pkl @@ -100,17 +100,17 @@ class Nullable extends Type { } class Tuple extends Type { - members: Listing + members: List - isPolymorphic = members.toList().any((t) -> t.isPolymorphic) + isPolymorphic = members.any((t) -> t.isPolymorphic) - isAny = members.toList().any((t) -> t.isAny) + isAny = members.any((t) -> t.isAny) function render(withinNamespace: String?) = - "(" + members.toList().map((it) -> it.render(withinNamespace)).join(", ") + ")" + "(" + members.map((it) -> it.render(withinNamespace)).join(", ") + ")" function renderGeneric(withinNamespace: String?) = - "(" + members.toList().map((it) -> it.renderGeneric(withinNamespace)).join(", ") + ")" + "(" + members.map((it) -> it.renderGeneric(withinNamespace)).join(", ") + ")" } class Declared extends Type { diff --git a/codegen/src/internal/typegen.pkl b/codegen/src/internal/typegen.pkl index 5b78fef..4c0215a 100644 --- a/codegen/src/internal/typegen.pkl +++ b/codegen/src/internal/typegen.pkl @@ -68,8 +68,21 @@ function generateDeclaredType( mappedHigherOrderTypes.getOrNull(reflectee)?.apply(type, enclosing, seenMappings) ?? if (referent is reflect.TypeAlias) generateType(referent.referent, enclosing, seenMappings) + else if (reflectee == Pair) + generatePair(type, enclosing, seenMappings) else throw("Cannot generate type \(type.referent.name) as Swift.") +function generatePair( + type: reflect.DeclaredType, + enclosing: reflect.TypeDeclaration, + seenMappings: List +): Type = + new Type.Declared { + swiftModuleName = "PklSwift" + typeName = "Pair" + typeArguments = type.typeArguments.map((t) -> generateType(t, enclosing, seenMappings)) + } + local function builtInType(typ: String): Type.Declared = new { typeName = typ } anyType: Type.Nullable = new { elem = builtInType("AnyHashable") } @@ -112,6 +125,14 @@ mappedTypes: Mapping = new { swiftModuleName = "PklSwift" typeName = "DataSizeUnit" } + [Regex] = new Type.Declared { + swiftModuleName = "PklSwift" + typeName = "PklRegex" + } + [IntSeq] = new Type.Declared { + swiftModuleName = "PklSwift" + typeName = "IntSeq" + } [Bytes] = new Type.Array { elem = new Type.Declared { typeName = "UInt8" diff --git a/docs/modules/ROOT/pages/codegen.adoc b/docs/modules/ROOT/pages/codegen.adoc index 37bd8c7..184d58c 100644 --- a/docs/modules/ROOT/pages/codegen.adoc +++ b/docs/modules/ROOT/pages/codegen.adoc @@ -76,7 +76,7 @@ The below table describes how Pkl types are mapped into Swift types. |`Set` |`Pair` -|Not supported +|`PklSwift.Pair` |`Dynamic` |Not supported @@ -88,7 +88,10 @@ The below table describes how Pkl types are mapped into Swift types. |`PklSwift.Duration` |`IntSeq` -|Not supported +|`PklSwift.IntSeq` + +|`Regex` +|`PklSwift.PklRegex` |`Class` |Not supported