| // 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}} |
| #assert(isOne(Int(readLine()!)!), "input is not 1") // expected-error{{#assert condition not constant}} |
| } |
| |
| func loops1(a: Int) -> Int { |
| var x = 42 |
| while x <= 42 { |
| x += a |
| } // expected-note {{control flow loop found}} |
| return x |
| } |
| |
| func loops2(a: Int) -> Int { |
| var x = 42 |
| // expected-note @+1 {{could not fold operation}} |
| for i in 0 ... a { |
| x += i |
| } |
| return x |
| } |
| |
| func infiniteLoop() -> Int { |
| // expected-note @+2 {{condition always evaluates to true}} |
| // expected-note @+1 {{control flow loop found}} |
| while true {} |
| // expected-warning @+1 {{will never be executed}} |
| return 1 |
| } |
| |
| func test_loops() { |
| // expected-error @+2 {{#assert condition not constant}} |
| // expected-note @+1 {{when called from here}} |
| #assert(loops1(a: 20000) > 42) |
| |
| // expected-error @+2 {{#assert condition not constant}} |
| // expected-note @+1 {{when called from here}} |
| #assert(loops2(a: 20000) > 42) |
| |
| // expected-error @+2 {{#assert condition not constant}} |
| // expected-note @+1 {{when called from here}} |
| #assert(infiniteLoop() == 1) |
| } |
| |
| func recursive(a: Int) -> Int { |
| // expected-note@+1 {{exceeded instruction limit: 512 when evaluating the expression at compile time}} |
| return a == 0 ? 0 : recursive(a: a-1) |
| } |
| |
| func test_recursive() { |
| // expected-error @+1 {{#assert condition not constant}} |
| #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 {{could not fold operation}} |
| 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) |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 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 {{could not fold operation}} |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 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 {{could not fold operation}} |
| } |
| |
| 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 {{could not fold operation}} |
| #assert(evaluate(intExpr: .int(5)) == 5) |
| // expected-error @+2 {{#assert condition not constant}} |
| // expected-note @+1 {{could not fold operation}} |
| #assert(evaluate(intExpr: .add(.int(5), .int(6))) == 11) |
| // expected-error @+2 {{#assert condition not constant}} |
| // expected-note @+1 {{could not fold operation}} |
| #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) |