Skip to content

Test: Implement a functional-style noncopyable List #73777

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 21, 2024
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
8 changes: 8 additions & 0 deletions test/Generics/inverse_generics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,11 @@ struct TestResolution3 {
var dictNC: [String: NC] = [:] // expected-error {{type 'NC' does not conform to protocol 'Copyable'}}
var exampleNC: Example<NC> = Example() // expected-error {{type 'NC' does not conform to protocol 'Copyable'}}
}

public struct Box<Wrapped: ~Copyable>: ~Copyable {}
// Box is never copyable, so we can't support this conditional conformance.
public enum List<Element: ~Copyable>: ~Copyable {
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>>)'}}
case empty
}
extension List: Copyable where Element: Copyable {}
176 changes: 154 additions & 22 deletions test/Inputs/Swiftskell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public protocol Show: ~Copyable {
borrowing func show() -> String
}

extension CustomStringConvertible {
public func show() -> String { return description }
}
extension Int: Show {}

public func print(_ s: borrowing some Show & ~Copyable) {
print(s.show())
}
Expand Down Expand Up @@ -54,33 +59,32 @@ public protocol Generator: ~Copyable {
public enum Pair<L: ~Copyable, R: ~Copyable>: ~Copyable {
case elms(L, R)
}
extension Pair: Copyable where L: Copyable, R: Copyable {}

/// MARK: Data.Maybe

public enum Maybe<Value: ~Copyable>: ~Copyable {
case just(Value)
public enum Maybe<Wrapped: ~Copyable>: ~Copyable {
case just(Wrapped)
case nothing
}

extension Maybe: Copyable {}

extension Maybe: Show where Value: Show & ~Copyable {
extension Maybe: Show where Wrapped: Show & ~Copyable {
public borrowing func show() -> String {
switch self {
case let .just(borrowing elm):
case let .just(elm):
return elm.show()
case .nothing:
return "<nothing>"
}
}
}

extension Maybe: Eq where Value: Eq, Value: ~Copyable {
extension Maybe: Eq where Wrapped: Eq, Wrapped: ~Copyable {
public static func ==(_ a: borrowing Self, _ b: borrowing Self) -> Bool {
switch a {
case let .just(borrowing a1):
case let .just(a1):
switch b {
case let .just(borrowing b1):
case let .just(b1):
return a1 == b1
case .nothing:
return false
Expand All @@ -96,19 +100,6 @@ extension Maybe: Eq where Value: Eq, Value: ~Copyable {
}
}


// FIXME: triggers crash!
// @inlinable
// public func fromMaybe<A: ~Copyable>(_ defaultVal: consuming A,
// _ mayb: consuming Maybe<A>) -> A {
// switch mayb {
// case let .just(payload):
// return payload
// case .nothing:
// return defaultVal
// }
// }

public func isJust<A: ~Copyable>(_ m: borrowing Maybe<A>) -> Bool {
switch m {
case .just:
Expand All @@ -127,3 +118,144 @@ public struct UnownedRef<Instance: AnyObject> {
@usableFromInline
internal unowned(unsafe) var _value: Instance
}

/// Provides underlying support so that you can create recursive enums, because
/// noncopyable enums do not yet support indirect cases.
public struct Box<Wrapped: ~Copyable>: ~Copyable {
private let _pointer: UnsafeMutablePointer<Wrapped>

init(_ wrapped: consuming Wrapped) {
_pointer = .allocate(capacity: 1)
_pointer.initialize(to: wrapped)
}

deinit {
_pointer.deinitialize(count: 1)
_pointer.deallocate()
}

consuming func take() -> Wrapped {
let wrapped = _pointer.move()
_pointer.deallocate()
discard self
return wrapped
}

var borrow: Wrapped {
_read { yield _pointer.pointee }
}

consuming func map(_ transform: (consuming Wrapped) -> Wrapped) -> Self {
_pointer.initialize(to: transform(_pointer.move()))
return self
}
}


/// MARK: Data.List
public enum List<Element: ~Copyable>: ~Copyable {
case cons(Element, Box<List<Element>>)
case empty

public init(_ head: consuming Element,
_ tail: consuming List<Element>) {
self = .cons(head, Box(tail))
}

public init() { self = .empty }
}

/// Pure Iteration
extension List where Element: ~Copyable {
/// Performs forward iteration through the list, accumulating a result value.
/// Returns f(xn,...,f(x2, f(x1, init))...), or init if the list is empty.
public borrowing func foldl<Out>(
init initial: consuming Out,
_ f: (borrowing Element, consuming Out) -> Out) -> Out
where Out: ~Copyable {
func loop(_ acc: consuming Out, _ lst: borrowing Self) -> Out {
switch lst {
case .empty:
return acc
case let .cons(elm, tail):
return loop(f(elm, acc), tail.borrow)
}
}
return loop(initial, self)
}

/// Performs reverse iteration through the list, accumulating a result value.
/// Returns f(x1, f(x2,...,f(xn, init)...)) or init if the list is empty.
public borrowing func foldr<Out>(
init initial: consuming Out,
_ f: (borrowing Element, consuming Out) -> Out) -> Out
where Out: ~Copyable {
switch self {
case .empty:
return initial
case let .cons(elm, tail):
return f(elm, tail.borrow.foldr(init: initial, f))
}
}

// Forward iteration without accumulating a result.
public borrowing func forEach(_ f: (borrowing Element) -> Void) -> Void {
switch self {
case .empty: return
case let .cons(elm, tail):
f(elm)
return tail.borrow.forEach(f)
}
}
}

/// Initialization
extension List where Element: ~Copyable {
// Generates a list of elements [f(0), f(1), ..., f(n-1)] from left to right.
// For n < 0, the empty list is created.
public init(length n: Int, _ f: (Int) -> Element) {
guard n > 0 else {
self = .empty
return
}

let cur = n-1
let elm = f(cur)
self = List(elm, List(length: cur, f))
}
}

/// Basic utilities
extension List where Element: ~Copyable {
/// Is this list empty?
public borrowing func empty() -> Bool {
switch self {
case .empty: return true
case .cons(_, _): return false
}
}

/// How many elements are in this list?
public borrowing func length() -> Int {
return foldl(init: 0) { $1 + 1 }
}

/// Pop the first element off the list, if present.
public consuming func pop() -> Optional<Pair<Element, List<Element>>> {
switch consume self {
case .empty: .none
case let .cons(elm, tail): .elms(elm, tail.take())
}
}

/// Push an element onto the list.
public consuming func push(_ newHead: consuming Element) -> List<Element> {
return List(newHead, self)
}
}

extension List: Show where Element: Show & ~Copyable {
public borrowing func show() -> String {
return "[" + foldl(init: "]", { $0.show() + ", " + $1 })
}
}
69 changes: 53 additions & 16 deletions test/Interpreter/moveonly_swiftskell.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,64 @@
// RUN: %empty-directory(%t)

// RUN: %target-build-swift \
// RUN: -emit-module-path %t \
// RUN: -enable-library-evolution \
// RUN: %target-build-swift-dylib(%t/%target-library-name(Swiftskell)) \
// RUN: -module-name Swiftskell \
// RUN: -emit-module \
// RUN: -emit-module-path %t/Swiftskell.swiftmodule \
// RUN: -enable-library-evolution \
// RUN: -parse-as-library \
// RUN: %S/../Inputs/Swiftskell.swift -c -o %t/Swiftskell.o \
// RUN: %S/../Inputs/Swiftskell.swift \
// RUN: -enable-experimental-feature SuppressedAssociatedTypes \
// RUN: -enable-experimental-feature NonescapableTypes

// RUN: %target-build-swift -o %t/a.out %s -I %t %t/Swiftskell.o
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s
// RUN: %target-build-swift -I%t -L%t -lSwiftskell -parse-as-library %s \
// RUN: -module-name E -o %t/E %target-rpath(%t)
// RUN: %target-codesign %t/E
// RUN: %target-codesign %t/%target-library-name(Swiftskell)
// RUN: %target-run %t/E %t/%target-library-name(Swiftskell) | %FileCheck %s

// REQUIRES: executable_test

// XFAIL: *
/* FIXME: there's a cycle getting triggered somehow (rdar://124117857)
<unknown>:0: error: circular reference
<unknown>:0: note: through reference here
<unknown>:0: error: merge-module command failed with exit code 1 (use -v to see invocation)
*/

import Swiftskell

print("hello, world")
// CHECK: hello, world
/// assertion function
func check(_ result: Bool, _ string: String? = nil, _ line: Int = #line) {
if result { return }
var msg = "assertion failure (line \(line))"
if let extra = string {
msg += ":\t" + extra
}
fatalError(msg)
}

/// Basic noncopyable type for testing.
struct File: ~Copyable, Show {
let id: Int
init(_ id: Int) {
self.id = id
}
func show() -> String { return id.show() }
}


@main
struct Main {
static func main() {
testListBasic()
}
}

func testListBasic() {
var items = List<File>(length: 5) { .init($0) }
print(items.show()) // CHECK: [0, 1, 2, 3, 4, ]
check(items.length() == 5)
check(!items.empty())

items = .empty
check(items.length() == 0)
check(items.empty())

let nums = List<Int>().push(7).push(7).push(3)
print(nums.show()) // CHECK: [7, 7, 3, ]


}
10 changes: 7 additions & 3 deletions test/ModuleInterface/noncopyable_generics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -225,19 +225,23 @@ import Swiftskell
// CHECK: #endif

// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-NEXT: public enum Maybe<Value> : ~Swift.Copyable where Value : ~Copyable {
// CHECK-NEXT: extension Swiftskell.Pair : Swift.Copyable {
// CHECK: #endif

// CHECK: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-NEXT: public enum Maybe<Wrapped> : ~Swift.Copyable where Wrapped : ~Copyable {
// CHECK: #endif

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

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

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

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