summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2019-08-30 23:26:45 -0700
committerAndreas Rumpf <rumpf_a@web.de>2019-08-31 08:26:45 +0200
commit9ae0dd611f77c4cd7122e6ac77e0278c1bafbbd7 (patch)
treec17bac39c0309f3e5c342bce74f525b060de6fee
parentf9600b7207e45573ee066ec7c9145df113ff5b99 (diff)
downloadNim-9ae0dd611f77c4cd7122e6ac77e0278c1bafbbd7.tar.gz
typeToString can now show (recursively) resolved type aliases; fixes #8569 #8083 #8570 (#11678)
* nested typeToString
* typeToString: preferResolved
* add test
* fix test
* preferMixed
* fix tests
-rw-r--r--compiler/semmagic.nim8
-rw-r--r--compiler/types.nim432
-rw-r--r--tests/errmsgs/tsigmatch.nim2
-rw-r--r--tests/errmsgs/twrong_at_operator.nim2
-rw-r--r--tests/metatype/ttypetraits2.nim29
-rw-r--r--tests/openarray/t8259.nim2
6 files changed, 272 insertions, 203 deletions
diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim
index 7f6bf8fa3..ed0c12a95 100644
--- a/compiler/semmagic.nim
+++ b/compiler/semmagic.nim
@@ -141,6 +141,14 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
     return typeWithSonsResult(tyAnd, @[operand, operand2])
   of "not":
     return typeWithSonsResult(tyNot, @[operand])
+  of "typeToString":
+    var prefer = preferTypeName
+    if traitCall.sons.len >= 2:
+      let preferStr = traitCall.sons[2].strVal
+      prefer = parseEnum[TPreferedDesc](preferStr)
+    result = newStrNode(nkStrLit, operand.typeToString(prefer))
+    result.typ = newType(tyString, context)
+    result.info = traitCall.info
   of "name", "$":
     result = newStrNode(nkStrLit, operand.typeToString(preferTypeName))
     result.typ = newType(tyString, context)
diff --git a/compiler/types.nim b/compiler/types.nim
index 1c21fffb1..1473be261 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -15,8 +15,14 @@ import
 
 type
   TPreferedDesc* = enum
-    preferName, preferDesc, preferExported, preferModuleInfo, preferGenericArg,
-    preferTypeName
+    preferName, # default
+    preferDesc, # probably should become what preferResolved is
+    preferExported,
+    preferModuleInfo, # fully qualified
+    preferGenericArg,
+    preferTypeName,
+    preferResolved, # fully resolved symbols
+    preferMixed, # show symbol + resolved symbols if it differs, eg: seq[cint{int32}, float]
 
 proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string
 template `$`*(typ: PType): string = typeToString(typ)
@@ -121,6 +127,7 @@ proc isFloatLit*(t: PType): bool {.inline.} =
 
 proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferName; getDeclarationPath = true): string =
   assert sym != nil
+  # consider using `skipGenericOwner` to avoid fun2.fun2 when fun2 is generic
   result = sym.owner.name.s & '.' & sym.name.s
   if sym.kind in routineKinds:
     result.add '('
@@ -415,12 +422,12 @@ proc rangeToStr(n: PNode): string =
   result = valueToString(n.sons[0]) & ".." & valueToString(n.sons[1])
 
 const
