summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2022-04-07 14:38:01 -0700
committerGitHub <noreply@github.com>2022-04-07 17:38:01 -0400
commite78ef57c93d66da483e0482ce0907dfe16dc8d27 (patch)
tree32ba3900b479bc2e046aca563df99c1be77a5d9d
parent1807de38e52f45c2fb88dac9b99b47729b12ebae (diff)
downloadNim-e78ef57c93d66da483e0482ce0907dfe16dc8d27.tar.gz
typetraits: add toSigned, toUnsigned (#18445)
* typetraits: add toSigned, toUnsigned

* improve and add tests

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
Co-authored-by: flywind <xzsflywind@gmail.com>
-rw-r--r--lib/pure/bitops.nim26
-rw-r--r--lib/pure/typetraits.nim38
-rw-r--r--lib/std/private/bitops_utils.nim15
-rw-r--r--lib/system/countbits_impl.nim6
-rw-r--r--tests/metatype/ttypetraits.nim15
-rw-r--r--tests/stdlib/tbitops_utils.nim14
-rw-r--r--tests/stdlib/ttypetraits.nim4
7 files changed, 85 insertions, 33 deletions
diff --git a/lib/pure/bitops.nim b/lib/pure/bitops.nim
index 1b3838621..a518c25d2 100644
--- a/lib/pure/bitops.nim
+++ b/lib/pure/bitops.nim
@@ -27,7 +27,7 @@
 
 import macros
 import std/private/since
-from std/private/bitops_utils import forwardImpl, toUnsigned
+from std/private/bitops_utils import forwardImpl, castToUnsigned
 
 func bitnot*[T: SomeInteger](x: T): T {.magic: "BitnotI".}
   ## Computes the `bitwise complement` of the integer `x`.
@@ -72,7 +72,7 @@ func bitsliced*[T: SomeInteger](v: T; slice: Slice[int]): T {.inline, since: (1,
 
   let
     upmost = sizeof(T) * 8 - 1
-    uv     = when v is SomeUnsignedInt: v else: v.toUnsigned
+    uv     = v.castToUnsigned
   (uv shl (upmost - slice.b) shr (upmost - slice.b + slice.a)).T
 
 proc bitslice*[T: SomeInteger](v: var T; slice: Slice[int]) {.inline, since: (1, 3).} =
@@ -84,7 +84,7 @@ proc bitslice*[T: SomeInteger](v: var T; slice: Slice[int]) {.inline, since: (1,
 
   let
     upmost = sizeof(T) * 8 - 1
-    uv     = when v is SomeUnsignedInt: v else: v.toUnsigned
+    uv     = v.castToUnsigned
   v = (uv shl (upmost - slice.b) shr (upmost - slice.b + slice.a)).T
 
 func toMask*[T: SomeInteger](slice: Slice[int]): T {.inline, since: (1, 3).} =
@@ -95,10 +95,7 @@ func toMask*[T: SomeInteger](slice: Slice[int]): T {.inline, since: (1, 3).} =
 
   let
     upmost = sizeof(T) * 8 - 1
-    bitmask = when T is SomeUnsignedInt:
-                bitnot(0.T)
-              else:
-                bitnot(0.T).toUnsigned
+    bitmask = bitnot(0.T).castToUnsigned
   (bitmask shl (upmost - slice.b + slice.a) shr (upmost - slice.b)).T
 
 proc masked*[T: SomeInteger](v, mask :T): T {.inline, since: (1, 3).} =
@@ -505,8 +502,7 @@ func parityBits*(x: SomeInteger): int {.inline.} =
 
   # Can be used a base if creating ASM version.
   # https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
-  when x is SomeSignedInt:
-    let x = x.toUnsigned
+  let x = x.castToUnsigned
   when nimvm:
     result = forwardImpl(parityImpl, x)
   else:
@@ -529,8 +525,7 @@ func firstSetBit*(x: SomeInteger): int {.inline.} =
     doAssert firstSetBit(0b0000_1111'u8) == 1
 
   # GCC builtin 'builtin_ffs' already handle zero input.
-  when x is SomeSignedInt:
-    let x = x.toUnsigned
+  let x = x.castToUnsigned
   when nimvm:
     when noUndefined:
       if x == 0:
@@ -572,8 +567,7 @@ func fastLog2*(x: SomeInteger): int {.inline.} =
     doAssert fastLog2(0b0000_1000'u8) == 3
     doAssert fastLog2(0b0000_1111'u8) == 3
 
-  when x is SomeSignedInt:
-    let x = x.toUnsigned
+  let x = x.castToUnsigned
   when noUndefined:
     if x == 0:
       return -1
@@ -615,8 +609,7 @@ func countLeadingZeroBits*(x: SomeInteger): int {.inline.} =
     doAssert countLeadingZeroBits(0b0000_1000'u8) == 4
     doAssert countLeadingZeroBits(0b0000_1111'u8) == 4
 
-  when x is SomeSignedInt:
-    let x = x.toUnsigned
+  let x = x.castToUnsigned
   when noUndefined:
     if x == 0:
       return 0
@@ -644,8 +637,7 @@ func countTrailingZeroBits*(x: SomeInteger): int {.inline.} =
     doAssert countTrailingZeroBits(0b0000_1000'u8) == 3
     doAssert countTrailingZeroBits(0b0000_1111'u8) == 0
 
-  when x is SomeSignedInt:
-    let x = x.toUnsigned
+  let x = x.castToUnsigned
   when noUndefined:
     if x == 0:
       return 0
diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim
index 3fc1c7c5c..c20f9e645 100644
--- a/lib/pure/typetraits.nim
+++ b/lib/pure/typetraits.nim
@@ -35,7 +35,7 @@ runnableExamples:
   assert C[float] is HoleyEnum
 
 proc name*(t: typedesc): string {.magic: "TypeTrait".} =
-  ## Returns the name of the given type.
+  ## Returns the name of `t`.
   ##
   ## Alias for `system.\`$\`(t) <dollars.html#$,typedesc>`_ since Nim v0.20.
   runnableExamples:
@@ -43,7 +43,7 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".} =
     doAssert name(seq[string]) == "seq[string]"
 
 proc arity*(t: typedesc): int {.magic: "TypeTrait".} =
-  ## Returns the arity of the given type. This is the number of "type"
+  ## Returns the arity of `t`. This is the number of "type"
   ## components or the number of generic parameters a given type `t` has.
   runnableExamples:
     doAssert arity(int) == 0
@@ -92,8 +92,7 @@ proc stripGenericParams*(t: typedesc): typedesc {.magic: "TypeTrait".} =
     doAssert stripGenericParams(int) is int
 
 proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".}
-  ## This trait returns true if the type `t` is safe to use for
-  ## `copyMem`:idx:.
+  ## Returns true if `t` is safe to use for `copyMem`:idx:.
   ##
   ## Other languages name a type like these `blob`:idx:.
 
@@ -289,9 +288,38 @@ since (1, 1):
 proc hasClosureImpl(n: NimNode): bool = discard "see compiler/vmops.nim"
 
 proc hasClosure*(fn: NimNode): bool {.since: (1, 5, 1).} =
-  ## Return true if the func/proc/etc `fn` has `closure`.
+  ## Returns true if the func/proc/etc `fn` has `closure`.
   ## `fn` has to be a resolved symbol of kind `nnkSym`. This
   ## implies that the macro that calls this proc should accept `typed`
   ## arguments and not `untyped` arguments.
   expectKind fn, nnkSym
   result = hasClosureImpl(fn)
+
+template toUnsigned*(T: typedesc[SomeInteger and not range]): untyped =
+  ## Returns an unsigned type with same bit size as `T`.
+  runnableExamples:
+    assert int8.toUnsigned is uint8
+    assert uint.toUnsigned is uint
+    assert int.toUnsigned is uint
+    # range types are currently unsupported:
+    assert not compiles(toUnsigned(range[0..7]))
+  when T is int8: uint8
+  elif T is int16: uint16
+  elif T is int32: uint32
+  elif T is int64: uint64
+  elif T is int: uint
+  else: T
+
+template toSigned*(T: typedesc[SomeInteger and not range]): untyped =
+  ## Returns a signed type with same bit size as `T`.
+  runnableExamples:
+    assert int8.toSigned is int8
+    assert uint16.toSigned is int16
+    # range types are currently unsupported:
+    assert not compiles(toSigned(range[0..7]))
+  when T is uint8: int8
+  elif T is uint16: int16
+  elif T is uint32: int32
+  elif T is uint64: int64
+  elif T is uint: int
+  else: T
diff --git a/lib/std/private/bitops_utils.nim b/lib/std/private/bitops_utils.nim
index d54977b4e..0b9484416 100644
--- a/lib/std/private/bitops_utils.nim
+++ b/lib/std/private/bitops_utils.nim
@@ -10,8 +10,13 @@ template forwardImpl*(impl, arg) {.dirty.} =
     else:
       impl(x.uint64)
 
-template toUnsigned*(x: int8): uint8 = cast[uint8](x)
-template toUnsigned*(x: int16): uint16 = cast[uint16](x)
-template toUnsigned*(x: int32): uint32 = cast[uint32](x)
-template toUnsigned*(x: int64): uint64 = cast[uint64](x)
-template toUnsigned*(x: int): uint = cast[uint](x)
+# this could also be implemented via:
+# import std/typetraits
+# template castToUnsigned*(x: SomeInteger): auto = cast[toUnsigned(typeof(x))](x)
+
+template castToUnsigned*(x: int8): uint8 = cast[uint8](x)
+template castToUnsigned*(x: int16): uint16 = cast[uint16](x)
+template castToUnsigned*(x: int32): uint32 = cast[uint32](x)
+template castToUnsigned*(x: int64): uint64 = cast[uint64](x)
+template castToUnsigned*(x: int): uint = cast[uint](x)
+template castToUnsigned*[T: SomeUnsignedInt](x: T): T = x
diff --git a/lib/system/countbits_impl.nim b/lib/system/countbits_impl.nim
index 020423e01..34969cb32 100644
--- a/lib/system/countbits_impl.nim
+++ b/lib/system/countbits_impl.nim
@@ -9,8 +9,7 @@
 
 ## Contains the used algorithms for counting bits.
 
-from std/private/bitops_utils import forwardImpl, toUnsigned
-
+from std/private/bitops_utils import forwardImpl, castToUnsigned
 
 const useBuiltins* = not defined(noIntrinsicsBitOpts)
 const noUndefined* = defined(noUndefinedBitOpts)
@@ -65,8 +64,7 @@ func countSetBitsImpl*(x: SomeInteger): int {.inline.} =
   ## Counts the set bits in an integer (also called `Hamming weight`:idx:).
   # TODO: figure out if ICC support _popcnt32/_popcnt64 on platform without POPCNT.
   # like GCC and MSVC
-  when x is SomeSignedInt:
-    let x = x.toUnsigned
+  let x = x.castToUnsigned
   when nimvm:
     result = forwardImpl(countBitsImpl, x)
   else:
diff --git a/tests/metatype/ttypetraits.nim b/tests/metatype/ttypetraits.nim
index 3ff5c5ea6..bfaa23057 100644
--- a/tests/metatype/ttypetraits.nim
+++ b/tests/metatype/ttypetraits.nim
@@ -1,6 +1,21 @@
 import typetraits
 import macros
 
+block: # toUnsigned, toSigned
+  var a1: toSigned(int16)
+  doAssert a1 is int16
+  var a2: toSigned(uint16)
+  doAssert $a2.typeof == "int16"
+  doAssert toSigned(uint32) is int32
+  doAssert uint64.toSigned is int64
+  doAssert int64.toSigned is int64
+  doAssert int64.toUnsigned is uint64
+  doAssert int.toUnsigned is uint
+  doAssert $uint.toUnsigned == "uint"
+  # disallowed for now
+  doAssert not compiles(toUnsigned(range[0..7]))
+  doAssert not compiles(toSigned(range[0..7]))
+
 block: # isNamedTuple
   type Foo1 = (a:1,).type
   type Foo2 = (Field0:1,).type
diff --git a/tests/stdlib/tbitops_utils.nim b/tests/stdlib/tbitops_utils.nim
new file mode 100644
index 000000000..b571baeae
--- /dev/null
+++ b/tests/stdlib/tbitops_utils.nim
@@ -0,0 +1,14 @@
+import std/private/bitops_utils
+
+template chk(a, b) =
+  let a2 = castToUnsigned(a)
+  doAssert a2 == b
+  doAssert type(a2) is type(b)
+  doAssert type(b) is type(a2)
+
+chk 1'i8, 1'u8
+chk -1'i8, 255'u8
+chk 1'u8, 1'u8
+chk 1'u, 1'u
+chk -1, cast[uint](-1)
+chk -1'i64, cast[uint64](-1)
diff --git a/tests/stdlib/ttypetraits.nim b/tests/stdlib/ttypetraits.nim
index de8259ab0..799bcf6e2 100644
--- a/tests/stdlib/ttypetraits.nim
+++ b/tests/stdlib/ttypetraits.nim
@@ -2,9 +2,9 @@ discard """
   targets: "c cpp js"
 """
 
-import std/typetraits
-
+# xxx merge with tests/metatype/ttypetraits.nim
 
+import std/typetraits
 
 macro testClosure(fn: typed, flag: static bool) =
   if flag: