// RUN: %target-typecheck-verify-swift -swift-version 5 // Tests for the diagnostics emitted in Sema for checking constantness of // function arguments annotated to be so. Note that these annotation are // specific to the new os log overlay and the low-level atomics library. // The following tests check different types of constants that are accepted // by the constantness Sema check. It creates helper functions with the // semantics annotation: "oslog.requires_constant_arguments" which requires that // all arguments passed to the function are constants. The annotation is meant // to be used only by the os log overlay. The test helpers use it here only for // the purpose of testing the functionality. // Check simple literals. @_semantics("oslog.requires_constant_arguments") func constantArgumentFunction(_ constArg: T) { } func literalTest(x: Int) { constantArgumentFunction(1) constantArgumentFunction("Some string") constantArgumentFunction(1.9) constantArgumentFunction(true) constantArgumentFunction(x) // expected-error@-1 {{argument must be an integer literal}} constantArgumentFunction(x + 2) // expected-error@-1 {{argument must be an integer literal}} } @_semantics("oslog.requires_constant_arguments") func constArgFunctionWithReturn(_ constArg: T) -> T { return constArg } func testConstArgFuncWithReturn(x: Int, str: String) -> Int { _ = constArgFunctionWithReturn("") _ = constArgFunctionWithReturn(str) // expected-error@-1 {{argument must be a string literal}} constArgFunctionWithReturn(10) // expected-warning@-1 {{result of call to 'constArgFunctionWithReturn' is unused}} return constArgFunctionWithReturn(x) // expected-error@-1 {{argument must be an integer literal}} } @_semantics("oslog.requires_constant_arguments") func constantOptionalArgument(_ constArg: Optional) { } // Correct test cases. func optionalTest(x: Int) { constantOptionalArgument(nil) constantOptionalArgument(0) constantArgumentFunction(x + 2) // expected-error@-1 {{argument must be an integer literal}} } // Test string interpolation literals. We can only enforce constantness on custom string // interpolation types. For string types, the constant is a string literal. struct CustomStringInterpolation : ExpressibleByStringLiteral, ExpressibleByStringInterpolation { struct StringInterpolation : StringInterpolationProtocol { init(literalCapacity: Int, interpolationCount: Int) { } mutating func appendLiteral(_ x: String) { } @_semantics("oslog.requires_constant_arguments") mutating func appendInterpolation(_ x: Int) { } } init(stringLiteral value: String) { } init(stringInterpolation: StringInterpolation) { } } @_semantics("oslog.requires_constant_arguments") func constantStringInterpolation(_ constArg: CustomStringInterpolation) {} func testStringInterpolationLiteral(x: Int) { constantStringInterpolation("a string interpolation literal \(x)") // expected-error@-1 {{argument must be an integer literal}} constantStringInterpolation("a string interpolation literal \(10)") } // Test multiple arguments. @_semantics("oslog.requires_constant_arguments") func multipleArguments(_ arg1: Int, _ arg2: Bool, _ arg3: String, _ arg4: Double) { } func testMultipleArguments(_ x: String, _ y: Double) { multipleArguments(56, false, "", 23.3) multipleArguments(56, false, x, y) // expected-error@-1 {{argument must be a string literal}} // expected-error@-2 {{argument must be a floating-point literal}} } // Test enum uses. enum Color { case red case blue case green case rgb(r: Int, g: Int, b: Int) } @_semantics("oslog.requires_constant_arguments") func enumArgument(_ color: Color) { } func testEnumArgument(r: Int, c: Color) { enumArgument(.rgb(r: 12, g: 0, b: 1)) enumArgument(.green) enumArgument(.rgb(r: r, g: 200, b: 453)) // expected-error@-1 {{argument must be an integer literal}} enumArgument(c) // expected-error@-1 {{argument must be a case of enum 'Color'}} } // Test type expressions. @_semantics("oslog.requires_constant_arguments") func typeArgument(_ t: T.Type) { } func testTypeArgument(_ t: S.Type) { typeArgument(Int.self) typeArgument(S.self) typeArgument(t) // expected-error@-1 {{argument must be a .self}} } // Test constant evaluable function calls. @_semantics("constant_evaluable") func constantEval(_ x: Int, _ y: Bool) -> Int { x + 100 } func testConstantEvalArgument(x: Int) { constantArgumentFunction(constantEval(90, true)) constantArgumentFunction(constantEval(constantEval(500, true), false)) constantArgumentFunction(constantEval(x, true)) // expected-error@-1 {{argument must be an integer literal}} } // Test constant evaluable function calls with default arguments. @_semantics("constant_evaluable") func constantEvalAdvanced(_ x: () -> Int, _ y: Bool = true, z: String) { } func testConstantEvalAdvanced(arg: Int) { constantArgumentFunction(constantEvalAdvanced({ arg }, z: "")) } // Test constant evaluable methods. struct E { // expected-note@-1 {{'E' declared here}} func constantEvalMethod1() -> E { return self } @_semantics("constant_evaluable") func constantEvalMethod2() -> E { return self } @_semantics("constant_evaluable") static func constantEvalMethod3(x: Bool) -> E { return E() } @_semantics("constant_evaluable") static func constantEvalMethod4() -> E { return E() } } @_semantics("oslog.requires_constant_arguments") func functionNeedingConstE(_ x: E) { } func testConstantEvalMethod(b: Bool) { functionNeedingConstE(E().constantEvalMethod1()) // expected-error@-1 {{argument must be a static method or property of 'E'}} functionNeedingConstE(E().constantEvalMethod2()) functionNeedingConstE(.constantEvalMethod3(x: true)) functionNeedingConstE(.constantEvalMethod3(x: b)) // expected-error@-1 {{argument must be a bool literal}} functionNeedingConstE(.constantEvalMethod4()) } // Test functions with autoclosures. @_semantics("oslog.requires_constant_arguments") func autoClosureArgument(_ number: @autoclosure @escaping () -> Int) { } func testAutoClosure(_ number: Int) { autoClosureArgument(number) } @_semantics("constant_evaluable") func constEvalWithAutoClosure(_ number: @autoclosure @escaping () -> Int) -> Int { return 0 } func testConstantEvalAutoClosure(_ number: Int) { constantArgumentFunction(constEvalWithAutoClosure(number)) } // Test nested use of constant parameter. @_semantics("oslog.requires_constant_arguments") func testConstantArgumentRequirementPropagation(constParam: Int) { constantArgumentFunction(constParam) } // Test nested use of constant parameter in constant evaluable function. @_semantics("oslog.requires_constant_arguments") func testConstantArgumentWithConstEval(constParam: Int) { constantArgumentFunction(constantEval(constParam, true)) } // Test parital-apply of constantArgumentFunction. @_semantics("oslog.requires_constant_arguments") func constArg2(_ x: Int) -> Int { x } // This is not an error. func testPartialApply() -> ((Int) -> Int) { return constArg2 } @_semantics("oslog.requires_constant_arguments") func constArg3(_ x: (Int) -> Int) -> Int { x(0) } @_semantics("constant_evaluable") func intIdentity(_ x: Int) -> Int { x } func testPartialApply2() -> Int { return constArg3(intIdentity) // expected-error@-1 {{argument must be a closure}} } // Test struct and class constructions. Structs whose initializers are marked as // constant_evaluable are considered as constants. struct AStruct { var i: Int } struct BStruct { var i: Int @_semantics("constant_evaluable") init(_ value: Int) { i = value } } class CClass { var str: String init() { str = "" } } func testStructAndClasses(arg: Int) { constantArgumentFunction(AStruct(i: 9)) // expected-error@-1 {{argument must be a static method or property of 'AStruct'}} constantArgumentFunction(BStruct(340)) constantArgumentFunction(CClass()) // expected-error@-1 {{argument must be a static method or property of 'CClass'}} } // Test "requires_constant" annotation on protocol requirements. protocol Proto { @_semantics("oslog.requires_constant_arguments") func method(arg1: Int, arg2: Bool) } struct SConf : Proto { func method(arg1: Int, arg2: Bool) { } } func testProtocolMethods(b: Bool, p: T, p2: Proto, s: SConf) { p.method(arg1: 6, arg2: true) p.method(arg1: 6, arg2: b) // expected-error@-1 {{argument must be a bool literal}} p2.method(arg1: 6, arg2: b) // expected-error@-1 {{argument must be a bool literal}} // Note that even though 's' conforms to Proto, since its method is not // annotated as requiring constant arg2, there will be no error here. s.method(arg1: 6, arg2: b) } // Check requires annotation on a class method. class ClassD { @_semantics("oslog.requires_constant_arguments") func method(_ arg1: Int, _ arg2: Bool) } func testClassMethod(d: ClassD, b: Bool) { d.method(10, true) d.method(10, b) // expected-error@-1 {{argument must be a bool literal}} } // Test that the check is resilient to errors in the semantics attribute. @_semantics("oslog.requires_constant_") func funcWithWrongSemantics(x: Int) {} func testFunctionWithWrongSemantics(x: Int) { funcWithWrongSemantics(x: x) } // Test that the check is resilient to other type errors. func testOtherTypeErrors() { constantArgumentFunction(x) // expected-error@-1 {{cannot find 'x' in scope}} constantArgumentFunction(10 as String) // expected-error@-1 {{cannot convert value of type 'Int' to type 'String' in coercion}} } // Test constantness of the ordering used in the atomic operations. The atomic // operations that requires a constant ordering are required to use the // semantics annotation "atomics.requires_constant_orderings". internal struct AtomicLoadOrdering { @_semantics("constant_evaluable") internal static var acquiring: Self { Self() } @_semantics("constant_evaluable") internal static var sequentiallyConsistent: Self { Self() } } internal struct UnsafeAtomicIntStub { @_semantics("atomics.requires_constant_orderings") internal func load( ordering: AtomicLoadOrdering = .sequentiallyConsistent ) -> Int { return 0 } } func testAtomicOrderingConstantness( atomicInt: UnsafeAtomicIntStub, myOrder: AtomicLoadOrdering ) { _ = atomicInt.load() _ = atomicInt.load(ordering: .acquiring) _ = atomicInt.load(ordering: .sequentiallyConsistent) _ = atomicInt.load(ordering: myOrder) // expected-error@-1 {{ordering argument must be a static method or property of 'AtomicLoadOrdering'}} } // Test that the check can handle ranges @_semantics("oslog.requires_constant_arguments") func constantRange(x: Range) {} @_semantics("oslog.requires_constant_arguments") func constantClosedRange(x: ClosedRange) {} func testConstantRange(x: Int) { constantRange(x: 0..<5) constantRange(x: 0..(_ x: () -> T) -> T { return x() } func normalFunction() {} @_semantics("oslog.requires_constant_arguments") func constantArgumentFunctionReturningIntCollection(_ constArg: Int) -> Array { return [constArg, constArg, constArg] } @_semantics("oslog.requires_constant_arguments") func constantArgumentFunctionReturningInt(_ constArg: Int) -> Int { return constArg } func testCallsWithinClosures(s: String, x: Int) { funcAcceptingClosure { constantArgumentFunction(s) // expected-error@-1 {{argument must be a string literal}} } funcAcceptingClosure { constantArgumentFunction(s) // expected-error@-1 {{argument must be a string literal}} constantArgumentFunction(s) // expected-error@-1 {{argument must be a string literal}} } funcAcceptingClosure { funcAcceptingClosure { constantArgumentFunction(s) // expected-error@-1 {{argument must be a string literal}} } } funcAcceptingClosure { normalFunction() funcAcceptingClosure { constantArgumentFunction(s) // expected-error@-1 {{argument must be a string literal}} } } let _ = funcAcceptingClosure { constantArgumentFunctionReturningIntCollection(x) // expected-error@-1 {{argument must be an integer literal}} } .filter { $0 > 0 } .map { $0 + 1 } let _ = funcAcceptingClosure { constantArgumentFunctionReturningInt(x) // expected-error@-1 {{argument must be an integer literal}} } + 10 * x let _ = { constantArgumentFunctionReturningIntCollection(x) } // expected-error@-1 {{argument must be an integer literal}} funcAcceptingClosure { constantArgumentFunction(1) constantArgumentFunction("string literal") constantArgumentFunction("string with a single interpolation \(x)") } } @resultBuilder struct MyArrayBuilder { typealias Component = [Int] typealias Expression = Int static func buildExpression(_ element: Expression) -> Component { return [element] } static func buildBlock(_ components: Component...) -> Component { return Array(components.joined()) } } struct MyArray { public init(@MyArrayBuilder arr: () -> [Int]) {} } func testResultBuilder(x: Int, y: Int) -> MyArray { let _: MyArray = MyArray { constantArgumentFunctionReturningInt(x) // expected-error@-1 {{argument must be an integer literal}} constantArgumentFunctionReturningInt(y) // expected-error@-1 {{argument must be an integer literal}} } let _: MyArray = MyArray { constantArgumentFunctionReturningInt(x) // expected-error@-1 {{argument must be an integer literal}} } }