Skip to content

Commit e78ef57

Browse files
timotheecourAraqringabout
authored
typetraits: add toSigned, toUnsigned (#18445)
* typetraits: add toSigned, toUnsigned * improve and add tests Co-authored-by: Andreas Rumpf <[email protected]> Co-authored-by: flywind <[email protected]>
1 parent 1807de3 commit e78ef57

File tree

7 files changed

+85
-33
lines changed

7 files changed

+85
-33
lines changed

lib/pure/bitops.nim

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
import macros
2929
import std/private/since
30-
from std/private/bitops_utils import forwardImpl, toUnsigned
30+
from std/private/bitops_utils import forwardImpl, castToUnsigned
3131

3232
func bitnot*[T: SomeInteger](x: T): T {.magic: "BitnotI".}
3333
## 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,
7272

7373
let
7474
upmost = sizeof(T) * 8 - 1
75-
uv = when v is SomeUnsignedInt: v else: v.toUnsigned
75+
uv = v.castToUnsigned
7676
(uv shl (upmost - slice.b) shr (upmost - slice.b + slice.a)).T
7777

7878
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,
8484

8585
let
8686
upmost = sizeof(T) * 8 - 1
87-
uv = when v is SomeUnsignedInt: v else: v.toUnsigned
87+
uv = v.castToUnsigned
8888
v = (uv shl (upmost - slice.b) shr (upmost - slice.b + slice.a)).T
8989

9090
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).} =
9595

9696
let
9797
upmost = sizeof(T) * 8 - 1
98-
bitmask = when T is SomeUnsignedInt:
99-
bitnot(0.T)
100-
else:
101-
bitnot(0.T).toUnsigned
98+
bitmask = bitnot(0.T).castToUnsigned
10299
(bitmask shl (upmost - slice.b + slice.a) shr (upmost - slice.b)).T
103100

104101
proc masked*[T: SomeInteger](v, mask :T): T {.inline, since: (1, 3).} =
@@ -505,8 +502,7 @@ func parityBits*(x: SomeInteger): int {.inline.} =
505502

