tinvalid_construction.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. template accept(x) =
  2. static: assert compiles(x)
  3. template reject(x) =
  4. static: assert(not compiles(x))
  5. {.experimental: "notnil".}
  6. type
  7. TRefObj = ref object
  8. x: int
  9. IllegalToConstruct = object
  10. x: cstring not nil
  11. THasNotNils = object of RootObj
  12. a: TRefObj not nil
  13. b: TRefObj not nil
  14. c: TRefObj
  15. THasNotNilsRef = ref THasNotNils
  16. TRefObjNotNil = TRefObj not nil
  17. TChoice = enum A, B, C, D, E, F
  18. TBaseHasNotNils = object of THasNotNils
  19. case choice: TChoice
  20. of A:
  21. moreNotNils: THasNotNils
  22. of B:
  23. indirectNotNils: ref THasNotNils
  24. else:
  25. discard
  26. PartialRequiresInit = object
  27. a {.requiresInit.}: int
  28. b: string
  29. PartialRequiresInitRef = ref PartialRequiresInit
  30. FullRequiresInit {.requiresInit.} = object
  31. a: int
  32. b: int
  33. FullRequiresInitRef = ref FullRequiresInit
  34. FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils
  35. e: int
  36. d: int
  37. TObj = object
  38. case choice: TChoice
  39. of A:
  40. a: int
  41. of B, C:
  42. bc: int
  43. of D:
  44. d: TRefObj
  45. of E:
  46. e1: TRefObj
  47. e2: int
  48. else:
  49. f: string
  50. TNestedChoices = object
  51. case outerChoice: bool
  52. of true:
  53. truthy: int
  54. else:
  55. case innerChoice: TChoice
  56. of A:
  57. a: int
  58. of B:
  59. b: int
  60. else:
  61. notnil: TRefObj not nil
  62. var x = D
  63. var nilRef: TRefObj
  64. let notNilRef = TRefObjNotNil(x: 20)
  65. proc makeHasNotNils: ref THasNotNils =
  66. (ref THasNotNils)(a: TRefObj(x: 10),
  67. b: TRefObj(x: 20))
  68. proc userDefinedDefault(T: typedesc): T =
  69. # We'll use that to make sure the user cannot cheat
  70. # with constructing requiresInit types
  71. discard
  72. proc genericDefault(T: typedesc): T =
  73. result = default(T)
  74. reject IllegalToConstruct()
  75. reject:
  76. var x: IllegalToConstruct
  77. accept TObj()
  78. accept TObj(choice: A)
  79. reject TObj(choice: A, bc: 10) # bc is in the wrong branch
  80. accept TObj(choice: B, bc: 20)
  81. reject TObj(a: 10) # branch selected without providing discriminator
  82. reject TObj(choice: x, a: 10) # the discrimantor must be a compile-time value when a branch is selected
  83. accept TObj(choice: x) # it's OK to use run-time value when a branch is not selected
  84. accept TObj(choice: F, f: "") # match an else clause
  85. reject TObj(f: "") # the discriminator must still be provided for an else clause
  86. reject TObj(a: 10, f: "") # conflicting fields
  87. accept TObj(choice: E, e1: TRefObj(x: 10), e2: 10)
  88. accept THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
  89. reject THasNotNils(a: notNilRef, b: nilRef, c: nilRef) # `b` shouldn't be nil
  90. reject THasNotNils(b: notNilRef, c: notNilRef) # there is a missing not nil field
  91. reject THasNotNils() # again, missing fields
  92. accept THasNotNils(a: notNilRef, b: notNilRef) # it's OK to omit a non-mandatory field
  93. # produces only warning: reject default(THasNotNils)
  94. # produces only warning: reject userDefinedDefault(THasNotNils)
  95. # produces only warning: reject default(TRefObjNotNil)
  96. # produces only warning: reject userDefinedDefault(TRefObjNotNil)
  97. # produces only warning: reject genericDefault(TRefObjNotNil)
  98. # missing not nils in base
  99. reject TBaseHasNotNils()
  100. # produces only warning: reject default(TBaseHasNotNils)
  101. # produces only warning: reject userDefinedDefault(TBaseHasNotNils)
  102. # produces only warning: reject genericDefault(TBaseHasNotNils)
  103. # once you take care of them, it's ok
  104. accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: D)
  105. # this one is tricky!
  106. # it has to be rejected, because choice gets value A by default (0) and this means
  107. # that the THasNotNils field will be active (and it will demand more initialized fields).
  108. reject TBaseHasNotNils(a: notNilRef, b: notNilRef)
  109. # you can select a branch without mandatory fields
  110. accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B)
  111. accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: nil)
  112. # but once you select a branch with mandatory fields, you must specify them
  113. reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A)
  114. reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, indirectNotNils: nil)
  115. reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, moreNotNils: THasNotNils())
  116. accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: A, moreNotNils: THasNotNils(a: notNilRef, b: notNilRef))
  117. # all rules apply to sub-objects as well
  118. accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: makeHasNotNils())
  119. reject TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef())
  120. accept TBaseHasNotNils(a: notNilRef, b: notNilRef, choice: B, indirectNotNils: THasNotNilsRef(a: notNilRef, b: notNilRef))
  121. # Accept only instances where the `a` field is present
  122. accept PartialRequiresInit(a: 10, b: "x")
  123. accept PartialRequiresInit(a: 20)
  124. reject PartialRequiresInit(b: "x")
  125. reject PartialRequiresInit()
  126. accept PartialRequiresInitRef(a: 10, b: "x")
  127. accept PartialRequiresInitRef(a: 20)
  128. reject PartialRequiresInitRef(b: "x")
  129. reject PartialRequiresInitRef()
  130. accept((ref PartialRequiresInit)(a: 10, b: "x"))
  131. accept((ref PartialRequiresInit)(a: 20))
  132. reject((ref PartialRequiresInit)(b: "x"))
  133. reject((ref PartialRequiresInit)())
  134. # produces only warning: reject default(PartialRequiresInit)
  135. # produces only warning: reject userDefinedDefault(PartialRequiresInit)
  136. reject:
  137. var obj: PartialRequiresInit
  138. accept FullRequiresInit(a: 10, b: 20)
  139. reject FullRequiresInit(a: 10)
  140. reject FullRequiresInit(b: 20)
  141. reject FullRequiresInit()
  142. accept FullRequiresInitRef(a: 10, b: 20)
  143. reject FullRequiresInitRef(a: 10)
  144. reject FullRequiresInitRef(b: 20)
  145. reject FullRequiresInitRef()
  146. accept((ref FullRequiresInit)(a: 10, b: 20))
  147. reject((ref FullRequiresInit)(a: 10))
  148. reject((ref FullRequiresInit)(b: 20))
  149. reject((ref FullRequiresInit)())
  150. # produces only warning: reject default(FullRequiresInit)
  151. # produces only warning: reject userDefinedDefault(FullRequiresInit)
  152. reject:
  153. var obj: FullRequiresInit
  154. accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: notNilRef, e: 10, d: 20)
  155. accept FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10, d: 20)
  156. reject FullRequiresInitWithParent(a: notNilRef, b: nil, c: nil, e: 10, d: 20) # b should not be nil
  157. reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20) # c should not be missing
  158. reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10) # d should not be missing
  159. reject FullRequiresInitWithParent()
  160. # produces only warning: reject default(FullRequiresInitWithParent)
  161. # produces only warning: reject userDefinedDefault(FullRequiresInitWithParent)
  162. reject:
  163. var obj: FullRequiresInitWithParent
  164. # this will be accepted, because the false outer branch will be taken and the inner A branch
  165. accept TNestedChoices()
  166. accept default(TNestedChoices)
  167. accept:
  168. var obj: TNestedChoices
  169. #[# produces only warning:
  170. reject:
  171. # This proc is illegal, because it tries to produce
  172. # a default object of a type that requires initialization:
  173. proc defaultHasNotNils: THasNotNils =
  174. discard
  175. #]#
  176. #[# produces only warning:
  177. reject:
  178. # You cannot cheat by using the result variable to specify
  179. # only some of the fields
  180. proc invalidPartialTHasNotNils: THasNotNils =
  181. result.c = nilRef
  182. #]#
  183. #[# produces only warning:
  184. reject:
  185. # The same applies for requiresInit types
  186. proc invalidPartialRequiersInit: PartialRequiresInit =
  187. result.b = "x"
  188. #]#
  189. #[# produces only warning:
  190. # All code paths must return a value when the result requires initialization:
  191. reject:
  192. proc ifWithoutAnElse: THasNotNils =
  193. if stdin.readLine == "":
  194. return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
  195. #]#
  196. accept:
  197. # All code paths must return a value when the result requires initialization:
  198. proc wellFormedIf: THasNotNils =
  199. if stdin.readLine == "":
  200. return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
  201. else:
  202. return THasNotNIls(a: notNilRef, b: notNilRef)
  203. #[# produces only warning:
  204. reject:
  205. proc caseWithoutAllCasesCovered: FullRequiresInit =
  206. # Please note that these is no else branch here:
  207. case stdin.readLine
  208. of "x":
  209. return FullRequiresInit(a: 10, b: 20)
  210. of "y":
  211. return FullRequiresInit(a: 30, b: 40)
  212. #]#
  213. accept:
  214. proc wellFormedCase: FullRequiresInit =
  215. case stdin.readLine
  216. of "x":
  217. result = FullRequiresInit(a: 10, b: 20)
  218. else:
  219. # Mixing result and return is fine:
  220. return FullRequiresInit(a: 30, b: 40)
  221. # but if we supply a run-time value for the inner branch, the compiler won't be able to prove
  222. # that the notnil field was initialized
  223. reject TNestedChoices(outerChoice: false, innerChoice: x) # XXX: The error message is not very good here
  224. reject TNestedChoices(outerChoice: true, innerChoice: A) # XXX: The error message is not very good here
  225. accept TNestedChoices(outerChoice: false, innerChoice: B)
  226. reject TNestedChoices(outerChoice: false, innerChoice: C)
  227. accept TNestedChoices(outerChoice: false, innerChoice: C, notnil: notNilRef)
  228. reject TNestedChoices(outerChoice: false, innerChoice: C, notnil: nil)
  229. # Tests involving generics and sequences:
  230. #
  231. block:
  232. # This test aims to show that it's possible to instantiate and
  233. # use a sequence with a requiresInit type:
  234. var legalSeq: seq[IllegalToConstruct]
  235. legalSeq.add IllegalToConstruct(x: "one")
  236. var two = IllegalToConstruct(x: "two")
  237. legalSeq.add two
  238. var one = legalSeq[0]
  239. var twoAgain = legalSeq.pop
  240. #[# produces only warning:
  241. # It's not possible to tell the sequence to create elements
  242. # for us though:
  243. reject:
  244. var illegalSeq = newSeq[IllegalToConstruct](10)
  245. #]#
  246. #[# produces only warning:
  247. reject:
  248. var illegalSeq: seq[IllegalToConstruct]
  249. newSeq(illegalSeq, 10)
  250. #]#
  251. #[# produces only warning:
  252. reject:
  253. var illegalSeq: seq[IllegalToConstruct]
  254. illegalSeq.setLen 10
  255. #]#
  256. # You can still use newSeqOfCap to write efficient code:
  257. var anotherLegalSequence = newSeqOfCap[IllegalToConstruct](10)
  258. for i in 0..9:
  259. anotherLegalSequence.add IllegalToConstruct(x: "x")
  260. type DefaultConstructible[yesOrNo: static[bool]] = object
  261. when yesOrNo:
  262. x: string
  263. else:
  264. x: cstring not nil
  265. block:
  266. # Constructability may also depend on the generic parameters of the type:
  267. accept:
  268. var a: DefaultConstructible[true]
  269. var b = DefaultConstructible[true]()
  270. var c = DefaultConstructible[true](x: "test")
  271. var d = DefaultConstructible[false](x: "test")
  272. reject:
  273. var y: DefaultConstructible[false]
  274. reject:
  275. var y = DefaultConstructible[false]()
  276. block:
  277. type
  278. Hash = int
  279. HashTableSlotType = enum
  280. Free = Hash(0)
  281. Deleted = Hash(1)
  282. HasKey = Hash(2)
  283. KeyValuePair[A, B] = object
  284. key: A
  285. case hash: HashTableSlotType
  286. of Free, Deleted:
  287. discard
  288. else:
  289. value: B
  290. # The above KeyValuePair is an interesting type because it
  291. # may become unconstructible depending on the generic parameters:
  292. accept KeyValuePair[int, string](hash: Deleted)
  293. accept KeyValuePair[int, IllegalToConstruct](hash: Deleted)
  294. accept KeyValuePair[int, string](hash: HasKey)
  295. reject KeyValuePair[int, IllegalToConstruct](hash: HasKey)
  296. # Since all the above variations don't have a non-constructible
  297. # field in the default branch of the case object, we can construct
  298. # such values:
  299. accept KeyValuePair[int, string]()
  300. accept KeyValuePair[int, IllegalToConstruct]()
  301. accept KeyValuePair[DefaultConstructible[true], string]()
  302. accept KeyValuePair[DefaultConstructible[true], IllegalToConstruct]()
  303. var a: KeyValuePair[int, string]
  304. var b: KeyValuePair[int, IllegalToConstruct]
  305. var c: KeyValuePair[DefaultConstructible[true], string]
  306. var d: KeyValuePair[DefaultConstructible[true], IllegalToConstruct]
  307. var s1 = newSeq[KeyValuePair[int, IllegalToConstruct]](10)
  308. var s2 = newSeq[KeyValuePair[DefaultConstructible[true], IllegalToConstruct]](10)
  309. # But let's put the non-constructible values as keys:
  310. reject KeyValuePair[IllegalToConstruct, int](hash: Deleted)
  311. reject KeyValuePair[IllegalToConstruct, int]()
  312. type IllegalPair = KeyValuePair[DefaultConstructible[false], string]
  313. reject:
  314. var x: IllegalPair
  315. #[# produces only warning:
  316. reject:
  317. var s = newSeq[IllegalPair](10)
  318. #]#
  319. # Specific issues:
  320. #
  321. block:
  322. # https://github.com/nim-lang/Nim/issues/11428
  323. type
  324. Enum = enum A, B, C
  325. Thing = object
  326. case kind: Enum
  327. of A: discard
  328. of B: s: string
  329. of C: r: range[1..1] # DateTime
  330. # Fine to not initialize 'r' because this is implicitly initialized and known to be branch 'A'.
  331. var x = Thing()
  332. discard x
  333. block:
  334. # https://github.com/nim-lang/Nim/issues/4907
  335. type
  336. Foo = ref object
  337. Bar = object
  338. Thing[A, B] = ref object
  339. a: A not nil
  340. b: ref B
  341. c: ref B not nil
  342. proc allocNotNil(T: typedesc): T not nil =
  343. new result
  344. proc mutateThing(t: var Thing[Foo, Bar]) =
  345. let fooNotNil = allocNotNil(Foo)
  346. var foo: Foo
  347. let barNotNil = allocNotNil(ref Bar)
  348. var bar: ref Bar
  349. t.a = fooNotNil
  350. t.b = bar
  351. t.b = barNotNil
  352. t.c = barNotNil
  353. reject:
  354. t.a = foo
  355. reject:
  356. t.c = bar
  357. var thing = Thing[Foo, Bar](a: allocNotNil(Foo),
  358. b: allocNotNil(ref Bar),
  359. c: allocNotNil(ref Bar))
  360. mutateThing thing