summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2021-04-16 05:21:26 -0700
committerGitHub <noreply@github.com>2021-04-16 14:21:26 +0200
commitd19e4310dc16cae2329c55dfa8feb94e0981dc0c (patch)
tree0fd3df253460a37aef246621538e50c351a5a0f3
parent611b88763f8ec88889b14da31ff220cb47789846 (diff)
downloadNim-d19e4310dc16cae2329c55dfa8feb94e0981dc0c.tar.gz
std/hashes: hash(ref|ptr|pointer) + other improvements (#17731)
-rw-r--r--lib/pure/hashes.nim72
-rw-r--r--testament/lib/stdtest/testutils.nim4
-rw-r--r--tests/collections/ttables.nim14
-rw-r--r--tests/stdlib/thashes.nim22
-rw-r--r--tests/stdlib/tstrutils.nim6
5 files changed, 89 insertions, 29 deletions
diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim
index 3339adba2..2ef0e2454 100644
--- a/lib/pure/hashes.nim
+++ b/lib/pure/hashes.nim
@@ -182,7 +182,7 @@ proc hashData*(data: pointer, size: int): Hash =
   var h: Hash = 0
   when defined(js):
     var p: cstring
-    asm """`p` = `Data`;"""
+    asm """`p` = `Data`"""
   else:
     var p = cast[cstring](data)
   var i = 0
@@ -193,12 +193,22 @@ proc hashData*(data: pointer, size: int): Hash =
     dec(s)
   result = !$h
 
+proc hashIdentity*[T: Ordinal|enum](x: T): Hash {.inline, since: (1, 3).} =
+  ## The identity hash, i.e. `hashIdentity(x) = x`.
+  cast[Hash](ord(x))
+
+when defined(nimIntHash1):
+  proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
+    ## Efficient hashing of integers.
+    cast[Hash](ord(x))
+else:
+  proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
+    ## Efficient hashing of integers.
+    hashWangYi1(uint64(ord(x)))
+
 when defined(js):
   var objectID = 0
-
-proc hash*(x: pointer): Hash {.inline.} =
-  ## Efficient hashing of pointers.
-  when defined(js):
+  proc getObjectId(x: pointer): int =
     asm """
       if (typeof `x` == "object") {
         if ("_NimID" in `x`)
@@ -209,29 +219,46 @@ proc hash*(x: pointer): Hash {.inline.} =
         }
       }
     """
+
+proc hash*(x: pointer): Hash {.inline.} =
+  ## Efficient `hash` overload.
+  when defined(js):
+    let y = getObjectId(x)
   else:
-    result = cast[Hash](cast[uint](x) shr 3) # skip the alignment
+    let y = cast[int](x)
+  hash(y) # consistent with code expecting scrambled hashes depending on `nimIntHash1`.
+
+proc hash*[T](x: ref[T] | ptr[T]): Hash {.inline.} =
+  ## Efficient `hash` overload.
+  runnableExamples:
+    var a: array[10, uint8]
+    assert a[0].addr.hash != a[1].addr.hash
+    assert cast[pointer](a[0].addr).hash == a[0].addr.hash
+  runnableExamples:
+    type A = ref object
+      x: int
+    let a = A(x: 3)
+    let ha = a.hash
+    assert ha != A(x: 3).hash # A(x: 3) is a different ref object from `a`.
+    a.x = 4
+    assert ha == a.hash # the hash only depends on the address
+  runnableExamples:
+    # you can overload `hash` if you want to customize semantics
+    type A[T] = ref object
+      x, y: T
+    proc hash(a: A): Hash = hash(a.x)
+    assert A[int](x: 3, y: 4).hash == A[int](x: 3, y: 5).hash
+  # xxx pending bug #17733, merge as `proc hash*(pointer | ref | ptr): Hash`
+  # or `proc hash*[T: ref | ptr](x: T): Hash`
+  hash(cast[pointer](x))
 
 proc hash*[T: proc](x: T): Hash {.inline.} =
   ## Efficient hashing of proc vars. Closures are supported too.
   when T is "closure":
-    result = hash(rawProc(x)) !& hash(rawEnv(x))
+    result = hash((rawProc(x), rawEnv(x)))
   else:
     result = hash(pointer(x))
 
-proc hashIdentity*[T: Ordinal|enum](x: T): Hash {.inline, since: (1, 3).} =
-  ## The identity hash, i.e. `hashIdentity(x) = x`.
-  cast[Hash](ord(x))
-
-when defined(nimIntHash1):
-  proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
-    ## Efficient hashing of integers.
-    cast[Hash](ord(x))
-else:
-  proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
-    ## Efficient hashing of integers.
-    hashWangYi1(uint64(ord(x)))
-
 proc hash*(x: float): Hash {.inline.} =
   ## Efficient hashing of floats.
   let y = x + 0.0 # for denormalization
@@ -484,10 +511,9 @@ proc hashIgnoreCase*(sBuf: string, sPos, ePos: int): Hash =
     h = h !& ord(c)
   result = !$h
 
-
 proc hash*[T: tuple | object](x: T): Hash =
-  ## Efficient hashing of tuples and objects.
-  ## There must be a `hash` proc defined for each of the field types.
+  ## Efficient `hash` overload.
+  ## `hash` must be defined for each component of `x`.
   runnableExamples:
     type Obj = object
       x: int
diff --git a/testament/lib/stdtest/testutils.nim b/testament/lib/stdtest/testutils.nim
index abffff24c..35423de17 100644
--- a/testament/lib/stdtest/testutils.nim
+++ b/testament/lib/stdtest/testutils.nim
@@ -85,3 +85,7 @@ template accept*(a) =
 
 template reject*(a) =
   doAssert not compiles(a)
+
+template disableVm*(body) =
+  when nimvm: discard
+  else: body
diff --git a/tests/collections/ttables.nim b/tests/collections/ttables.nim
index 61197e9f0..c2864b75f 100644
--- a/tests/collections/ttables.nim
+++ b/tests/collections/ttables.nim
@@ -7,7 +7,11 @@ And we get here
 3
 '''
 joinable: false
+targets: "c cpp js"
 """
+
+# xxx wrap in a template to test in VM, see https://github.com/timotheecour/Nim/issues/534#issuecomment-769565033
+
 import hashes, sequtils, tables, algorithm
 
 proc sortedPairs[T](t: T): auto = toSeq(t.pairs).sorted
@@ -444,3 +448,13 @@ block emptyOrdered:
   var t2: OrderedTable[int, string]
   doAssert t1 == t2
 
+block: # Table[ref, int]
+  type A = ref object
+    x: int
+  var t: OrderedTable[A, int]
+  let a1 = A(x: 3)
+  let a2 = A(x: 3)
+  t[a1] = 10
+  t[a2] = 11
+  doAssert t[a1] == 10
+  doAssert t[a2] == 11
diff --git a/tests/stdlib/thashes.nim b/tests/stdlib/thashes.nim
index 044259f88..66857d3ca 100644
--- a/tests/stdlib/thashes.nim
+++ b/tests/stdlib/thashes.nim
@@ -3,7 +3,7 @@ discard """
 """
 
 import std/hashes
-
+from stdtest/testutils import disableVm, whenVMorJs
 
 when not defined(js) and not defined(cpp):
   block:
@@ -177,5 +177,25 @@ proc main() =
     doAssert hash(Obj5(t: false, x: 1)) == hash(Obj5(t: true, y: 1))
     doAssert hash(Obj5(t: false, x: 1)) != hash(Obj5(t: true, y: 2))
 
+  block: # hash(ref|ptr|pointer)
+    var a: array[10, uint8]
+    # disableVm:
+    whenVMorJs:
+      # pending fix proposed in https://github.com/nim-lang/Nim/issues/15952#issuecomment-786312417
+      discard
+    do:
+      assert a[0].addr.hash != a[1].addr.hash
+      assert cast[pointer](a[0].addr).hash == a[0].addr.hash
+
+  block: # hash(ref)
+    type A = ref object
+      x: int
+    let a = A(x: 3)
+    disableVm: # xxx Error: VM does not support 'cast' from tyRef to tyPointer
+      let ha = a.hash
+      assert ha != A(x: 3).hash # A(x: 3) is a different ref object from `a`.
+      a.x = 4
+      assert ha == a.hash # the hash only depends on the address
+
 static: main()
 main()
diff --git a/tests/stdlib/tstrutils.nim b/tests/stdlib/tstrutils.nim
index a6248d1e3..771dddcaf 100644
--- a/tests/stdlib/tstrutils.nim
+++ b/tests/stdlib/tstrutils.nim
@@ -3,7 +3,7 @@ discard """
 """
 
 import std/strutils
-
+from stdtest/testutils import disableVm
 # xxx each instance of `disableVm` and `when not defined js:` should eventually be fixed
 
 template rejectParse(e) =
@@ -12,10 +12,6 @@ template rejectParse(e) =
     raise newException(AssertionDefect, "This was supposed to fail: $#!" % astToStr(e))
   except ValueError: discard
 
-template disableVm(body) =
-  when nimvm: discard
-  else: body
-
 template main() =
   block: # strip
     doAssert strip("  ha  ") == "ha"