123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- 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
|