// RUN: %target-swift-frontend -enable-experimental-static-assert -emit-sil %s -verify // REQUIRES: asserts // REQUIRES: swift_in_compiler //===----------------------------------------------------------------------===// // 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 {{encountered call to 'isOne(_:)' where the 1st argument is not a constant}} #assert(isOne(Int(readLine()!)!), "input is not 1") // expected-error{{#assert condition not constant}} // expected-note@-1 {{encountered call to 'isOne(_:)' where the 1st argument is not a constant}} } 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 for i in 0 ... a { x += i } return x } func infiniteLoop() -> Int { // expected-note @+1 {{found loop here}} while true {} 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 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) var topLevelVarConditionallyMutated = 1 if topLevelArgument < 0 { topLevelVarConditionallyMutated += 1 } // expected-note @+2 {{cannot evaluate expression as constant here}} // 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(_ a: T, _ b: T) -> T { return a + b } func test_genericAdd() { #assert(genericAdd(1, 1) == 2) } func test_tupleAsGeneric() { func identity(_ t: T) -> T { return t } #assert(identity((1, 2)) == (1, 2)) } //===----------------------------------------------------------------------===// // Reduced testcase propagating substitutions around. //===----------------------------------------------------------------------===// protocol SubstitutionsP { init(something: T) func get() -> Int } struct SubstitutionsX : SubstitutionsP { var state : Int init(something: T) { state = something.get() } func get() -> Int { fatalError() } func getState() -> Int { return state } } struct SubstitutionsY : SubstitutionsP { init() {} init(something: T) { } func get() -> Int { return 123 } } func substitutionsF(_: T.Type) -> T { return T(something: SubstitutionsY()) } func testProto() { #assert(substitutionsF(SubstitutionsX.self).getState() == 123) } //===----------------------------------------------------------------------===// // Structs with generics //===----------------------------------------------------------------------===// // Test 1 struct S { func method(_ z: Z) -> Int { return 0 } } func callerOfSMethod(_ s: S, _ w: W) -> Int { return s.method(w) } func toplevel() { let s = S() #assert(callerOfSMethod(s, -1) == 0) } // Test 2: test a struct method returning its generic argument. struct S2 { func method(_ z: Z) -> Z { return z } } func callerOfS2Method(_ s: S2, _ v: V) -> V { return s.method(v) } func testStructMethodReturningGenericParam() { let s = S2() #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) -> Int } func callMethod(_ a: T, _ u: U) -> Int { return a.amethod(u) } // Test 1 struct Sp : Proto { func amethod(_ u: U) -> Int { return 0 } } func testProtocolMethod() { let s = Sp() #assert(callMethod(s, 10) == 0) } // Test 2 struct GenericS

: Proto { func amethod(_ u: U) -> Int { return 12 } } func testProtocolMethodForGenericStructs() { let s = GenericS() #assert(callMethod(s, 10) == 12) } // Test 3 (with generic fields) struct GenericS2: Proto { var fld1: P var fld2: P init(_ p: P, _ q: P) { fld1 = p fld2 = q } func amethod(_ u: U) -> Int { if (fld1 == fld2) { return 15 } return 0 } } func testProtocolMethodForStructsWithGenericFields() { let s = GenericS2(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 : ProtoWithAssocType { typealias U = X func amethod(_ x: X) -> X { return x } } func callerOfStMethod(_ s: St, _ p: P) -> P { return s.amethod(p) } func testProtoWithAssocTypes() { let s = St() #assert(callerOfStMethod(s, 11) == 11) } // Test 2: test a protocol method returning its generic argument. protocol ProtoWithGenericMethod { func amethod(_ u: U) -> U } struct SProtoWithGenericMethod : ProtoWithGenericMethod { func amethod(_ z: Z) -> Z { return z } } func callerOfGenericProtoMethod(_ s: S, _ v: V) -> V { return s.amethod(v) } func testProtoWithGenericMethod() { let s = SProtoWithGenericMethod() #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 {{encountered call to 'callProtoSimpleMethod(_:)' where the 1st argument is not a constant}} } //===----------------------------------------------------------------------===// // 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 call to 'evaluate(intExpr:)' where the 1st argument is not a constant}} #assert(evaluate(intExpr: .int(5)) == 5) // expected-error @+2 {{#assert condition not constant}} // expected-note @+1 {{encountered call to 'evaluate(intExpr:)' where the 1st argument is not a constant}} #assert(evaluate(intExpr: .add(.int(5), .int(6))) == 11) // expected-error @+2 {{#assert condition not constant}} // expected-note @+1 {{encountered call to 'evaluate(intExpr:)' where the 1st argument is not a constant}} #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 { case double(_ value: T) case triple(_ value: T) } func evaluate(addressOnlyEnum: AddressOnlyEnum) -> 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) //===----------------------------------------------------------------------===// // Arrays //===----------------------------------------------------------------------===// // When the const-evaluator evaluates this struct, it forces evaluation of the // `arr` value. struct ContainsArray { let x: Int let arr: [Int] } func arrayInitEmptyTopLevel() { let c = ContainsArray(x: 1, arr: Array()) #assert(c.x == 1) } func arrayInitEmptyLiteralTopLevel() { // TODO: More work necessary for array initialization using literals to work // at the top level. // expected-note@+1 {{encountered call to 'ContainsArray.init(x:arr:)' where the 2nd argument is not a constant}} let c = ContainsArray(x: 1, arr: []) // expected-error @+1 {{#assert condition not constant}} #assert(c.x == 1) } func arrayInitLiteral() { // TODO: More work necessary for array initialization using literals to work // at the top level. // expected-note @+1 {{encountered call to 'ContainsArray.init(x:arr:)' where the 2nd argument is not a constant}} let c = ContainsArray(x: 1, arr: [2, 3, 4]) // expected-error @+1 {{#assert condition not constant}} #assert(c.x == 1) } func arrayInitNonConstantElementTopLevel(x: Int) { // expected-note @+1 {{encountered call to 'ContainsArray.init(x:arr:)' where the 2nd argument is not a constant}} let c = ContainsArray(x: 1, arr: [x]) // expected-error @+1 {{#assert condition not constant}} #assert(c.x == 1) } func arrayInitEmptyFlowSensitive() -> ContainsArray { return ContainsArray(x: 1, arr: Array()) } func invokeArrayInitEmptyFlowSensitive() { #assert(arrayInitEmptyFlowSensitive().x == 1) } func arrayInitEmptyLiteralFlowSensitive() -> ContainsArray { return ContainsArray(x: 1, arr: []) } func invokeArrayInitEmptyLiteralFlowSensitive() { #assert(arrayInitEmptyLiteralFlowSensitive().x == 1) } func arrayInitLiteralFlowSensitive() -> ContainsArray { return ContainsArray(x: 1, arr: [2, 3, 4]) } func invokeArrayInitLiteralFlowSensitive() { #assert(arrayInitLiteralFlowSensitive().x == 1) }