-  typeToStr: array[TTypeKind, string] = ["None", "bool", "Char", "empty",
+  typeToStr: array[TTypeKind, string] = ["None", "bool", "char", "empty",
     "Alias", "nil", "untyped", "typed", "typeDesc",
     "GenericInvocation", "GenericBody", "GenericInst", "GenericParam",
     "distinct $1", "enum", "ordinal[$1]", "array[$1, $2]", "object", "tuple",
     "set[$1]", "range[$1]", "ptr ", "ref ", "var ", "seq[$1]", "proc",
-    "pointer", "OpenArray[$1]", "string", "CString", "Forward",
+    "pointer", "OpenArray[$1]", "string", "cstring", "Forward",
     "int", "int8", "int16", "int32", "int64",
     "float", "float32", "float64", "float128",
     "uint", "uint8", "uint16", "uint32", "uint64",
@@ -431,7 +438,8 @@ const
     "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor",
     "void"]
 
-const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo, preferGenericArg}
+const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo,
+  preferGenericArg, preferResolved, preferMixed}
 
 template bindConcreteTypeToUserTypeClass*(tc, concrete: PType) =
   tc.sons.add concrete
@@ -450,208 +458,232 @@ proc addTypeFlags(name: var string, typ: PType) {.inline.} =
   if tfNotNil in typ.flags: name.add(" not nil")
 
 proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
-  var t = typ
-  result = ""
-  if t == nil: return
-  if prefer in preferToResolveSymbols and t.sym != nil and
-       sfAnon notin t.sym.flags and t.kind != tySequence:
-    if t.kind == tyInt and isIntLit(t):
-      result = t.sym.name.s & " literal(" & $t.n.intVal & ")"
-    elif t.kind == tyAlias and t.sons[0].kind != tyAlias:
-      result = typeToString(t.sons[0])
-    elif prefer in {preferName, preferTypeName} or t.sym.owner.isNil:
-      result = t.sym.name.s
-      if t.kind == tyGenericParam and t.sonsLen > 0:
-        result.add ": "
-        var first = true
-        for son in t.sons:
-          if not first: result.add " or "
-          result.add son.typeToString
-          first = false
-    else:
-      result = t.sym.owner.name.s & '.' & t.sym.name.s
-    result.addTypeFlags(t)
-    return
-  case t.kind
-  of tyInt:
-    if not isIntLit(t) or prefer == preferExported:
-      result = typeToStr[t.kind]
+  let preferToplevel = prefer
+  proc getPrefer(prefer: TPreferedDesc): TPreferedDesc =
+    if preferToplevel in {preferResolved, preferMixed}:
+      preferToplevel # sticky option
     else:
-      if prefer == preferGenericArg:
-        result = $t.n.intVal
+      prefer
+
+  proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
+    let prefer = getPrefer(prefer)
+    let t = typ
+    result = ""
+    if t == nil: return
+    if prefer in preferToResolveSymbols and t.sym != nil and
+         sfAnon notin t.sym.flags and t.kind != tySequence:
+      if t.kind == tyInt and isIntLit(t):
+        result = t.sym.name.s & " literal(" & $t.n.intVal & ")"
+      elif t.kind == tyAlias and t.sons[0].kind != tyAlias:
+        result = typeToString(t.sons[0])
+      elif prefer in {preferResolved, preferMixed}:
+        case t.kind
+        of IntegralTypes + {tyFloat..tyFloat128} + {tyString, tyCString}:
+          result = typeToStr[t.kind]
+        of tyGenericBody:
+          result = typeToString(t.lastSon)
+        of tyCompositeTypeClass:
+          # avoids showing `A[any]` in `proc fun(a: A)` with `A = object[T]`
+          result = typeToString(t.lastSon.lastSon)
+        else:
+          result = t.sym.name.s
+        if prefer == preferMixed and result != t.sym.name.s:
+          result = t.sym.name.s & "{" & result & "}"
+      elif prefer in {preferName, preferTypeName} or t.sym.owner.isNil:
+        # note: should probably be: {preferName, preferTypeName, preferGenericArg}
+        result = t.sym.name.s
+        if t.kind == tyGenericParam and t.sonsLen > 0:
+          result.add ": "
+          var first = true
+          for son in t.sons:
+            if not first: result.add " or "
+            result.add son.typeToString
+            first = false
       else:
