blob: 7ee19e67a85fb5f8024cf00c3e605c6a9cb88220 [file] [log] [blame]
// BoxingCasts.swift - Tests for boxing/unboxing casts
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
// -----------------------------------------------------------------------------
///
/// Contains tests for existential, optional, and other casts that box/unbox values.
///
// -----------------------------------------------------------------------------
// RUN: %empty-directory(%t)
//
// RUN: %gyb %s -o %t/BoxingCasts.swift
// RUN: %line-directive %t/BoxingCasts.swift -- %target-build-swift -g -module-name a -swift-version 5 -Onone %t/BoxingCasts.swift -o %t/a.swift5.Onone.out
// RUN: %target-codesign %t/a.swift5.Onone.out
// RUN: %line-directive %t/BoxingCasts.swift -- %target-run %t/a.swift5.Onone.out
//
// Note: The RUN directives above override the default test optimizations.
// This test is deliberately run non-optimized in order to verify the
// behavior of runtime methods that may not be called for optimized casts.
//
// XXX FIXME XXX TODO XXX _Also_ build this with optimizations in order to
// verify compiler behaviors.
//
// REQUIRES: executable_test
import StdlibUnittest
#if _runtime(_ObjC)
import Foundation
#endif
fileprivate func runtimeCast<From, To> (_ x: From, to: To.Type) -> To? {
return x as? To
}
fileprivate func optional<T>(_ x: T) -> Optional<T> {
return runtimeCast(x, to: Optional<T>.self)!
}
fileprivate protocol FilePrivateProtocol {}
internal protocol InternalProtocol {}
public protocol PublicProtocol {}
protocol UnimplementedProtocol {}
fileprivate enum EmptyEnum: FilePrivateProtocol, InternalProtocol, PublicProtocol { }
fileprivate enum SingleCaseEnum: FilePrivateProtocol, InternalProtocol, PublicProtocol {
case case0
init() {self = .case0}
}
fileprivate enum TrivialPayloadEnum: FilePrivateProtocol, InternalProtocol, PublicProtocol {
case payloadCase(Int)
init() {self = .payloadCase(42)}
}
extension TrivialPayloadEnum: Hashable {}
fileprivate enum MultiPayloadEnum: FilePrivateProtocol, InternalProtocol, PublicProtocol {
case case0(String)
case case1(Int)
init() {self = .case1(42)}
}
extension MultiPayloadEnum: Hashable {}
fileprivate class ClassInt: FilePrivateProtocol, InternalProtocol, PublicProtocol {
public var value: Int
private var tracker = LifetimeTracked(77)
init(_ v: Int = 42) {value = v}
}
extension ClassInt: Equatable, Hashable {
static func ==(left: ClassInt, right: ClassInt) -> Bool {left.value == right.value}
func hash(into hasher: inout Hasher) { value.hash(into: &hasher) }
}
fileprivate struct StructInt: FilePrivateProtocol, InternalProtocol, PublicProtocol {
public var value: Int
private var tracker = LifetimeTracked(77)
init(_ v: Int = 42) { value = v}
}
extension StructInt: Hashable, Equatable { }
#if _runtime(_ObjC)
fileprivate class OCClassInt: NSObject, FilePrivateProtocol, InternalProtocol, PublicProtocol {
public var value: Int
private var tracker = LifetimeTracked(77)
init(_ v: Int = 42) { value = v}
}
#endif
let BoxingCasts = TestSuite("BoxingCasts")
%{
import random
# The test body goes into a named function and the test case just invokes that
# function by name. This makes debugging easier, since it's easier to set break
# points on named functions than on arbitrary closures.
# The function names are included in the test name
# for ease of reference.
testNumber = 0
def testFunctionName():
return "test{number}".format(number=testNumber)
def nextTestNumber():
global testNumber
testNumber += 1
# Type used for intermediate casts. The base object gets
# cast to one or more of these before the final test.
class Box:
def __init__(self, name, typeName=None, cast=None):
self.name = name
self.typeName = typeName or name
self.cast_template = cast or "runtimeCast({expr}, to: {typeName}.self)!"
def cast_oper(self, expr):
return self.cast_template.format(expr=expr, typeName=self.typeName)
anyHashableBox = Box(name="AnyHashable")
all_boxes = [
Box(name="Any", typeName="Any"),
Box(name="AnyStatic", cast="({expr} as Any)"),
Box(name="AnyObject"),
Box(name="SwiftValueBox", cast="_bridgeAnythingToObjectiveC({expr})"),
Box(name="Optional", cast="optional({expr})")
]
protocol_boxes = [
Box(name="PublicProtocol"),
# Box(name="FilePrivateProtocol"), # Blocked by SR-2620 aka rdar://28281488
Box(name="InternalProtocol"),
]
# Describes a base object that will be subject to a variety of casts
default_protocols = [
"PublicProtocol",
"InternalProtocol",
# "FilePrivateProtocol" # Blocked by SR-2620 aka rdar://28281488
]
class Contents:
def __init__(self, name, constructor=None, extra_targets=[], hashable=True, roundtrips=True, protocols=default_protocols, objc_only=False):
self.name = name
self.constructor = constructor or "{name}()".format(name=name)
self.objc_only = objc_only
self.targets = ["Any"]
self.targets.extend(protocols)
self.targets.extend(extra_targets)
if roundtrips:
self.targets.append(self.name)
self.boxes = []
self.boxes.extend(all_boxes)
self.boxes.extend([Box(name=n) for n in protocols])
if hashable:
self.boxes.append(anyHashableBox)
contents = [
Contents(name="StructInt",
# extra_targets=["StructInt?"],
),
Contents(name="StructInt?",
constructor="Optional<StructInt>.some(StructInt())",
extra_targets=["StructInt"],
roundtrips=False, # Compiler bug rejects roundtrip cast T? => Any => T?
),
Contents(name="ClassInt"),
Contents(name="OCClassInt", objc_only=True),
Contents(name="SingleCaseEnum"),
Contents(name="TrivialPayloadEnum"),
Contents(name="TrivialPayloadEnum"),
Contents(name="MultiPayloadEnum"),
Contents(name="StructInt.Type",
constructor="StructInt.self",
hashable=False,
protocols=[],
),
Contents(name="StructInt.Type?",
constructor="Optional<StructInt.Type>.some(StructInt.self)",
hashable=False,
protocols=[],
extra_targets=["StructInt.Type"],
roundtrips=False, # Compiler bug rejects roundtrip cast T? => Any => T?
),
Contents(name="PublicProtocol.Protocol",
constructor="PublicProtocol.self",
hashable=False,
protocols=[],
),
]
# Code below generates a separate test case for each combination of content,
# target type, and one or more box/intermediate types.
}%
% for content in contents:
% if content.objc_only:
#if _runtime(_ObjC)
% end
% for target in content.targets:
% for box in content.boxes:
% nextTestNumber()
BoxingCasts.test("${testFunctionName()}(): Casting ${box.name}(${content.name}) to ${target}") {
// TODO: Selectively enable/disable cases that work with earlier stdlib
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
${testFunctionName()}()
}
}
func ${testFunctionName()}() {
let a = ${content.constructor}
let b = ${box.cast_oper("a")}
% # // Skip trivial cast from T? to T
% if not (content.name == target and box.name == "Optional"):
% # // Skip trivial cast from protocol box to protocol
% if box.name != target:
% # // Skip trivial cast from T? => P
% if not (target.endswith("Protocol") and box.name == "Optional"):
let c = /* ${box.name}(${content.name})) */ b as? ${target}
expectNotNil(c)
% end
% end
% end
let d = runtimeCast(/* ${box.name}(${content.name}) */ b, to: ${target}.self)
expectNotNil(d)
}
% for innerBox in [random.choice(content.boxes)]:
% nextTestNumber()
BoxingCasts.test("${testFunctionName()}(): Casting ${box.name}(${innerBox.name}(${content.name})) to ${target}") {
// TODO: Selectively enable/disable cases that work with earlier stdlib
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
${testFunctionName()}()
}
}
func ${testFunctionName()}() {
let a = ${content.constructor}
let b = ${innerBox.cast_oper("a")}
let c = ${box.cast_oper("b")}
% # // Skip trivial cast from T? to T
% if not (innerBox.name == target and box.name == "Optional"):
% # // Skip trivial cast from protocol box to protocol
% if box.name != target:
let d = /* ${box.name}(${innerBox.name}(${content.name})) */ c as? ${target}
expectNotNil(d)
% end
% end
let e = runtimeCast(/* ${box.name}(${innerBox.name}(${content.name})) */ c, to: ${target}.self)
expectNotNil(e)
}
% end
% end
% end
% if content.objc_only:
#endif
% end
% end
runAllTests()