From 9ae0dd611f77c4cd7122e6ac77e0278c1bafbbd7 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 30 Aug 2019 23:26:45 -0700 Subject: typeToString can now show (recursively) resolved type aliases; fixes #8569 #8083 #8570 (#11678) * nested typeToString * typeToString: preferResolved * add test * fix test * preferMixed * fix tests --- compiler/semmagic.nim | 8 + compiler/types.nim | 432 +++++++++++++++++++---------------- tests/errmsgs/tsigmatch.nim | 2 +- tests/errmsgs/twrong_at_operator.nim | 2 +- tests/metatype/ttypetraits2.nim | 29 +++ tests/openarray/t8259.nim | 2 +- 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 = "" - 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 = "" + 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 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 """ -- cgit 1.4.1-2-gfad0