-        result = "int literal(" & $t.n.intVal & ")"
-  of tyGenericInst, tyGenericInvocation:
-    result = typeToString(t.sons[0]) & '['
-    for i in 1 ..< sonsLen(t)-ord(t.kind != tyGenericInvocation):
-      if i > 1: add(result, ", ")
-      add(result, typeToString(t.sons[i], preferGenericArg))
-    add(result, ']')
-  of tyGenericBody:
-    result = typeToString(t.lastSon) & '['
-    for i in 0 .. sonsLen(t)-2:
-      if i > 0: add(result, ", ")
-      add(result, typeToString(t.sons[i], preferTypeName))
-    add(result, ']')
-  of tyTypeDesc:
-    if t.sons[0].kind == tyNone: result = "typedesc"
-    else: result = "type " & typeToString(t.sons[0])
-  of tyStatic:
-    if prefer == preferGenericArg and t.n != nil:
-      result = t.n.renderTree
-    else:
-      result = "static[" & (if t.len > 0: typeToString(t.sons[0]) else: "") & "]"
-      if t.n != nil: result.add "(" & renderTree(t.n) & ")"
-  of tyUserTypeClass:
-    if t.sym != nil and t.sym.owner != nil:
-      if t.isResolvedUserTypeClass: return typeToString(t.lastSon)
-      return t.sym.owner.name.s
-    else:
-      result = "<invalid tyUserTypeClass>"
-  of tyBuiltInTypeClass:
-    result = case t.base.kind:
-      of tyVar: "var"
-      of tyRef: "ref"
-      of tyPtr: "ptr"
-      of tySequence: "seq"
-      of tyArray: "array"
-      of tySet: "set"
-      of tyRange: "range"
-      of tyDistinct: "distinct"
-      of tyProc: "proc"
-      of tyObject: "object"
-      of tyTuple: "tuple"
-      of tyOpenArray: "openarray"
-      else: typeToStr[t.base.kind]
-  of tyInferred:
-    let concrete = t.previouslyInferred
-    if concrete != nil: result = typeToString(concrete)
-    else: result = "inferred[" & typeToString(t.base) & "]"
-  of tyUserTypeClassInst:
-    let body = t.base
-    result = body.sym.name.s & "["
-    for i in 1 .. sonsLen(t) - 2:
-      if i > 1: add(result, ", ")
-      add(result, typeToString(t.sons[i]))
-    result.add "]"
-  of tyAnd:
-    for i, son in t.sons:
-      result.add(typeToString(son))
-      if i < t.sons.high:
-        result.add(" and ")
-  of tyOr:
-    for i, son in t.sons:
-      result.add(typeToString(son))
-      if i < t.sons.high:
-        result.add(" or ")
-  of tyNot:
-    result = "not " & typeToString(t.sons[0])
-  of tyUntyped:
-    #internalAssert t.len == 0
-    result = "untyped"
-  of tyFromExpr:
-    if t.n == nil:
-      result = "unknown"
-    else:
-      result = "type(" & renderTree(t.n) & ")"
-  of tyArray:
-    if t.sons[0].kind == tyRange:
-      result = "array[" & rangeToStr(t.sons[0].n) & ", " &
-          typeToString(t.sons[1]) & ']'
-    else:
-      result = "array[" & typeToString(t.sons[0]) & ", " &
-          typeToString(t.sons[1]) & ']'
-  of tyUncheckedArray:
-    result = "UncheckedArray[" & typeToString(t.sons[0]) & ']'
-  of tySequence:
-    result = "seq[" & typeToString(t.sons[0]) & ']'
-  of tyOpt:
-    result = "opt[" & typeToString(t.sons[0]) & ']'
-  of tyOrdinal:
-    result = "ordinal[" & typeToString(t.sons[0]) & ']'
-  of tySet:
-    result = "set[" & typeToString(t.sons[0]) & ']'
-  of tyOpenArray:
-    result = "openarray[" & typeToString(t.sons[0]) & ']'
-  of tyDistinct:
-    result = "distinct " & typeToString(t.sons[0],
-      if prefer == preferModuleInfo: preferModuleInfo else: preferTypeName)
-  of tyTuple:
-    # we iterate over t.sons here, because t.n may be nil
-    if t.n != nil:
-      result = "tuple["
-      assert(sonsLen(t.n) == sonsLen(t))
-      for i in 0 ..< sonsLen(t.n):
-        assert(t.n.sons[i].kind == nkSym)
-        add(result, t.n.sons[i].sym.name.s & ": " & typeToString(t.sons[i]))
-        if i < sonsLen(t.n) - 1: add(result, ", ")
+        result = t.sym.owner.name.s & '.' & t.sym.name.s
+      result.addTypeFlags(t)
+      return
+    case t.kind
+    of tyInt:
+      if not isIntLit(t) or prefer == preferExported:
+        result = typeToStr[t.kind]
+      else:
+        if prefer == preferGenericArg:
+          result = $t.n.intVal
+        else:
+          result = "int literal(" & $t.n.intVal & ")"
+    of tyGenericInst, tyGenericInvocation:
+      result = typeToString(t.sons[0]) & '['
+      for i in 1 ..< sonsLen(t)-ord(t.kind != tyGenericInvocation):
+        if i > 1: add(result, ", ")
+        add(result, typeToString(t.sons[i], preferGenericArg))
       add(result, ']')
