diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index e4e00767831c5..20b0f95f88c88 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -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 diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 84556ca1afdaf..f9afb9772aebb 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -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 diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 3e215a0ea3524..ef385e6059219 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -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. ## diff --git a/tests/metatype/mtypetraits_impl.nim b/tests/metatype/mtypetraits_impl.nim new file mode 100644 index 0000000000000..9829f192b9c7d --- /dev/null +++ b/tests/metatype/mtypetraits_impl.nim @@ -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 + "" diff --git a/tests/metatype/mtypetraits_types.nim b/tests/metatype/mtypetraits_types.nim new file mode 100644 index 0000000000000..0c8609b5377b6 --- /dev/null +++ b/tests/metatype/mtypetraits_types.nim @@ -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)) diff --git a/tests/metatype/ttypetraits.nim b/tests/metatype/ttypetraits.nim index 3ff5c5ea66f6c..ae0ca20937d2f 100644 --- a/tests/metatype/ttypetraits.nim +++ b/tests/metatype/ttypetraits.nim @@ -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