Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: typetraits.getTypeId #13305

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions compiler/semmagic.nim
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
result = newIntNode(nkIntLit, operand.len - ord(operand.kind==tyProc))
result.typ = newType(tyInt, nextTypeId c.idgen, context)
result.info = traitCall.info
of "getTypeIdImpl":
var arg = operand
if arg.kind in IntegralTypes - {tyEnum}:
# needed otherwise we could get different ids, see tests
arg = getSysType(c.graph, traitCall[1].info, arg.kind)
result = newStrNode(nkStrLit, $arg.id)
# `id` better than cast[int](arg) so that it's reproducible across compiles
result.typ = getSysType(c.graph, traitCall[1].info, tyString)
result.info = traitCall.info
of "genericHead":
var arg = operand
case arg.kind
Expand Down
1 change: 1 addition & 0 deletions compiler/semtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
else:
assignType(prev, s.typ)
# bugfix: keep the fresh id for aliases to integral types:
# could use simply: `IntegralTypes - {tyEnum}`
if s.typ.kind notin {tyBool, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
tyUInt..tyUInt64}:
prev.itemId = s.typ.itemId
Expand Down
31 changes: 31 additions & 0 deletions lib/pure/typetraits.nim
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,37 @@ runnableExamples:
type C[T] = enum h0 = 2, h1 = 4
assert C[float] is HoleyEnum

type
TypeId* = distinct string ## opaque, used by `getTypeId`

proc `==`*(x, y: TypeId): bool {.borrow.}
proc `$`*(x: TypeId): string {.borrow.}

proc getTypeIdImpl(t: typedesc): string {.magic: "TypeTrait", since: (1, 5, 1).}

proc getTypeId*(t: typedesc): TypeId {.since: (1, 5, 1).} =
## Returns a unique id representing a type; the id is stable across
## recompilations of the same program, but may differ if the program source
## changes. In particular serializing it will be meaningless after source change
## and recompilation: ids are reused in an un-specified manner.
##
## Example use cases that are impossible / hard without such feature:
## 1: using ids as keys in Tables (eg for Type hashing) or to prevent recursions during type traversal
## 2: passing a callback proc that can handle multiple types to a routine
## 3: defining an exportc proc that can handle multiple types, this can be used
## as workaround for lack of cyclic imports
##
## See examples for those use cases in ttypetraits.nim; in each case the
## callback is called via:
## `callbackFun(cast[pointer](a), getTypeId(type(a)), ...)`

runnableExamples:
type Foo[T] = object
type Foo2 = Foo
assert Foo[int].getTypeId == Foo2[type(1)].getTypeId
assert Foo[int].getTypeId != Foo[float].getTypeId
TypeId(getTypeIdImpl(t))

proc name*(t: typedesc): string {.magic: "TypeTrait".} =
## Returns the name of the given type.
##
Expand Down
14 changes: 14 additions & 0 deletions tests/metatype/mtypetraits_impl.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{.used.}

import std/typetraits
import mtypetraits_types
import ttypetraits

proc callbackFun(a: pointer, id: TypeId): string {.exportc.} =
case id
of getTypeid(Foo1): $("custom1", cast[Foo1](a))
of getTypeid(Foo2): $("custom2", cast[Foo2](a))
of getTypeid(Foo3): $("custom3", cast[Foo3](a))
else:
doAssert false, $id
""
10 changes: 10 additions & 0 deletions tests/metatype/mtypetraits_types.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Foo1* = object
x1*: int

type Foo2* = object
x2*: int

import std/typetraits
proc callbackFun(a: pointer, id: TypeId): string {.importc.}

proc callbackFun*[T](a: T): string = callbackFun(cast[pointer](a), getTypeid(T))
103 changes: 103 additions & 0 deletions tests/metatype/ttypetraits.nim
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,106 @@ block: # enum.len
doAssert MyEnum.enumLen == 4
doAssert OtherEnum.enumLen == 3
doAssert MyFlag.enumLen == 4

block: # getTypeId
const c1 = getTypeId(type(12))
const a1 = getTypeId(type(12))
const a2 = getTypeId(type(12))
let a3 = getTypeId(type(12))
doAssert a1 == a2
# we need to check that because nim uses different id's
# for different instances of tyInt (etc), so we make sure implementation of
# `getTypeId` is robust to that
doAssert a1 == a3
doAssert getTypeId(type(12.0)) != getTypeId(type(12))
doAssert getTypeId(type(12.0)) == getTypeId(float)

type Foo = object
x1: int

type FooT[T] = object
x1: int
type Foo2 = Foo
type FooT2 = FooT
doAssert Foo.getTypeId == Foo2.getTypeId
doAssert FooT2.getTypeId == FooT.getTypeId
doAssert FooT2[float].getTypeId == FooT[type(1.2)].getTypeId
doAssert FooT2[float].getTypeId != FooT[type(1)].getTypeId
doAssert Foo.x1.type.getTypeId == int.getTypeId

doAssert int.getTypeId is TypeId

block: # example use case for `getTypeId`: passing a callback that handles multiple types
## this would be in a library, say prettys.nim:
type Callback = proc(result: var string, a: pointer, id: TypeId): bool

proc pretty[T](result: var string, a: T, callback: Callback) =
when T is object:
result.add "("
for k,v in fieldPairs(a):
result.add $k & ": "
pretty(result, v, callback)
result.add ", "
result.add ")"
elif T is ref|ptr:
if callback(result, cast[pointer](a), getTypeId(T)):
discard
elif a == nil:
result.add "nil"
else:
pretty(result, a[], callback)
else:
result.add $a

proc pretty[T](a: T, callback: Callback): string = pretty(result, a, callback)

## this would be in user code, say main.nim:
proc main()=
type Foo = ref object
x: int
type Bar = object
b1: Foo
b2: string

let f = Bar(b1: Foo(x: 12), b2: "abc")

proc callback(ret: var string, a: pointer, id: TypeId): bool =
case id
of Foo.getTypeId:
ret.add $("custom:", cast[Foo](a).x)
return true
else:
discard

proc callback2(ret: var string, a: pointer, id: TypeId): bool =
case id
of Foo.getTypeId:
ret.add $("custom2:", cast[Foo](a).x)
return true
else:
discard

doAssert pretty(f, callback) == """(b1: ("custom:", 12), b2: abc, )"""
doAssert pretty(f, callback2) == """(b1: ("custom2:", 12), b2: abc, )"""

main()

type Foo3* = object
x3*: int

import ./mtypetraits_types

block:
# example use case for `getTypeId`: exportc proc that handles multiple types.
# This can be used in cases where we want to define implementation for a
# proc in a separate module (here, mtypetraits_impl), to avoid cyclic import
# issues or avoid dragging many dependencies for users of the proc, which can
# be declared in another import module (here, mtypetraits_types).
# This mimicks the use of headers vs source files in C.

doAssert callbackFun(Foo1(x1: 1)) == """("custom1", (x1: 1))"""
doAssert callbackFun(Foo2(x2: 2)) == """("custom2", (x2: 2))"""
doAssert callbackFun(Foo3(x3: 3)) == """("custom3", (x3: 3))"""

import ./mtypetraits_impl
# this could be imported from any module; it defines our exportc proc