-    elif sonsLen(t) == 0:
-      result = "tuple[]"
-    else:
-      if prefer == preferTypeName: result = "("
-      else: result = "tuple of ("
-      for i in 0 ..< sonsLen(t):
+    of tyGenericBody:
+      result = typeToString(t.lastSon) & '['
+      for i in 0 .. sonsLen(t)-2:
+        if i > 0: add(result, ", ")
+        add(result, typeToString(t.sons[i], preferTypeName))
+      add(result, ']')
+    of tyTypeDesc:
+      if t.sons[0].kind == tyNone: result = "typedesc"
+      else: result = "type " & typeToString(t.sons[0])
+    of tyStatic:
+      if prefer == preferGenericArg and t.n != nil:
+        result = t.n.renderTree
+      else:
+        result = "static[" & (if t.len > 0: typeToString(t.sons[0]) else: "") & "]"
+        if t.n != nil: result.add "(" & renderTree(t.n) & ")"
+    of tyUserTypeClass:
+      if t.sym != nil and t.sym.owner != nil:
+        if t.isResolvedUserTypeClass: return typeToString(t.lastSon)
+        return t.sym.owner.name.s
+      else:
+        result = "<invalid tyUserTypeClass>"
+    of tyBuiltInTypeClass:
+      result = case t.base.kind:
+        of tyVar: "var"
+        of tyRef: "ref"
+        of tyPtr: "ptr"
+        of tySequence: "seq"
+        of tyArray: "array"
+        of tySet: "set"
+        of tyRange: "range"
+        of tyDistinct: "distinct"
+        of tyProc: "proc"
+        of tyObject: "object"
+        of tyTuple: "tuple"
+        of tyOpenArray: "openArray"
+        else: typeToStr[t.base.kind]
+    of tyInferred:
+      let concrete = t.previouslyInferred
+      if concrete != nil: result = typeToString(concrete)
+      else: result = "inferred[" & typeToString(t.base) & "]"
+    of tyUserTypeClassInst:
+      let body = t.base
+      result = body.sym.name.s & "["
+      for i in 1 .. sonsLen(t) - 2:
+        if i > 1: add(result, ", ")
         add(result, typeToString(t.sons[i]))
