Skip to content

Commit 6edc0ad

Browse files
authored
Merge pull request #73777 from kavon/expand-swiftskell
Test: Implement a functional-style noncopyable List
2 parents 6e25d8b + b72824d commit 6edc0ad

File tree

4 files changed

+222
-41
lines changed

4 files changed

+222
-41
lines changed

test/Generics/inverse_generics.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,11 @@ struct TestResolution3 {
500500
var dictNC: [String: NC] = [:] // expected-error {{type 'NC' does not conform to protocol 'Copyable'}}
501501
var exampleNC: Example<NC> = Example() // expected-error {{type 'NC' does not conform to protocol 'Copyable'}}
502502
}
503+
504+
public struct Box<Wrapped: ~Copyable>: ~Copyable {}
505+
// Box is never copyable, so we can't support this conditional conformance.
506+
public enum List<Element: ~Copyable>: ~Copyable {
507+
case cons(Element, Box<List<Element>>) // expected-error {{associated value 'cons' of 'Copyable'-conforming generic enum 'List' has non-Copyable type '(Element, Box<List<Element>>)'}}
508+
case empty
509+
}
510+
extension List: Copyable where Element: Copyable {}

test/Inputs/Swiftskell.swift

Lines changed: 154 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ public protocol Show: ~Copyable {
88
borrowing func show() -> String
99
}
1010

