forked from swiftlang/swift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdiag_constantness_check.swift
455 lines (381 loc) · 14.1 KB
/
diag_constantness_check.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
// 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<T>(_ 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<T>(_ 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<Int>) {
}
// 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: T.Type) { }
func testTypeArgument<S>(_ t: S.Type) {
typeArgument(Int.self)
typeArgument(S.self)
typeArgument(t)
// expected-error@-1 {{argument must be a <Type>.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<T: Proto>(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<Int>) {}
@_semantics("oslog.requires_constant_arguments")
func constantClosedRange(x: ClosedRange<Int>) {}
func testConstantRange(x: Int) {
constantRange(x: 0..<5)
constantRange(x: 0..<x)
// expected-error@-1 {{argument must be an integer literal}}
constantClosedRange(x: 0...10)
constantClosedRange(x: x...10)
// expected-error@-1 {{argument must be an integer literal}}
}
struct ConstructorTest {
@_semantics("oslog.requires_constant_arguments")
init(x: Int) { }
@_semantics("oslog.requires_constant_arguments")
init(_ x: Int) { }
}
func testConstructorAnnotation(x: Int) {
let _ = ConstructorTest(x: 10)
let _ = ConstructorTest(x: x)
// expected-error@-1 {{argument must be an integer literal}}
let _ = ConstructorTest(10)
let _ = ConstructorTest(x)
// expected-error@-1 {{argument must be an integer literal}}
}
// Test closure expressions
func funcAcceptingClosure<T>(_ x: () -> T) -> T {
return x()
}
func normalFunction() {}
@_semantics("oslog.requires_constant_arguments")
func constantArgumentFunctionReturningIntCollection(_ constArg: Int) -> Array<Int> {
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}}
}
}