-        if i < sonsLen(t) - 1: add(result, ", ")
-      add(result, ')')
-  of tyPtr, tyRef, tyVar, tyLent:
-    result = typeToStr[t.kind]
-    if t.len >= 2:
-      setLen(result, result.len-1)
-      result.add '['
-      for i in 0 ..< sonsLen(t):
+      result.add "]"
+    of tyAnd:
+      for i, son in t.sons:
+        result.add(typeToString(son))
+        if i < t.sons.high:
+          result.add(" and ")
+    of tyOr:
+      for i, son in t.sons:
+        result.add(typeToString(son))
+        if i < t.sons.high:
+          result.add(" or ")
+    of tyNot:
+      result = "not " & typeToString(t.sons[0])
+    of tyUntyped:
+      #internalAssert t.len == 0
+      result = "untyped"
+    of tyFromExpr:
+      if t.n == nil:
+        result = "unknown"
+      else:
+        result = "type(" & renderTree(t.n) & ")"
+    of tyArray:
+      if t.sons[0].kind == tyRange:
+        result = "array[" & rangeToStr(t.sons[0].n) & ", " &
+            typeToString(t.sons[1]) & ']'
+      else:
+        result = "array[" & typeToString(t.sons[0]) & ", " &
+            typeToString(t.sons[1]) & ']'
+    of tyUncheckedArray:
+      result = "UncheckedArray[" & typeToString(t.sons[0]) & ']'
+    of tySequence:
+      result = "seq[" & typeToString(t.sons[0]) & ']'
+    of tyOpt:
+      result = "opt[" & typeToString(t.sons[0]) & ']'
+    of tyOrdinal:
+      result = "ordinal[" & typeToString(t.sons[0]) & ']'
+    of tySet:
+      result = "set[" & typeToString(t.sons[0]) & ']'
+    of tyOpenArray:
+      result = "openArray[" & typeToString(t.sons[0]) & ']'
+    of tyDistinct:
+      result = "distinct " & typeToString(t.sons[0],
+        if prefer == preferModuleInfo: preferModuleInfo else: preferTypeName)
+    of tyTuple:
+      # we iterate over t.sons here, because t.n may be nil
+      if t.n != nil:
+        result = "tuple["
+        assert(sonsLen(t.n) == sonsLen(t))
+        for i in 0 ..< sonsLen(t.n):
+          assert(t.n.sons[i].kind == nkSym)
+          add(result, t.n.sons[i].sym.name.s & ": " & typeToString(t.sons[i]))
+          if i < sonsLen(t.n) - 1: add(result, ", ")
+        add(result, ']')
+      elif sonsLen(t) == 0:
+        result = "tuple[]"
+      else:
+        if prefer == preferTypeName: result = "("
+        else: result = "tuple of ("
+        for i in 0 ..< sonsLen(t):
+          add(result, typeToString(t.sons[i]))
+          if i < sonsLen(t) - 1: add(result, ", ")
+        add(result, ')')
+    of tyPtr, tyRef, tyVar, tyLent:
+      result = typeToStr[t.kind]
+      if t.len >= 2:
+        setLen(result, result.len-1)
+        result.add '['
+        for i in 0 ..< sonsLen(t):
+          add(result, typeToString(t.sons[i]))
+          if i < sonsLen(t) - 1: add(result, ", ")
+        result.add ']'
+      else:
+        result.add typeToString(t.sons[0])
+    of tyRange:
+      result = "range "
+      if t.n != nil and t.n.kind == nkRange:
+        result.add rangeToStr(t.n)
+      if prefer != preferExported:
+        result.add("(" & typeToString(t.sons[0]) & ")")
+    of tyProc:
+      result = if tfIterator in t.flags: "iterator "
+               elif t.owner != nil:
+                 case t.owner.kind
+                 of skTemplate: "template "
+                 of skMacro: "macro "
+                 of skConverter: "converter "
+                 else: "proc "
+              else:
+                "proc "
+      if tfUnresolved in t.flags: result.add "[*missing parameters*]"
+      result.add "("
+      for i in 1 ..< sonsLen(t):
+        if t.n != nil and i < t.n.len and t.n[i].kind == nkSym:
+          add(result, t.n[i].sym.name.s)
+          add(result, ": ")
         add(result, typeToString(t.sons[i]))
         if i < sonsLen(t) - 1: add(result, ", ")