11+
extension CustomStringConvertible {
12+
public func show() -> String { return description }
13+
}
14+
extension Int: Show {}
15+
1116
public func print(_ s: borrowing some Show & ~Copyable) {
1217
print(s.show())
1318
}
@@ -54,33 +59,32 @@ public protocol Generator: ~Copyable {
5459
public enum Pair<L: ~Copyable, R: ~Copyable>: ~Copyable {
5560
case elms(L, R)
5661
}
62+
extension Pair: Copyable where L: Copyable, R: Copyable {}
5763

5864
/// MARK: Data.Maybe
59-
60-
public enum Maybe<Value: ~Copyable>: ~Copyable {
61-
case just(Value)
65+
public enum Maybe<Wrapped: ~Copyable>: ~Copyable {
66+
case just(Wrapped)
6267
case nothing
6368
}
64-
6569
extension Maybe: Copyable {}
6670

67-
extension Maybe: Show where Value: Show & ~Copyable {
71+
extension Maybe: Show where Wrapped: Show & ~Copyable {
6872
public borrowing func show() -> String {
6973
switch self {
70-
case let .just(borrowing elm):
74+
case let .just(elm):
7175
return elm.show()
7276
case .nothing:
7377
return "<nothing>"
7478
}
7579
}
7680
}
7781

78-
extension Maybe: Eq where Value: Eq, Value: ~Copyable {
82+
extension Maybe: Eq where Wrapped: Eq, Wrapped: ~Copyable {
7983
public static func ==(_ a: borrowing Self, _ b: borrowing Self) -> Bool {
8084
switch a {
81-
case let .just(borrowing a1):
85+
case let .just(a1):
8286
switch b {
83-
case let .just(borrowing b1):
87+
case let .just(b1):
8488
return a1 == b1
8589
case .nothing:
8690
return false
@@ -96,19 +100,6 @@ extension Maybe: Eq where Value: Eq, Value: ~Copyable {
96100
}
97101
}
98102

99-
100-
// FIXME: triggers crash!
101-
// @inlinable
102-
// public func fromMaybe<A: ~Copyable>(_ defaultVal: consuming A,
103-
// _ mayb: consuming Maybe<A>) -> A {
104-
// switch mayb {
105-
// case let .just(payload):
106-
// return payload
107-
// case .nothing:
108-
// return defaultVal
109-
// }
110-
// }
111-
112103
public func isJust<A: ~Copyable>(_ m: borrowing Maybe<A>) -> Bool {
113104
switch m {
114105
case .just:
@@ -127,3 +118,144 @@ public struct UnownedRef<Instance: AnyObject> {
127118
@usableFromInline
128119
internal unowned(unsafe) var _value: Instance
129120
}
121+
122+
/// Provides underlying support so that you can create recursive enums, because
123+
/// noncopyable enums do not yet support indirect cases.
124+
public struct Box<Wrapped: ~Copyable>: ~Copyable {
125+
private let _pointer: UnsafeMutablePointer<Wrapped>
126+
127+
init(_ wrapped: consuming Wrapped) {
128+
_pointer = .allocate(capacity: 1)
129+
_pointer.initialize(to: wrapped)
130+
}
131+
132+
deinit {
133+
_pointer.deinitialize(count: 1)
134+
_pointer.deallocate()
135+
}
136+
137+
consuming func take() -> Wrapped {
138+
let wrapped = _pointer.move()
139+
_pointer.deallocate()
140+
discard self
141+
return wrapped
142+
}
143+
144+
var borrow: Wrapped {
145+
_read { yield _pointer.pointee }
146+
}
147+
148+
consuming func map(_ transform: (consuming Wrapped) -> Wrapped) -> Self {
149+
_pointer.initialize(to: transform(_pointer.move()))
150+
return self
151+
}
152+
}
153+
154+
155+
/// MARK: Data.List
156+
public enum List<Element: ~Copyable>: ~Copyable {
157+
case cons(Element, Box<List<Element>>)
158+
case empty
159+
160+
public init(_ head: consuming Element,
161+
_ tail: consuming List<Element>) {
162+
self = .cons(head, Box(tail))
163+
}
164+
165+
public init() { self = .empty }
166+
}
167+
168+
/// Pure Iteration
169+
extension List where Element: ~Copyable {
170+
/// Performs forward iteration through the list, accumulating a result value.
171+
/// Returns f(xn,...,f(x2, f(x1, init))...), or init if the list is empty.
172+
public borrowing func foldl<Out>(
173+
init initial: consuming Out,
174+
_ f: (borrowing Element, consuming Out) -> Out) -> Out
175+
where Out: ~Copyable {
176+
func loop(_ acc: consuming Out, _ lst: borrowing Self) -> Out {
177+
switch lst {
178+
case .empty:
179+
return acc
180+
case let .cons(elm, tail):
181+
return loop(f(elm, acc), tail.borrow)
182+
}
183+
}
184+
return loop(initial, self)
185+
}
186+
187+
/// Performs reverse iteration through the list, accumulating a result value.
188+
/// Returns f(x1, f(x2,...,f(xn, init)...)) or init if the list is empty.
189+
public borrowing func foldr<Out>(
190+
init initial: consuming Out,
191+
_ f: (borrowing Element, consuming Out) -> Out) -> Out
192+
where Out: ~Copyable {
193+
switch self {
194+
case .empty:
195+
return initial
196+
case let .cons(elm, tail):
197+
return f(elm, tail.borrow.foldr(init: initial, f))
198+
}
199+
}
200+
201+
// Forward iteration without accumulating a result.
202+
public borrowing func forEach(_ f: (borrowing Element) -> Void) -> Void {
203+
switch self {
204+
case .empty: return
205+
case let .cons(elm, tail):
206+
f(elm)
207+
return tail.borrow.forEach(f)
208+
}
209+
}
210+
}
211+
212+
/// Initialization
213+
extension List where Element: ~Copyable {
214+
// Generates a list of elements [f(0), f(1), ..., f(n-1)] from left to right.
215+
// For n < 0, the empty list is created.
216+
public init(length n: Int, _ f: (Int) -> Element) {
217+
guard n > 0 else {
218+
self = .empty
219+
return
220+
}
221+
222+
let cur = n-1
223+
let elm = f(cur)
224+
self = List(elm, List(length: cur, f))
225+
}
226+
}
227+
228+
/// Basic utilities
229+
extension List where Element: ~Copyable {
230+
/// Is this list empty?
231+
public borrowing func empty() -> Bool {
232+
switch self {
233+
case .empty: return true
234+
case .cons(_, _): return false
235+
}
236+
}
237+
238+
/// How many elements are in this list?
239+
public borrowing func length() -> Int {
240+
return foldl(init: 0) { $1 + 1 }
241+
}
242+
243+
/// Pop the first element off the list, if present.
244+
public consuming func pop() -> Optional<Pair<Element, List<Element>>> {
245+
switch consume self {
246+
case .empty: .none
247+
case let .cons(elm, tail): .elms(elm, tail.take())
248+
}
249+
}
250+
251+
/// Push an element onto the list.
252+
public consuming func push(_ newHead: consuming Element) -> List<Element> {
253+
return List(newHead, self)
254+
}
255+
}
256+
257+
extension List: Show where Element: Show & ~Copyable {
258+
public borrowing func show() -> String {
259+
return "[" + foldl(init: "]", { $0.show() + ", " + $1 })
260+
}
261+
}
Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,64 @@
11
// RUN: %empty-directory(%t)
22

3-
// RUN: %target-build-swift \
4-
// RUN: -emit-module-path %t \
5-
// RUN: -enable-library-evolution \
3+
// RUN: %target-build-swift-dylib(%t/%target-library-name(Swiftskell)) \
64
// RUN: -module-name Swiftskell \
5+
// RUN: -emit-module \
6+
// RUN: -emit-module-path %t/Swiftskell.swiftmodule \
7+
// RUN: -enable-library-evolution \
78
// RUN: -parse-as-library \
8-
// RUN: %S/../Inputs/Swiftskell.swift -c -o %t/Swiftskell.o \
9+
// RUN: %S/../Inputs/Swiftskell.swift \
10+
// RUN: -enable-experimental-feature SuppressedAssociatedTypes \
911
// RUN: -enable-experimental-feature NonescapableTypes
1012

11-
// RUN: %target-build-swift -o %t/a.out %s -I %t %t/Swiftskell.o
12-
// RUN: %target-codesign %t/a.out
13-
// RUN: %target-run %t/a.out | %FileCheck %s
13+
// RUN: %target-build-swift -I%t -L%t -lSwiftskell -parse-as-library %s \
14+
// RUN: -module-name E -o %t/E %target-rpath(%t)
15+
// RUN: %target-codesign %t/E
16+
// RUN: %target-codesign %t/%target-library-name(Swiftskell)
17+
// RUN: %target-run %t/E %t/%target-library-name(Swiftskell) | %FileCheck %s
1418

1519
// REQUIRES: executable_test
1620

17-
// XFAIL: *
18-
/* FIXME: there's a cycle getting triggered somehow (rdar://124117857)
19-
<unknown>:0: error: circular reference
20-
<unknown>:0: note: through reference here
21-
<unknown>:0: error: merge-module command failed with exit code 1 (use -v to see invocation)
22-
*/
23-
2421
import Swiftskell
2522

26-
print("hello, world")
27-
// CHECK: hello, world
23+
/// assertion function
24+
func check(_ result: Bool, _ string: String? = nil, _ line: Int = #line) {
25+
if result { return }
26+
var msg = "assertion failure (line \(line))"
27+
if let extra = string {
28+
msg += ":\t" + extra
29+
}
30+
fatalError(msg)
31+
}
32+
33+
/// Basic noncopyable type for testing.
34+
struct File: ~Copyable, Show {
35+
let id: Int
36+
init(_ id: Int) {
37+
self.id = id
38+
}
39+
func show() -> String { return id.show() }
40+
}
41+
42+
43+
@main
44+
struct Main {
45+
static func main() {
46+
testListBasic()
47+
}
48+
}
49+
50+
func testListBasic() {
51+
var items = List<File>(length: 5) { .init($0) }
52+
print(items.show()) // CHECK: [0, 1, 2, 3, 4, ]
53+
check(items.length() == 5)
54+
check(!items.empty())
55+
56+
items = .empty
57+
check(items.length() == 0)
58+
check(items.empty())
59+
60+
let nums = List<Int>().push(7).push(7).push(3)
61+
print(nums.show()) // CHECK: [7, 7, 3, ]
62+
63+
64+
}

test/ModuleInterface/noncopyable_generics.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,19 +250,23 @@ import Swiftskell
250250
// CHECK: #endif
251251

252252
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
253-
// CHECK-NEXT: public enum Maybe<Value> : ~Swift.Copyable where Value : ~Copyable {
253+
// CHECK-NEXT: extension Swiftskell.Pair : Swift.Copyable {
254+
// CHECK: #endif
255+
256+
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
257+
// CHECK-NEXT: public enum Maybe<Wrapped> : ~Swift.Copyable where Wrapped : ~Copyable {
254258
// CHECK: #endif
255259

256260
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
257261
// CHECK-NEXT: extension Swiftskell.Maybe : Swift.Copyable {
258262
// CHECK: #endif
259263

260264
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
261-
// CHECK-NEXT: extension Swiftskell.Maybe : Swiftskell.Show where Value : Swiftskell.Show, Value : ~Copyable {
265+
// CHECK-NEXT: extension Swiftskell.Maybe : Swiftskell.Show where Wrapped : Swiftskell.Show, Wrapped : ~Copyable {
262266
// CHECK: #endif
263267

264268
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
265-
// CHECK-NEXT: extension Swiftskell.Maybe : Swiftskell.Eq where Value : Swiftskell.Eq, Value : ~Copyable {
269+
// CHECK-NEXT: extension Swiftskell.Maybe : Swiftskell.Eq where Wrapped : Swiftskell.Eq, Wrapped : ~Copyable {
266270
// CHECK: #endif
267271

268272
// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics

0 commit comments

Comments
 (0)