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