-      result.add ']'
+      add(result, ')')
+      if t.len > 0 and t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0]))
+      var prag = if t.callConv == ccDefault: "" else: CallingConvToStr[t.callConv]
+      if tfNoSideEffect in t.flags:
+        addSep(prag)
+        add(prag, "noSideEffect")
+      if tfThread in t.flags:
+        addSep(prag)
+        add(prag, "gcsafe")
+      if t.lockLevel.ord != UnspecifiedLockLevel.ord:
+        addSep(prag)
+        add(prag, "locks: " & $t.lockLevel)
+      if len(prag) != 0: add(result, "{." & prag & ".}")
+    of tyVarargs:
+      result = typeToStr[t.kind] % typeToString(t.sons[0])
+    of tySink:
+      result = "sink " & typeToString(t.sons[0])
+    of tyOwned:
+      result = "owned " & typeToString(t.sons[0])
     else:
-      result.add typeToString(t.sons[0])
-  of tyRange:
-    result = "range "
-    if t.n != nil and t.n.kind == nkRange:
-      result.add rangeToStr(t.n)
-    if prefer != preferExported:
-      result.add("(" & typeToString(t.sons[0]) & ")")
-  of tyProc:
-    result = if tfIterator in t.flags: "iterator "
-             elif t.owner != nil:
-               case t.owner.kind
-               of skTemplate: "template "
-               of skMacro: "macro "
-               of skConverter: "converter "
-               else: "proc "
-            else:
-              "proc "
-    if tfUnresolved in t.flags: result.add "[*missing parameters*]"
-    result.add "("
-    for i in 1 ..< sonsLen(t):
-      if t.n != nil and i < t.n.len and t.n[i].kind == nkSym:
-        add(result, t.n[i].sym.name.s)
-        add(result, ": ")
-      add(result, typeToString(t.sons[i]))
-      if i < sonsLen(t) - 1: add(result, ", ")
-    add(result, ')')
-    if t.len > 0 and t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0]))
-    var prag = if t.callConv == ccDefault: "" else: CallingConvToStr[t.callConv]
-    if tfNoSideEffect in t.flags:
-      addSep(prag)
-      add(prag, "noSideEffect")
-    if tfThread in t.flags:
-      addSep(prag)
-      add(prag, "gcsafe")
-    if t.lockLevel.ord != UnspecifiedLockLevel.ord:
-      addSep(prag)
-      add(prag, "locks: " & $t.lockLevel)
-    if len(prag) != 0: add(result, "{." & prag & ".}")
-  of tyVarargs:
-    result = typeToStr[t.kind] % typeToString(t.sons[0])
-  of tySink:
-    result = "sink " & typeToString(t.sons[0])
-  of tyOwned:
-    result = "owned " & typeToString(t.sons[0])
-  else:
-    result = typeToStr[t.kind]
-  result.addTypeFlags(t)
+      result = typeToStr[t.kind]
+    result.addTypeFlags(t)
+  result = typeToString(typ, prefer)
 
 proc firstOrd*(conf: ConfigRef; t: PType): Int128 =
   case t.kind
