summary refs log tree commit diff stats
path: root/tests/constructors
diff options
context:
space:
mode:
Diffstat (limited to 'tests/constructors')
-rw-r--r--tests/constructors/a.nim2
-rw-r--r--tests/constructors/b.nim2
-rw-r--r--tests/constructors/t18990.nim3
-rw-r--r--tests/constructors/t5965_1.nim10
-rw-r--r--tests/constructors/t5965_2.nim10
-rw-r--r--tests/constructors/tconstr1.nim28
-rw-r--r--tests/constructors/tconstr2.nim22
-rw-r--r--tests/constructors/tinvalid_construction.nim439
8 files changed, 516 insertions, 0 deletions
diff --git a/tests/constructors/a.nim b/tests/constructors/a.nim
new file mode 100644
index 000000000..03788fc57
--- /dev/null
+++ b/tests/constructors/a.nim
@@ -0,0 +1,2 @@
+type A* = object
+  a: uint8
\ No newline at end of file
diff --git a/tests/constructors/b.nim b/tests/constructors/b.nim
new file mode 100644
index 000000000..437dd0550
--- /dev/null
+++ b/tests/constructors/b.nim
@@ -0,0 +1,2 @@
+type B* = object
+proc A*(a, b: float): B = discard
\ No newline at end of file
diff --git a/tests/constructors/t18990.nim b/tests/constructors/t18990.nim
new file mode 100644
index 000000000..2f60f3c2c
--- /dev/null
+++ b/tests/constructors/t18990.nim
@@ -0,0 +1,3 @@
+import a, b
+discard A(1f, 1f) # works
+proc x(b = A(1f, 1f)) = discard # doesn't work
\ No newline at end of file
diff --git a/tests/constructors/t5965_1.nim b/tests/constructors/t5965_1.nim
new file mode 100644
index 000000000..abf07b21c
--- /dev/null
+++ b/tests/constructors/t5965_1.nim
@@ -0,0 +1,10 @@
+discard """
+  errormsg: "incorrect object construction syntax"
+  file: "t5965_1.nim"
+  line: 10
+"""
+
+type Foo = object
+  a, b, c: int
+
+discard Foo(a: 1, 2)
diff --git a/tests/constructors/t5965_2.nim b/tests/constructors/t5965_2.nim
new file mode 100644
index 000000000..e04f1b715
--- /dev/null
+++ b/tests/constructors/t5965_2.nim
@@ -0,0 +1,10 @@
+discard """
+  errormsg: "incorrect object construction syntax"
+  file: "t5965_2.nim"
+  line: 10
+"""
+
+type Foo = object
+  a: int
+
+discard Foo(a: 1, 2)
diff --git a/tests/constructors/tconstr1.nim b/tests/constructors/tconstr1.nim
new file mode 100644
index 000000000..a169bf453
--- /dev/null
+++ b/tests/constructors/tconstr1.nim
@@ -0,0 +1,28 @@
+discard """
+  errormsg: "type mismatch"
+  file: "tconstr1.nim"
+  line: 25
+"""
+# Test array, record constructors
+
+type
+  TComplexRecord = tuple[
+    s: string,
+    x, y: int,
+    z: float,
+    chars: set[char]]
+
+proc testSem =
+  var
+    things: array[0..1, TComplexRecord] = [
+      (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}),
+      (s: "hi", x: 69, y: 45, z: 1.0, chars: {'a', 'b', 'c'})]
+  write(stdout, things[0].x)
+
+const
+  things: array[0..1, TComplexRecord] = [
+    (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}),
+    (s: "hi", x: 69, y: 45, z: 1.0)] #ERROR
+  otherThings = [  # the same
+    (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}),
+    (s: "hi", x: 69, y: 45, z: 1.0, chars: {'a'})]
diff --git a/tests/constructors/tconstr2.nim b/tests/constructors/tconstr2.nim
new file mode 100644
index 000000000..2557d7db9
--- /dev/null
+++ b/tests/constructors/tconstr2.nim
@@ -0,0 +1,22 @@
+discard """
+  output: "69"
+"""
+# Test array, record constructors
+
+type
+  TComplexRecord = tuple[
+    s: string,
+    x, y: int,
+    z: float,
+    chars: set[char]]
+
+const
+  things: array[0..1, TComplexRecord] = [
+    (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}),
+    (s: "hi", x: 69, y: 45, z: 1.0, chars: {})]
+  otherThings = [  # the same
+    (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}),
+    (s: "hi", x: 69, y: 45, z: 1.0, chars: {'a'})]
+
+writeLine(stdout, things[0].x)
+#OUT 69
diff --git a/tests/constructors/tinvalid_construction.nim b/tests/constructors/tinvalid_construction.nim
new file mode 100644
index 000000000..4b372d68a
--- /dev/null
+++ b/tests/constructors/tinvalid_construction.nim
@@ -0,0 +1,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
+