mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
unknown symbolic values by renaming some diagnostics and creating new unknown reasons for each type of failure that can happen during constant evaluation.
683 lines
18 KiB
Swift
683 lines
18 KiB
Swift
// RUN: %target-swift-frontend -enable-experimental-static-assert -emit-sil %s -verify
|
|
|
|
// REQUIRES: asserts
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Basic function calls and control flow
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func isOne(_ x: Int) -> Bool {
|
|
return x == 1
|
|
}
|
|
|
|
func test_assertionSuccess() {
|
|
#assert(isOne(1))
|
|
#assert(isOne(1), "1 is not 1")
|
|
}
|
|
|
|
func test_assertionFailure() {
|
|
#assert(isOne(2)) // expected-error{{assertion failed}}
|
|
#assert(isOne(2), "2 is not 1") // expected-error{{2 is not 1}}
|
|
}
|
|
|
|
func test_nonConstant() {
|
|
#assert(isOne(Int(readLine()!)!)) // expected-error{{#assert condition not constant}}
|
|
// expected-note@-1 {{cannot evaluate expression as constant here}}
|
|
#assert(isOne(Int(readLine()!)!), "input is not 1") // expected-error{{#assert condition not constant}}
|
|
// expected-note@-1 {{cannot evaluate expression as constant here}}
|
|
}
|
|
|
|
func loops1(a: Int) -> Int {
|
|
var x = 42
|
|
while x <= 42 {
|
|
x += a
|
|
} // expected-note {{found loop here}}
|
|
return x
|
|
}
|
|
|
|
func loops2(a: Int) -> Int {
|
|
var x = 42
|
|
// expected-note @+1 {{operation not supported by the evaluator}}
|
|
for i in 0 ... a {
|
|
x += i
|
|
}
|
|
return x
|
|
}
|
|
|
|
func infiniteLoop() -> Int {
|
|
// expected-note @+2 {{condition always evaluates to true}}
|
|
// expected-note @+1 {{found loop here}}
|
|
while true {}
|
|
// expected-warning @+1 {{will never be executed}}
|
|
return 1
|
|
}
|
|
|
|
func test_loops() {
|
|
// expected-error @+2 {{#assert condition not constant}}
|
|
// expected-note @+1 {{control-flow loop found during evaluation}}
|
|
#assert(loops1(a: 20000) > 42)
|
|
|
|
// expected-error @+2 {{#assert condition not constant}}
|
|
// expected-note @+1 {{encountered operation not supported by the evaluator}}
|
|
#assert(loops2(a: 20000) > 42)
|
|
|
|
// expected-error @+2 {{#assert condition not constant}}
|
|
// expected-note @+1 {{control-flow loop found during evaluation}}
|
|
#assert(infiniteLoop() == 1)
|
|
}
|
|
|
|
func recursive(a: Int) -> Int {
|
|
// expected-note@+1 {{limit exceeded here}}
|
|
return a == 0 ? 0 : recursive(a: a-1)
|
|
}
|
|
|
|
func test_recursive() {
|
|
// expected-error @+2 {{#assert condition not constant}}
|
|
// expected-note @+1 {{exceeded instruction limit: 512 when evaluating the expression at compile time}}
|
|
#assert(recursive(a: 20000) > 42)
|
|
}
|
|
|
|
func conditional(_ x: Int) -> Int {
|
|
if x < 0 {
|
|
return 0
|
|
} else {
|
|
return x
|
|
}
|
|
}
|
|
|
|
func test_conditional() {
|
|
#assert(conditional(-5) == 0)
|
|
#assert(conditional(5) == 5)
|
|
|
|
// expected-error @+1 {{assertion failed}}
|
|
#assert(conditional(-5) == 1)
|
|
// expected-error @+1 {{assertion failed}}
|
|
#assert(conditional(5) == 1)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top-level evaluation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func test_topLevelEvaluation(topLevelArgument: Int) {
|
|
let topLevelConst = 1
|
|
#assert(topLevelConst == 1)
|
|
|
|
// The #assert successfully sees the value of this `var` even though it is
|
|
// mutable because DiagnosticConstantPropagation propagates its value.
|
|
var topLevelVar = 1 // expected-warning {{never mutated}}
|
|
#assert(topLevelVar == 1)
|
|
|
|
// expected-note @+1 {{cannot evaluate top-level value as constant here}}
|
|
var topLevelVarConditionallyMutated = 1
|
|
if topLevelVarConditionallyMutated < 0 {
|
|
topLevelVarConditionallyMutated += 1
|
|
}
|
|
// expected-error @+1 {{#assert condition not constant}}
|
|
#assert(topLevelVarConditionallyMutated == 1)
|
|
|
|
// expected-error @+1 {{#assert condition not constant}}
|
|
#assert(topLevelArgument == 1)
|
|
// expected-note@-1 {{cannot evaluate expression as constant here}}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Integers
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func test_trapsAndOverflows() {
|
|
// The error message below is generated by the traditional constant folder.
|
|
// The interpreter responsible for #assert does not generate an overflow
|
|
// error because the traditional constant folder replaces the condition with
|
|
// a constant before the #assert interpreter sees it.
|
|
// expected-error @+1 {{arithmetic operation '124 + 92' (on type 'Int8') results in an overflow}}
|
|
#assert((124 as Int8) + 92 < 42)
|
|
|
|
// One error message below is generated by the traditional constant folder.
|
|
// The interpreter responsible for #assert does generate an additional error
|
|
// message.
|
|
// expected-error @+2 {{integer literal '123231' overflows when stored into 'Int8'}}
|
|
// expected-error @+1 {{#assert condition not constant}}
|
|
#assert(Int8(123231) > 42)
|
|
// expected-note @-1 {{integer overflow detected}}
|
|
|
|
// The error message below is generated by the traditional constant folder.
|
|
// The interpreter responsible for #assert does not generate an overflow
|
|
// error because the traditional constant folder replaces the condition with
|
|
// a constant before the #assert interpreter sees it.
|
|
// expected-error @+2 {{arithmetic operation '124 + 8' (on type 'Int8') results in an overflow}}
|
|
// expected-error @+1 {{assertion failed}}
|
|
#assert(Int8(124) + 8 > 42)
|
|
}
|
|
|
|
// Calling this stops the traditional mandatory constant folder from folding
|
|
// the arithmetic before ConstExpr.cpp gets it.
|
|
func identity(_ x: Int) -> Int {
|
|
return x
|
|
}
|
|
|
|
func test_integerArithmetic() {
|
|
#assert(identity(1) + 1 == 2)
|
|
#assert(identity(1) - 1 == 0)
|
|
#assert(identity(2) * 2 == 4)
|
|
#assert(identity(10) / 10 == 1)
|
|
#assert(identity(10) % 7 == 3)
|
|
#assert(identity(1) < 2)
|
|
#assert(identity(1) <= 1)
|
|
#assert(identity(2) > 1)
|
|
#assert(identity(1) >= 1)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Custom structs and tuples
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct CustomStruct {
|
|
let x: (Int, Int)
|
|
let y: Int
|
|
}
|
|
|
|
func test_CustomStruct() {
|
|
let cs = CustomStruct(x: (1, 2), y: 3)
|
|
#assert(cs.x.0 == 1)
|
|
#assert(cs.x.1 == 2)
|
|
#assert(cs.y == 3)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Mutation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct InnerStruct {
|
|
var a, b: Int
|
|
}
|
|
|
|
struct MutableStruct {
|
|
var x: InnerStruct
|
|
var y: Int
|
|
}
|
|
|
|
func addOne(to target: inout Int) {
|
|
target += 1
|
|
}
|
|
|
|
func callInout() -> Bool {
|
|
var myMs = MutableStruct(x: InnerStruct(a: 1, b: 2), y: 3)
|
|
addOne(to: &myMs.x.a)
|
|
addOne(to: &myMs.y)
|
|
return (myMs.x.a + myMs.x.b + myMs.y) == 8
|
|
}
|
|
|
|
func replaceAggregate() -> Bool {
|
|
var myMs = MutableStruct(x: InnerStruct(a: 1, b: 2), y: 3)
|
|
myMs.x = InnerStruct(a: 10, b: 20)
|
|
return myMs.x.a == 10 && myMs.x.b == 20 && myMs.y == 3
|
|
}
|
|
|
|
func shouldNotAlias() -> Bool {
|
|
var x = 1
|
|
var y = x
|
|
x += 1
|
|
y += 2
|
|
return x == 2 && y == 3
|
|
}
|
|
|
|
func invokeMutationTests() {
|
|
#assert(callInout())
|
|
#assert(replaceAggregate())
|
|
#assert(shouldNotAlias())
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Evaluating generic functions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func genericAdd<T: Numeric>(_ a: T, _ b: T) -> T {
|
|
return a + b
|
|
}
|
|
|
|
func test_genericAdd() {
|
|
#assert(genericAdd(1, 1) == 2)
|
|
}
|
|
|
|
func test_tupleAsGeneric() {
|
|
func identity<T>(_ t: T) -> T {
|
|
return t
|
|
}
|
|
#assert(identity((1, 2)) == (1, 2))
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Reduced testcase propagating substitutions around.
|
|
//===----------------------------------------------------------------------===//
|
|
protocol SubstitutionsP {
|
|
init<T: SubstitutionsP>(something: T)
|
|
|
|
func get() -> Int
|
|
}
|
|
|
|
struct SubstitutionsX : SubstitutionsP {
|
|
var state : Int
|
|
init<T: SubstitutionsP>(something: T) {
|
|
state = something.get()
|
|
}
|
|
func get() -> Int {
|
|
fatalError()
|
|
}
|
|
|
|
func getState() -> Int {
|
|
return state
|
|
}
|
|
}
|
|
|
|
struct SubstitutionsY : SubstitutionsP {
|
|
init() {}
|
|
init<T: SubstitutionsP>(something: T) {
|
|
}
|
|
|
|
func get() -> Int {
|
|
return 123
|
|
}
|
|
}
|
|
func substitutionsF<T: SubstitutionsP>(_: T.Type) -> T {
|
|
return T(something: SubstitutionsY())
|
|
}
|
|
|
|
func testProto() {
|
|
#assert(substitutionsF(SubstitutionsX.self).getState() == 123)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Structs with generics
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test 1
|
|
struct S<X, Y> {
|
|
func method<Z>(_ z: Z) -> Int {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func callerOfSMethod<U, V, W>(_ s: S<U, V>, _ w: W) -> Int {
|
|
return s.method(w)
|
|
}
|
|
|
|
func toplevel() {
|
|
let s = S<Int, Float>()
|
|
#assert(callerOfSMethod(s, -1) == 0)
|
|
}
|
|
|
|
// Test 2: test a struct method returning its generic argument.
|
|
struct S2<X> {
|
|
func method<Z>(_ z: Z) -> Z {
|
|
return z
|
|
}
|
|
}
|
|
|
|
func callerOfS2Method<U, V>(_ s: S2<U>, _ v: V) -> V {
|
|
return s.method(v)
|
|
}
|
|
|
|
func testStructMethodReturningGenericParam() {
|
|
let s = S2<Float>()
|
|
#assert(callerOfS2Method(s, -1) == -1)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Test that the order in which the generic parameters are declared doesn't
|
|
// affect the interpreter.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
protocol Proto {
|
|
func amethod<U>(_ u: U) -> Int
|
|
}
|
|
|
|
func callMethod<U, T: Proto>(_ a: T, _ u: U) -> Int {
|
|
return a.amethod(u)
|
|
}
|
|
|
|
// Test 1
|
|
struct Sp : Proto {
|
|
func amethod<U>(_ u: U) -> Int {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func testProtocolMethod() {
|
|
let s = Sp()
|
|
#assert(callMethod(s, 10) == 0)
|
|
}
|
|
|
|
// Test 2
|
|
struct GenericS<P>: Proto {
|
|
func amethod<U>(_ u: U) -> Int {
|
|
return 12
|
|
}
|
|
}
|
|
|
|
func testProtocolMethodForGenericStructs() {
|
|
let s = GenericS<Int>()
|
|
#assert(callMethod(s, 10) == 12)
|
|
}
|
|
|
|
// Test 3 (with generic fields)
|
|
struct GenericS2<P: Equatable>: Proto {
|
|
var fld1: P
|
|
var fld2: P
|
|
|
|
init(_ p: P, _ q: P) {
|
|
fld1 = p
|
|
fld2 = q
|
|
}
|
|
|
|
func amethod<U>(_ u: U) -> Int {
|
|
if (fld1 == fld2) {
|
|
return 15
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func testProtocolMethodForStructsWithGenericFields() {
|
|
let s = GenericS2<Int>(1, 1)
|
|
#assert(callMethod(s, 10) == 15)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Structs with generics and protocols with associated types.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
protocol ProtoWithAssocType {
|
|
associatedtype U
|
|
|
|
func amethod(_ u: U) -> U
|
|
}
|
|
|
|
struct St<X, Y> : ProtoWithAssocType {
|
|
typealias U = X
|
|
|
|
func amethod(_ x: X) -> X {
|
|
return x
|
|
}
|
|
}
|
|
|
|
func callerOfStMethod<P, Q>(_ s: St<P, Q>, _ p: P) -> P {
|
|
return s.amethod(p)
|
|
}
|
|
|
|
func testProtoWithAssocTypes() {
|
|
let s = St<Int, Float>()
|
|
#assert(callerOfStMethod(s, 11) == 11)
|
|
}
|
|
|
|
// Test 2: test a protocol method returning its generic argument.
|
|
protocol ProtoWithGenericMethod {
|
|
func amethod<U>(_ u: U) -> U
|
|
}
|
|
|
|
|
|
struct SProtoWithGenericMethod<X> : ProtoWithGenericMethod {
|
|
func amethod<Z>(_ z: Z) -> Z {
|
|
return z
|
|
}
|
|
}
|
|
|
|
func callerOfGenericProtoMethod<S: ProtoWithGenericMethod, V>(_ s: S,
|
|
_ v: V) -> V {
|
|
return s.amethod(v)
|
|
}
|
|
|
|
func testProtoWithGenericMethod() {
|
|
let s = SProtoWithGenericMethod<Float>()
|
|
#assert(callerOfGenericProtoMethod(s, -1) == -1)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Converting a struct instance to protocol instance is not supported yet.
|
|
// This requires handling init_existential_addr instruction. Once they are
|
|
// supported, the following static assert must pass. For now, a workaround is
|
|
// to use generic parameters with protocol constraints in the interpretable
|
|
// code fragments.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
protocol ProtoSimple {
|
|
func amethod() -> Int
|
|
}
|
|
|
|
func callProtoSimpleMethod(_ p: ProtoSimple) -> Int {
|
|
return p.amethod()
|
|
}
|
|
|
|
struct SPsimp : ProtoSimple {
|
|
func amethod() -> Int {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func testStructPassedAsProtocols() {
|
|
let s = SPsimp()
|
|
#assert(callProtoSimpleMethod(s) == 0) // expected-error {{#assert condition not constant}}
|
|
// expected-note@-1 {{cannot evaluate top-level value as constant here}}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Strings
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct ContainsString {
|
|
let x: Int
|
|
let str: String
|
|
}
|
|
|
|
// Test string initialization
|
|
|
|
func stringInitEmptyTopLevel() {
|
|
let c = ContainsString(x: 1, str: "")
|
|
#assert(c.x == 1)
|
|
}
|
|
|
|
func stringInitNonEmptyTopLevel() {
|
|
let c = ContainsString(x: 1, str: "hello world")
|
|
#assert(c.x == 1)
|
|
}
|
|
|
|
// Test string equality (==)
|
|
|
|
func emptyString() -> String {
|
|
return ""
|
|
}
|
|
|
|
func asciiString() -> String {
|
|
return "test string"
|
|
}
|
|
|
|
func dollarSign() -> String {
|
|
return "dollar sign: \u{24}"
|
|
}
|
|
|
|
func flag() -> String {
|
|
return "flag: \u{1F1FA}\u{1F1F8}"
|
|
}
|
|
|
|
func compareWithIdenticalStrings() {
|
|
#assert(emptyString() == "")
|
|
#assert(asciiString() == "test string")
|
|
#assert(dollarSign() == "dollar sign: $")
|
|
#assert(flag() == "flag: 🇺🇸")
|
|
}
|
|
|
|
func compareWithUnequalStrings() {
|
|
#assert(emptyString() == "Nonempty") // expected-error {{assertion failed}}
|
|
#assert(asciiString() == "") // expected-error {{assertion failed}}
|
|
#assert(dollarSign() == flag()) // expected-error {{assertion failed}}
|
|
#assert(flag() == "flag: \u{1F496}") // expected-error {{assertion failed}}
|
|
}
|
|
|
|
// Test string appends (+=)
|
|
|
|
// String.+= when used at the top-level of #assert cannot be folded as the
|
|
// interpreter cannot extract the relevant instructions to interpret.
|
|
// (This is because append is a mutating function and there will be more than
|
|
// one writer to the string.) Nonetheless, flow-sensitive uses of String.+=
|
|
// will be interpretable.
|
|
func testStringAppendTopLevel() {
|
|
var a = "a"
|
|
a += "b"
|
|
#assert(a == "ab") // expected-error {{#assert condition not constant}}
|
|
// expected-note@-1 {{operation with invalid operands encountered during evaluation}}
|
|
// Note: the operands to the equals operation are invalid as the variable
|
|
// `a` is uninitialized when the call is made. This is due to imprecision
|
|
// in the top-level evaluation mode.
|
|
}
|
|
|
|
func appendedAsciiString() -> String {
|
|
var str = "test "
|
|
str += "string"
|
|
return str
|
|
}
|
|
|
|
func appendedDollarSign() -> String {
|
|
var d = "dollar sign: "
|
|
d += "\u{24}"
|
|
return d
|
|
}
|
|
|
|
func appendedFlag() -> String {
|
|
var flag = "\u{1F1FA}"
|
|
flag += "\u{1F1F8}"
|
|
return flag
|
|
}
|
|
|
|
func testStringAppend() {
|
|
#assert(appendedAsciiString() == asciiString())
|
|
#assert(appendedDollarSign() == dollarSign())
|
|
#assert(appendedFlag() == "🇺🇸")
|
|
|
|
#assert(appendedAsciiString() == "") // expected-error {{assertion failed}}
|
|
#assert(appendedDollarSign() == "") // expected-error {{assertion failed}}
|
|
#assert(appendedFlag() == "") // expected-error {{assertion failed}}
|
|
}
|
|
|
|
func conditionalAppend(_ b: Bool, _ str1: String, _ str2: String) -> String {
|
|
let suffix = "One"
|
|
var result = ""
|
|
if b {
|
|
result = str1
|
|
result += suffix
|
|
} else {
|
|
result = str2
|
|
result += suffix
|
|
}
|
|
return result
|
|
}
|
|
|
|
func testConditionalAppend() {
|
|
let first = "first"
|
|
let second = "second"
|
|
#assert(conditionalAppend(true, first, second) == "firstOne")
|
|
#assert(conditionalAppend(false, first, second) == "secondOne")
|
|
}
|
|
|
|
struct ContainsMutableString {
|
|
let x: Int
|
|
var str: String
|
|
}
|
|
|
|
func appendOfStructProperty() -> ContainsMutableString {
|
|
var c = ContainsMutableString(x: 0, str: "broken")
|
|
c.str += " arrow"
|
|
return c
|
|
}
|
|
|
|
func testAppendOfStructProperty() {
|
|
#assert(appendOfStructProperty().str == "broken arrow")
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Enums and optionals.
|
|
//===----------------------------------------------------------------------===//
|
|
func isNil(_ x: Int?) -> Bool {
|
|
return x == nil
|
|
}
|
|
|
|
#assert(isNil(nil))
|
|
#assert(!isNil(3))
|
|
|
|
public enum Pet {
|
|
case bird
|
|
case cat(Int)
|
|
case dog(Int, Int)
|
|
case fish
|
|
}
|
|
|
|
public func weighPet(pet: Pet) -> Int {
|
|
switch pet {
|
|
case .bird: return 3
|
|
case let .cat(weight): return weight
|
|
case let .dog(w1, w2): return w1+w2
|
|
default: return 1
|
|
}
|
|
}
|
|
|
|
#assert(weighPet(pet: .bird) == 3)
|
|
#assert(weighPet(pet: .fish) == 1)
|
|
#assert(weighPet(pet: .cat(2)) == 2)
|
|
// expected-error @+1 {{assertion failed}}
|
|
#assert(weighPet(pet: .cat(2)) == 3)
|
|
#assert(weighPet(pet: .dog(9, 10)) == 19)
|
|
|
|
// Test indirect enums.
|
|
indirect enum IntExpr {
|
|
case int(_ value: Int)
|
|
case add(_ lhs: IntExpr, _ rhs: IntExpr)
|
|
case multiply(_ lhs: IntExpr, _ rhs: IntExpr)
|
|
}
|
|
|
|
func evaluate(intExpr: IntExpr) -> Int {
|
|
switch intExpr {
|
|
case .int(let value):
|
|
return value
|
|
case .add(let lhs, let rhs):
|
|
return evaluate(intExpr: lhs) + evaluate(intExpr: rhs)
|
|
case .multiply(let lhs, let rhs):
|
|
return evaluate(intExpr: lhs) * evaluate(intExpr: rhs)
|
|
}
|
|
}
|
|
|
|
// TODO: The constant evaluator can't handle indirect enums yet.
|
|
// expected-error @+2 {{#assert condition not constant}}
|
|
// expected-note @+1 {{encountered operation not supported by the evaluator}}
|
|
#assert(evaluate(intExpr: .int(5)) == 5)
|
|
// expected-error @+2 {{#assert condition not constant}}
|
|
// expected-note @+1 {{encountered operation not supported by the evaluator}}
|
|
#assert(evaluate(intExpr: .add(.int(5), .int(6))) == 11)
|
|
// expected-error @+2 {{#assert condition not constant}}
|
|
// expected-note @+1 {{encountered operation not supported by the evaluator}}
|
|
#assert(evaluate(intExpr: .add(.multiply(.int(2), .int(2)), .int(3))) == 7)
|
|
|
|
// Test address-only enums.
|
|
protocol IntContainerProtocol {
|
|
var value: Int { get }
|
|
}
|
|
|
|
struct IntContainer : IntContainerProtocol {
|
|
let value: Int
|
|
}
|
|
|
|
enum AddressOnlyEnum<T: IntContainerProtocol> {
|
|
case double(_ value: T)
|
|
case triple(_ value: T)
|
|
}
|
|
|
|
func evaluate<T>(addressOnlyEnum: AddressOnlyEnum<T>) -> Int {
|
|
switch addressOnlyEnum {
|
|
case .double(let value):
|
|
return 2 * value.value
|
|
case .triple(let value):
|
|
return 3 * value.value
|
|
}
|
|
}
|
|
|
|
#assert(evaluate(addressOnlyEnum: .double(IntContainer(value: 1))) == 2)
|
|
#assert(evaluate(addressOnlyEnum: .triple(IntContainer(value: 1))) == 3)
|