diff --git a/tests/errmsgs/tsigmatch.nim b/tests/errmsgs/tsigmatch.nim
index 42a98a891..21e2c217d 100644
--- a/tests/errmsgs/tsigmatch.nim
+++ b/tests/errmsgs/tsigmatch.nim
@@ -36,7 +36,7 @@ tsigmatch.nim(143, 13) Error: type mismatch: got <array[0..0, proc (x: int){.gcs
 but expected one of:
 proc takesFuncs(fs: openArray[proc (x: int) {.gcsafe, locks: 0.}])
   first type mismatch at position: 1
-  required type for fs: openarray[proc (x: int){.closure, gcsafe, locks: 0.}]
+  required type for fs: openArray[proc (x: int){.closure, gcsafe, locks: 0.}]
   but expression '[proc (x: int) {.gcsafe, locks: 0.} = echo [x]]' is of type: array[0..0, proc (x: int){.gcsafe, locks: 0.}]
 
 expression: takesFuncs([proc (x: int) {.gcsafe, locks: 0.} = echo [x]])
diff --git a/tests/errmsgs/twrong_at_operator.nim b/tests/errmsgs/twrong_at_operator.nim
index edf584094..9933b0f39 100644
--- a/tests/errmsgs/twrong_at_operator.nim
+++ b/tests/errmsgs/twrong_at_operator.nim
@@ -6,7 +6,7 @@ twrong_at_operator.nim(22, 30) Error: type mismatch: got <array[0..0, type int]>
 but expected one of:
 proc `@`[T](a: openArray[T]): seq[T]
   first type mismatch at position: 1
-  required type for a: openarray[T]
+  required type for a: openArray[T]
   but expression '[int]' is of type: array[0..0, type int]
 proc `@`[IDX, T](a: sink array[IDX, T]): seq[T]
   first type mismatch at position: 1
diff --git a/tests/metatype/ttypetraits2.nim b/tests/metatype/ttypetraits2.nim
index c5beaa307..e07b51660 100644
--- a/tests/metatype/ttypetraits2.nim
+++ b/tests/metatype/ttypetraits2.nim
@@ -14,3 +14,32 @@ block: # isNamedTuple
   doAssert not Foo3.isNamedTuple
   doAssert not Foo4.isNamedTuple
   doAssert not (1,).type.isNamedTuple
+
+proc typeToString*(t: typedesc, prefer = "preferTypeName"): string {.magic: "TypeTrait".}
+  ## Returns the name of the given type, with more flexibility than `name`,
+  ## and avoiding the potential clash with a variable named `name`.
+  ## prefer = "preferResolved" will resolve type aliases recursively.
+  # Move to typetraits.nim once api stabilized.
+
+block: # typeToString
+  type MyInt = int
+  type
+    C[T0, T1] = object
+  type C2=C # alias => will resolve as C
+  type C2b=C # alias => will resolve as C (recursively)
+  type C3[U,V] = C[V,U]
+  type C4[X] = C[X,X]
+  template name2(T): string = typeToString(T, "preferResolved")
+  doAssert MyInt.name2 == "int"
+  doAssert C3[MyInt, C2b].name2 == "C3[int, C]"
+    # C3 doesn't get resolved to C, not an alias (nor does C4)
+  doAssert C2b[MyInt, C4[cstring]].name2 == "C[int, C4[cstring]]"
+  doAssert C4[MyInt].name2 == "C4[int]"
+  when BiggestFloat is float and cint is int:
+    doAssert C2b[cint, BiggestFloat].name2 == "C3[int, C3[float, int32]]"
+
+  template name3(T): string = typeToString(T, "preferMixed")
+  doAssert MyInt.name3 == "MyInt{int}"
+  doAssert (tuple[a: MyInt, b: float]).name3 == "tuple[a: MyInt{int}, b: float]"
+  doAssert (tuple[a: C2b[MyInt, C4[cstring]], b: cint, c: float]).name3 ==
+    "tuple[a: C2b{C}[MyInt{int}, C4[cstring]], b: cint{int32}, c: float]"
diff --git a/tests/openarray/t8259.nim b/tests/openarray/t8259.nim
index c07576997..283c6cd02 100644
--- a/tests/openarray/t8259.nim
+++ b/tests/openarray/t8259.nim
@@ -1,5 +1,5 @@
 discard """
-  errormsg: "invalid type: 'openarray[int]' for result"
+  errormsg: "invalid type: 'openArray[int]' for result"
   line: 6
 """