506503
# Can be used a base if creating ASM version.
507504
# https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
508-
when x is SomeSignedInt:
509-
let x = x.toUnsigned
505+
let x = x.castToUnsigned
510506
when nimvm:
511507
result = forwardImpl(parityImpl, x)
512508
else:
@@ -529,8 +525,7 @@ func firstSetBit*(x: SomeInteger): int {.inline.} =
529525
doAssert firstSetBit(0b0000_1111'u8) == 1
530526

531527
# GCC builtin 'builtin_ffs' already handle zero input.
532-
when x is SomeSignedInt:
533-
let x = x.toUnsigned
528+
let x = x.castToUnsigned
534529
when nimvm:
535530
when noUndefined:
536531
if x == 0:
@@ -572,8 +567,7 @@ func fastLog2*(x: SomeInteger): int {.inline.} =
572567
doAssert fastLog2(0b0000_1000'u8) == 3
573568
doAssert fastLog2(0b0000_1111'u8) == 3
574569

575-
when x is SomeSignedInt:
576-
let x = x.toUnsigned
570+
let x = x.castToUnsigned
577571
when noUndefined:
578572
if x == 0:
579573
return -1
@@ -615,8 +609,7 @@ func countLeadingZeroBits*(x: SomeInteger): int {.inline.} =
615609
doAssert countLeadingZeroBits(0b0000_1000'u8) == 4
616610
doAssert countLeadingZeroBits(0b0000_1111'u8) == 4
617611

618-
when x is SomeSignedInt:
619-
let x = x.toUnsigned
612+
let x = x.castToUnsigned
620613
when noUndefined:
621614
if x == 0:
622615
return 0
@@ -644,8 +637,7 @@ func countTrailingZeroBits*(x: SomeInteger): int {.inline.} =
644637
doAssert countTrailingZeroBits(0b0000_1000'u8) == 3
645638
doAssert countTrailingZeroBits(0b0000_1111'u8) == 0
646639

647-
when x is SomeSignedInt:
648-
let x = x.toUnsigned
640+
let x = x.castToUnsigned
649641
when noUndefined:
650642
if x == 0:
651643
return 0

lib/pure/typetraits.nim

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ runnableExamples:
3535
assert C[float] is HoleyEnum
3636

3737
proc name*(t: typedesc): string {.magic: "TypeTrait".} =
38-
## Returns the name of the given type.
38+
## Returns the name of `t`.
3939
##
4040
## Alias for `system.\`$\`(t) <dollars.html#$,typedesc>`_ since Nim v0.20.
4141
runnableExamples:
4242
doAssert name(int) == "int"
4343
doAssert name(seq[string]) == "seq[string]"
4444

4545
proc arity*(t: typedesc): int {.magic: "TypeTrait".} =
46-
## Returns the arity of the given type. This is the number of "type"
46+
## Returns the arity of `t`. This is the number of "type"
4747
## components or the number of generic parameters a given type `t` has.
4848
runnableExamples:
4949
doAssert arity(int) == 0
@@ -92,8 +92,7 @@ proc stripGenericParams*(t: typedesc): typedesc {.magic: "TypeTrait".} =
9292
doAssert stripGenericParams(int) is int
9393

9494
proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".}
95-
## This trait returns true if the type `t` is safe to use for
96-
## `copyMem`:idx:.
95+
## Returns true if `t` is safe to use for `copyMem`:idx:.
9796
##
9897
## Other languages name a type like these `blob`:idx:.
9998

@@ -289,9 +288,38 @@ since (1, 1):
289288
proc hasClosureImpl(n: NimNode): bool = discard "see compiler/vmops.nim"
290289

291290
proc hasClosure*(fn: NimNode): bool {.since: (1, 5, 1).} =
292-
## Return true if the func/proc/etc `fn` has `closure`.
291+
## Returns true if the func/proc/etc `fn` has `closure`.
293292
## `fn` has to be a resolved symbol of kind `nnkSym`. This
294293
## implies that the macro that calls this proc should accept `typed`
295294
## arguments and not `untyped` arguments.
296295
expectKind fn, nnkSym
297296
result = hasClosureImpl(fn)
297+
298+
template toUnsigned*(T: typedesc[SomeInteger and not range]): untyped =
299+
## Returns an unsigned type with same bit size as `T`.
300+
runnableExamples:
301+
assert int8.toUnsigned is uint8
302+
assert uint.toUnsigned is uint
303+
assert int.toUnsigned is uint
304+
# range types are currently unsupported:
305+
assert not compiles(toUnsigned(range[0..7]))
306+
when T is int8: uint8
307+
elif T is int16: uint16
308+
elif T is int32: uint32
309+
elif T is int64: uint64
310+
elif T is int: uint
311+
else: T
312+
313+
template toSigned*(T: typedesc[SomeInteger and not range]): untyped =
314+
## Returns a signed type with same bit size as `T`.
315+
runnableExamples:
316+
assert int8.toSigned is int8
317+
assert uint16.toSigned is int16
318+
# range types are currently unsupported:
319+
assert not compiles(toSigned(range[0..7]))
320+
when T is uint8: int8
321+
elif T is uint16: int16
322+
elif T is uint32: int32
323+
elif T is uint64: int64
324+
elif T is uint: int
325+
else: T

lib/std/private/bitops_utils.nim

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ template forwardImpl*(impl, arg) {.dirty.} =
1010
else:
1111
impl(x.uint64)
1212

13-
template toUnsigned*(x: int8): uint8 = cast[uint8](x)
14-
template toUnsigned*(x: int16): uint16 = cast[uint16](x)
15-
template toUnsigned*(x: int32): uint32 = cast[uint32](x)
16-
template toUnsigned*(x: int64): uint64 = cast[uint64](x)
17-
template toUnsigned*(x: int): uint = cast[uint](x)
13+
# this could also be implemented via:
14+
# import std/typetraits
15+
# template castToUnsigned*(x: SomeInteger): auto = cast[toUnsigned(typeof(x))](x)
16+
17+
template castToUnsigned*(x: int8): uint8 = cast[uint8](x)
18+
template castToUnsigned*(x: int16): uint16 = cast[uint16](x)
19+
template castToUnsigned*(x: int32): uint32 = cast[uint32](x)
20+
template castToUnsigned*(x: int64): uint64 = cast[uint64](x)
21+
template castToUnsigned*(x: int): uint = cast[uint](x)
22+
template castToUnsigned*[T: SomeUnsignedInt](x: T): T = x

lib/system/countbits_impl.nim

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99

1010
## Contains the used algorithms for counting bits.
1111

12-
from std/private/bitops_utils import forwardImpl, toUnsigned
13-
12+
from std/private/bitops_utils import forwardImpl, castToUnsigned
1413

1514
const useBuiltins* = not defined(noIntrinsicsBitOpts)
1615
const noUndefined* = defined(noUndefinedBitOpts)
@@ -65,8 +64,7 @@ func countSetBitsImpl*(x: SomeInteger): int {.inline.} =
6564
## Counts the set bits in an integer (also called `Hamming weight`:idx:).
6665
# TODO: figure out if ICC support _popcnt32/_popcnt64 on platform without POPCNT.
6766
# like GCC and MSVC
68-
when x is SomeSignedInt:
69-
let x = x.toUnsigned
67+
let x = x.castToUnsigned
7068
when nimvm:
7169
result = forwardImpl(countBitsImpl, x)
7270
else:

tests/metatype/ttypetraits.nim

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import typetraits
22
import macros
33

4+
block: # toUnsigned, toSigned
5+
var a1: toSigned(int16)
6+
doAssert a1 is int16
7+
var a2: toSigned(uint16)
8+
doAssert $a2.typeof == "int16"
9+
doAssert toSigned(uint32) is int32
10+
doAssert uint64.toSigned is int64
11+
doAssert int64.toSigned is int64
12+
doAssert int64.toUnsigned is uint64
13+
doAssert int.toUnsigned is uint
14+
doAssert $uint.toUnsigned == "uint"
15+
# disallowed for now
16+
doAssert not compiles(toUnsigned(range[0..7]))
17+
doAssert not compiles(toSigned(range[0..7]))
18+
419
block: # isNamedTuple
520
type Foo1 = (a:1,).type
621
type Foo2 = (Field0:1,).type

tests/stdlib/tbitops_utils.nim

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import std/private/bitops_utils
2+
3+
template chk(a, b) =
4+
let a2 = castToUnsigned(a)
5+
doAssert a2 == b
6+
doAssert type(a2) is type(b)
7+
doAssert type(b) is type(a2)
8+
9+
chk 1'i8, 1'u8
10+
chk -1'i8, 255'u8
11+
chk 1'u8, 1'u8
12+
chk 1'u, 1'u
13+
chk -1, cast[uint](-1)
14+
chk -1'i64, cast[uint64](-1)

tests/stdlib/ttypetraits.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ discard """
22
targets: "c cpp js"
33
"""
44

5-
import std/typetraits
6-
5+
# xxx merge with tests/metatype/ttypetraits.nim
76

7+
import std/typetraits
88

99
macro testClosure(fn: typed, flag: static bool) =
1010
if flag:

0 commit comments

Comments
 (0)