summary refs log tree commit diff stats
path: root/tests/constructors/tinvalid_construction.nim
blob: 4b372d68a61a7f54e4740934dae800f802891eef (plain) (blame)
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
template accept(x) =
  static: assert compiles(x)

template reject(x) =
  static: assert(not compiles(x))

{.experimental: "notnil".}

type
  TRefObj = ref object
    x: int

  IllegalToConstruct = object
    x: cstring not nil

  THasNotNils = object of RootObj
    a: TRefObj not nil
    b: TRefObj not nil
    c: TRefObj

  THasNotNilsRef = ref THasNotNils

  TRefObjNotNil = TRefObj not nil

  TChoice = enum A, B, C, D, E, F

  TBaseHasNotNils = object of THasNotNils
    case choice: TChoice
    of A:
      moreNotNils: THasNotNils
    of B:
      indirectNotNils: ref THasNotNils
    else:
      discard

  PartialRequiresInit = object
    a {.requiresInit.}: int
    b: string

  PartialRequiresInitRef = ref PartialRequiresInit

  FullRequiresInit {.requiresInit.} = object
    a: int
    b: int

  FullRequiresInitRef = ref FullRequiresInit

  FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils
    e: int
    d: int

  TObj = object
    case choice: TChoice
    of A:
      a: int
    of B, C:
      bc: int
    of D:
      d: TRefObj
    of E:
      e1: TRefObj
      e2: int
    else:
      f: string

  TNestedChoices = object
    case outerChoice: bool
    of true:
      truthy: int
    else:
      case innerChoice: TChoice
      of A:
        a: int
      of B:
        b: int
      else:
        notnil: TRefObj not nil

var x = D
var nilRef: TRefObj
let notNilRef = TRefObjNotNil(x: 20)

proc makeHasNotNils: ref THasNotNils =
  (ref THasNotNils)(a: TRefObj(x: 10),
                    b: TRefObj(x: 20))

proc userDefinedDefault(T: typedesc): T =
  # We'll use that to make sure the user cannot cheat
  # with constructing requiresInit types
  discard

proc genericDefault(T: typedesc): T =
  result = default(T)

reject IllegalToConstruct()
reject:
  var x: IllegalToConstruct

accept TObj()
accept TObj(choice: A)
reject TObj(choice: A, bc: 10)  # bc is in the wrong branch
accept TObj(choice: B, bc: 20)
reject TObj(a: 10)              # branch selected without providing discriminator
reject TObj(choice: x, a: 10)   # the discrimantor must be a compile-time value when a branch is selected
accept TObj(choice: x)          # it's OK to use run-time value when a branch is not selected
accept TObj(choice: F, f: "")   # match an else clause
reject TObj(f: "")              # the discriminator must still be provided for an else clause
reject TObj(a: 10, f: "")       # conflicting fields
accept TObj(choice: E, e1: TRefObj(x: 10), e2: 10)

accept THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
reject THasNotNils(a: notNilRef, b: nilRef, c: nilRef)      # `b` shouldn't be nil
reject THasNotNils(b: notNilRef, c: notNilRef)              # there is a missing not nil field
reject THasNotNils()                                        # again, missing fields
accept THasNotNils(a: notNilRef, b: notNilRef)              # it's OK to omit a non-mandatory field
# produces only warning: reject default(THasNotNils)
# produces only warning: reject userDefinedDefault(THasNotNils)

# produces only warning: reject default(TRefObjNotNil)
# produces only warning: reject userDefinedDefault(TRefObjNotNil)
# produces only warning: reject genericDefault(TRefObjNotNil)

# missing not nils in base
reject TBaseHasNotNils()
# produces only warning: reject default(TBaseHasNotNils)
# produces only warning: reject userDefinedDefault(TBaseHasNotNils)
# produces only warning: reject genericDefault(TBaseHasNotNils)

# once you take care of them, it's ok
accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: D)

# this one is tricky!
# it has to be rejected, because choice gets value A by default (0) and this means
# that the THasNotNils field will be active (and it will demand more initialized fields).
reject TBaseHasNotNils(a: notNilRef, b: notNilRef)

# you can select a branch without mandatory fields
accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B)
accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: nil)

# but once you select a branch with mandatory fields, you must specify them
reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A)
reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, indirectNotNils: nil)
reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, moreNotNils: THasNotNils())
accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, moreNotNils: THasNotNils(a: notNilRef, b: notNilRef))

# all rules apply to sub-objects as well
accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: makeHasNotNils())
reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef())
accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef(a: notNilRef, b: notNilRef))

# Accept only instances where the `a` field is present
accept PartialRequiresInit(a: 10, b: "x")
accept PartialRequiresInit(a: 20)
reject PartialRequiresInit(b: "x")
reject PartialRequiresInit()
accept PartialRequiresInitRef(a: 10, b: "x")
accept PartialRequiresInitRef(a: 20)
reject PartialRequiresInitRef(b: "x")
reject PartialRequiresInitRef()
accept((ref PartialRequiresInit)(a: 10, b: "x"))
accept((ref PartialRequiresInit)(a: 20))
reject((ref PartialRequiresInit)(b: "x"))
reject((ref PartialRequiresInit)())

# produces only warning: reject default(PartialRequiresInit)
# produces only warning: reject userDefinedDefault(PartialRequiresInit)
reject:
  var obj: PartialRequiresInit

accept FullRequiresInit(a: 10, b: 20)
reject FullRequiresInit(a: 10)
reject FullRequiresInit(b: 20)
reject FullRequiresInit()
accept FullRequiresInitRef(a: 10, b: 20)
reject FullRequiresInitRef(a: 10)
reject FullRequiresInitRef(b: 20)
reject FullRequiresInitRef()
accept((ref FullRequiresInit)(a: 10, b: 20))
reject((ref FullRequiresInit)(a: 10))
reject((ref FullRequiresInit)(b: 20))
reject((ref FullRequiresInit)())

# produces only warning: reject default(FullRequiresInit)
# produces only warning: reject userDefinedDefault(FullRequiresInit)
reject:
  var obj: FullRequiresInit

accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: notNilRef, e: 10, d: 20)
accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10, d: 20)
reject FullRequiresInitWithParent(a: notNilRef, b: nil, c: nil, e: 10, d: 20) # b should not be nil
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20)   # c should not be missing
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10)  # d should not be missing
reject FullRequiresInitWithParent()
# produces only warning: reject default(FullRequiresInitWithParent)
# produces only warning: reject userDefinedDefault(FullRequiresInitWithParent)
reject:
  var obj: FullRequiresInitWithParent

# this will be accepted, because the false outer branch will be taken and the inner A branch
accept TNestedChoices()
accept default(TNestedChoices)
accept:
  var obj: TNestedChoices

#[# produces only warning:
reject:
  # This proc is illegal, because it tries to produce
  # a default object of a type that requires initialization:
  proc defaultHasNotNils: THasNotNils =
    discard
#]#

#[# produces only warning:
reject:
  # You cannot cheat by using the result variable to specify
  # only some of the fields
  proc invalidPartialTHasNotNils: THasNotNils =
    result.c = nilRef
#]#

#[# produces only warning:
reject:
  # The same applies for requiresInit types
  proc invalidPartialRequiersInit: PartialRequiresInit =
    result.b = "x"
#]#

#[# produces only warning:
# All code paths must return a value when the result requires initialization:
reject:
  proc ifWithoutAnElse: THasNotNils =
    if stdin.readLine == "":
      return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
#]#

accept:
  # All code paths must return a value when the result requires initialization:
  proc wellFormedIf: THasNotNils =
    if stdin.readLine == "":
      return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
    else:
      return THasNotNIls(a: notNilRef, b: notNilRef)

#[# produces only warning:
reject:
  proc caseWithoutAllCasesCovered: FullRequiresInit =
    # Please note that these is no else branch here:
    case stdin.readLine
    of "x":
      return FullRequiresInit(a: 10, b: 20)
    of "y":
      return FullRequiresInit(a: 30, b: 40)
#]#

accept:
  proc wellFormedCase: FullRequiresInit =
    case stdin.readLine
    of "x":
      result = FullRequiresInit(a: 10, b: 20)
    else:
      # Mixing result and return is fine:
      return FullRequiresInit(a: 30, b: 40)

# but if we supply a run-time value for the inner branch, the compiler won't be able to prove
# that the notnil field was initialized
reject TNestedChoices(outerChoice: false, innerChoice: x) # XXX: The error message is not very good here
reject TNestedChoices(outerChoice: true,  innerChoice: A) # XXX: The error message is not very good here

accept TNestedChoices(outerChoice: false, innerChoice: B)

reject TNestedChoices(outerChoice: false, innerChoice: C)
accept TNestedChoices(outerChoice: false, innerChoice: C, notnil: notNilRef)
reject TNestedChoices(outerChoice: false, innerChoice: C, notnil: nil)

