mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 08:54:53 +00:00
440 lines
13 KiB
Nim
440 lines
13 KiB
Nim
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
|
|
|