# Tests involving generics and sequences:
#
block:
  # This test aims to show that it's possible to instantiate and
  # use a sequence with a requiresInit type:

  var legalSeq: seq[IllegalToConstruct]
  legalSeq.add IllegalToConstruct(x: "one")
  var two = IllegalToConstruct(x: "two")
  legalSeq.add two
  var one = legalSeq[0]
  var twoAgain = legalSeq.pop

  #[# produces only warning:
  # It's not possible to tell the sequence to create elements
  # for us though:
  reject:
    var illegalSeq = newSeq[IllegalToConstruct](10)
  #]#

  #[# produces only warning:
  reject:
    var illegalSeq: seq[IllegalToConstruct]
    newSeq(illegalSeq, 10)
  #]#

  #[# produces only warning:
  reject:
    var illegalSeq: seq[IllegalToConstruct]
    illegalSeq.setLen 10
  #]#

  # You can still use newSeqOfCap to write efficient code:
  var anotherLegalSequence = newSeqOfCap[IllegalToConstruct](10)
  for i in 0..9:
    anotherLegalSequence.add IllegalToConstruct(x: "x")

type DefaultConstructible[yesOrNo: static[bool]] = object
  when yesOrNo:
    x: string
  else:
    x: cstring not nil

block:
  # Constructability may also depend on the generic parameters of the type:
  accept:
    var a: DefaultConstructible[true]
    var b = DefaultConstructible[true]()
    var c = DefaultConstructible[true](x: "test")
    var d = DefaultConstructible[false](x: "test")

  reject:
    var y: DefaultConstructible[false]

  reject:
    var y = DefaultConstructible[false]()

block:
  type
    Hash = int

    HashTableSlotType = enum
      Free    = Hash(0)
      Deleted = Hash(1)
      HasKey  = Hash(2)

    KeyValuePair[A, B] = object
      key: A
      case hash: HashTableSlotType
      of Free, Deleted:
        discard
      else:
        value: B

  # The above KeyValuePair is an interesting type because it
  # may become unconstructible depending on the generic parameters:
  accept KeyValuePair[int, string](hash: Deleted)
  accept KeyValuePair[int, IllegalToConstruct](hash: Deleted)

  accept KeyValuePair[int, string](hash: HasKey)
  reject KeyValuePair[int, IllegalToConstruct](hash: HasKey)

  # Since all the above variations don't have a non-constructible
  # field in the default branch of the case object, we can construct
  # such values:
  accept KeyValuePair[int, string]()
  accept KeyValuePair[int, IllegalToConstruct]()
  accept KeyValuePair[DefaultConstructible[true], string]()
  accept KeyValuePair[DefaultConstructible[true], IllegalToConstruct]()

  var a: KeyValuePair[int, string]
  var b: KeyValuePair[int, IllegalToConstruct]
  var c: KeyValuePair[DefaultConstructible[true], string]
  var d: KeyValuePair[DefaultConstructible[true], IllegalToConstruct]
  var s1 = newSeq[KeyValuePair[int, IllegalToConstruct]](10)
  var s2 = newSeq[KeyValuePair[DefaultConstructible[true], IllegalToConstruct]](10)

  # But let's put the non-constructible values as keys:
  reject KeyValuePair[IllegalToConstruct, int](hash: Deleted)
  reject KeyValuePair[IllegalToConstruct, int]()

  type IllegalPair = KeyValuePair[DefaultConstructible[false], string]

  reject:
    var x: IllegalPair

  #[# produces only warning:
  reject:
    var s = newSeq[IllegalPair](10)
  #]#

# Specific issues:
#
block:
  # https://github.com/nim-lang/Nim/issues/11428
  type
    Enum = enum A, B, C
    Thing = object
      case kind: Enum
      of A: discard
      of B: s: string
      of C: r: range[1..1] # DateTime

  # Fine to not initialize 'r' because this is implicitly initialized and known to be branch 'A'.
  var x = Thing()
  discard x

block:
  # https://github.com/nim-lang/Nim/issues/4907
  type
    Foo = ref object
    Bar = object

    Thing[A, B] = ref object
      a: A not nil
      b: ref B
      c: ref B not nil

  proc allocNotNil(T: typedesc): T not nil =
    new result

  proc mutateThing(t: var Thing[Foo, Bar]) =
    let fooNotNil = allocNotNil(Foo)
    var foo: Foo

    let barNotNil = allocNotNil(ref Bar)
    var bar: ref Bar

    t.a = fooNotNil
    t.b = bar
    t.b = barNotNil
    t.c = barNotNil

    reject:
      t.a = foo

    reject:
      t.c = bar

  var thing = Thing[Foo, Bar](a: allocNotNil(Foo),
                              b: allocNotNil(ref Bar),
                              c: allocNotNil(ref Bar))
  mutateThing thing