# # # The Nim Compiler # (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # this module contains routines for accessing and iterating over types import ast, astalgo, trees, msgs, platform, renderer, options, lineinfos, int128, modulegraphs, astmsgs, wordrecg import std/[intsets, strutils] when defined(nimPreviewSlimSystem): import std/[assertions, formatfloat] type TPreferedDesc* = enum preferName, # default preferDesc, # probably should become what preferResolved is preferExported, preferModuleInfo, # fully qualified preferGenericArg, preferTypeName, preferResolved, # fully resolved symbols preferMixed, # most useful, shows: symbol + resolved symbols if it differs, e.g.: # tuple[a: MyInt{int}, b: float] preferInlayHint, preferInferredEffects, TTypeRelation* = enum # order is important! isNone, isConvertible, isIntConv, isSubtype, isSubrange, # subrange of the wanted type; no type conversion # but apart from that counts as ``isSubtype`` isBothMetaConvertible # generic proc parameter was matched against # generic type, e.g., map(mySeq, x=>x+1), # maybe recoverable by rerun if the parameter is # the proc's return value isInferred, # generic proc was matched against a concrete type isInferredConvertible, # same as above, but requiring proc CC conversion isGeneric, isFromIntLit, # conversion *from* int literal; proven safe isEqual ProcConvMismatch* = enum pcmNoSideEffect pcmNotGcSafe pcmNotIterator pcmDifferentCallConv proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string proc addTypeDeclVerboseMaybe*(result: var string, conf: ConfigRef; typ: PType) = if optDeclaredLocs in conf.globalOptions: result.add typeToString(typ, preferMixed) result.addDeclaredLoc(conf, typ) else: result.add typeToString(typ) template `$`*(typ: PType): string = typeToString(typ) # ------------------- type iterator: ---------------------------------------- type TTypeIter* = proc (t: PType, closure: RootRef): bool {.nimcall.} # true if iteration should stop TTypePredicate* = proc (t: PType): bool {.nimcall.} proc iterOverType*(t: PType, iter: TTypeIter, closure: RootRef): bool # Returns result of `iter`. type TParamsEquality* = enum # they are equal, but their # identifiers or their return # type differ (i.e. they cannot be # overloaded) # this used to provide better error messages paramsNotEqual, # parameters are not equal paramsEqual, # parameters are equal paramsIncompatible proc equalParams*(a, b: PNode): TParamsEquality # returns whether the parameter lists of the procs a, b are exactly the same const # TODO: Remove tyTypeDesc from each abstractX and (where necessary) # replace with typedescX abstractPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tyInferred, tySink, tyLent, tyOwned} abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tyInferred, tySink, tyLent, tyOwned} abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tyInferred, tySink, tyOwned} abstractInstOwned* = abstractInst + {tyOwned} skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias, tyInferred, tySink, tyLent, tyOwned} # typedescX is used if we're sure tyTypeDesc should be included (or skipped) typedescPtrs* = abstractPtrs + {tyTypeDesc} typedescInst* = abstractInst + {tyTypeDesc, tyOwned, tyUserTypeClass} # incorrect definition of `[]` and `[]=` for these types in system.nim arrPutGetMagicApplies* = {tyArray, tyOpenArray, tyString, tySequence, tyCstring, tyTuple} proc invalidGenericInst*(f: PType): bool = result = f.kind == tyGenericInst and skipModifier(f) == nil proc isPureObject*(typ: PType): bool = var t = typ while t.kind == tyObject and t.baseClass != nil: t = t.baseClass.skipTypes(skipPtrs) result = t.sym != nil and sfPure in t.sym.flags proc isUnsigned*(t: PType): bool = t.skipTypes(abstractInst).kind in {tyChar, tyUInt..tyUInt64} proc getOrdValueAux*(n: PNode, err: var bool): Int128 = var k = n.kind if n.typ != nil and n.typ.skipTypes(abstractInst).kind in {tyChar, tyUInt..tyUInt64}: k = nkUIntLit case k of nkCharLit, nkUIntLit..nkUInt64Lit: # XXX: enable this assert #assert n.typ == nil or isUnsigned(n.typ), $n.typ toInt128(cast[uint64](n.intVal)) of nkIntLit..nkInt64Lit: # XXX: enable this assert #assert n.typ == nil or not isUnsigned(n.typ), $n.typ.kind toInt128(n.intVal) of nkNilLit: int128.Zero of nkHiddenStdConv: getOrdValueAux(n[1], err) else: err = true int128.Zero proc getOrdValue*(n: PNode): Int128 = var err: bool = false result = getOrdValueAux(n, err) #assert err == false proc getOrdValue*(n: PNode, onError: Int128): Int128 = var err = false result = getOrdValueAux(n, err) if err: result = onError proc getFloatValue*(n: PNode): BiggestFloat = case n.kind of nkFloatLiterals: n.floatVal of nkHiddenStdConv: getFloatValue(n[1]) else: NaN proc isIntLit*(t: PType): bool {.inline.} = result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit proc isFloatLit*(t: PType): bool {.inline.} = result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit proc addTypeHeader*(result: var string, conf: ConfigRef; typ: PType; prefer: TPreferedDesc = preferMixed; getDeclarationPath = true) = result.add typeToString(typ, prefer) if getDeclarationPath: result.addDeclaredLoc(conf, typ.sym) 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 '(' var n = sym.typ.n for i in 1..typedesc: typedesc is declared as such, and is 10x more common. "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", "int", "int8", "int16", "int32", "int64", "float", "float32", "float64", "float128", "uint", "uint8", "uint16", "uint32", "uint64", "owned", "sink", "lent ", "varargs[$1]", "UncheckedArray[$1]", "Error Type", "BuiltInTypeClass", "UserTypeClass", "UserTypeClassInst", "CompositeTypeClass", "inferred", "and", "or", "not", "any", "static", "TypeFromExpr", "concept", # xxx bugfix "void", "iterable"] const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo, preferGenericArg, preferResolved, preferMixed, preferInlayHint, preferInferredEffects} template bindConcreteTypeToUserTypeClass*(tc, concrete: PType) = tc.add concrete tc.flags.incl tfResolved # TODO: It would be a good idea to kill the special state of a resolved # concept by switching to tyAlias within the instantiated procs. # Currently, tyAlias is always skipped with skipModifier, which means that # we can store information about the matched concept in another position. # Then builtInFieldAccess can be modified to properly read the derived # consts and types stored within the concept. template isResolvedUserTypeClass*(t: PType): bool = tfResolved in t.flags 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 = let preferToplevel = prefer proc getPrefer(prefer: TPreferedDesc): TPreferedDesc = if preferToplevel in {preferResolved, preferMixed}: preferToplevel # sticky option else: prefer proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = result = "" let prefer = getPrefer(prefer) let t = typ 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): if prefer == preferInlayHint: result = t.sym.name.s else: result = t.sym.name.s & " literal(" & $t.n.intVal & ")" elif t.kind == tyAlias and t.elementType.kind != tyAlias: result = typeToString(t.elementType) elif prefer in {preferResolved, preferMixed}: case t.kind of IntegralTypes + {tyFloat..tyFloat128} + {tyString, tyCstring}: result = typeToStr[t.kind] of tyGenericBody: result = typeToString(t.last) of tyCompositeTypeClass: # avoids showing `A[any]` in `proc fun(a: A)` with `A = object[T]` result = typeToString(t.last.last) 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, preferInlayHint, preferInferredEffects} or t.sym.owner.isNil: # note: should probably be: {preferName, preferTypeName, preferGenericArg} result = t.sym.name.s if t.kind == tyGenericParam and t.genericParamHasConstraints: result.add ": " result.add t.elementType.typeToString 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] else: case prefer: of preferGenericArg: result = $t.n.intVal of preferInlayHint: result = "int" else: result = "int literal(" & $t.n.intVal & ")" of tyGenericInst: result = typeToString(t.genericHead) & '[' for needsComma, a in t.genericInstParams: if needsComma: result.add(", ") result.add(typeToString(a, preferGenericArg)) result.add(']') of tyGenericInvocation: result = typeToString(t.genericHead) & '[' for needsComma, a in t.genericInvocationParams: if needsComma: result.add(", ") result.add(typeToString(a, preferGenericArg)) result.add(']') of tyGenericBody: result = typeToString(t.typeBodyImpl) & '[' for i, a in t.genericBodyParams: if i > 0: result.add(", ") result.add(typeToString(a, preferTypeName)) result.add(']') of tyTypeDesc: if t.elementType.kind == tyNone: result = "typedesc" else: result = "typedesc[" & typeToString(t.elementType) & "]" of tyStatic: if prefer == preferGenericArg and t.n != nil: result = t.n.renderTree else: result = "static[" & (if t.hasElementType: typeToString(t.skipModifier) 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.last) 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 needsComma, a in t.userTypeClassInstParams: if needsComma: result.add(", ") result.add(typeToString(a)) result.add "]" of tyAnd: for i, son in t.ikids: if i > 0: result.add(" and ") result.add(typeToString(son)) of tyOr: for i, son in t.ikids: if i > 0: result.add(" or ") result.add(typeToString(son)) of tyNot: result = "not " & typeToString(t.elementType) of tyUntyped: #internalAssert t.len == 0 result = "untyped" of tyFromExpr: if t.n == nil: result = "unknown" else: result = "typeof(" & renderTree(t.n) & ")" of tyArray: result = "array" if t.hasElementType: if t.indexType.kind == tyRange: result &= "[" & rangeToStr(t.indexType.n) & ", " & typeToString(t.elementType) & ']' else: result &= "[" & typeToString(t.indexType) & ", " & typeToString(t.elementType) & ']' of tyUncheckedArray: result = "UncheckedArray" if t.hasElementType: result &= "[" & typeToString(t.elementType) & ']' of tySequence: if t.sym != nil and prefer != preferResolved: result = t.sym.name.s else: result = "seq" if t.hasElementType: result &= "[" & typeToString(t.elementType) & ']' of tyOrdinal: result = "ordinal" if t.hasElementType: result &= "[" & typeToString(t.skipModifier) & ']' of tySet: result = "set" if t.hasElementType: result &= "[" & typeToString(t.elementType) & ']' of tyOpenArray: result = "openArray" if t.hasElementType: result &= "[" & typeToString(t.elementType) & ']' of tyDistinct: result = "distinct " & typeToString(t.elementType, if prefer == preferModuleInfo: preferModuleInfo else: preferTypeName) of tyIterable: # xxx factor this pattern result = "iterable" if t.hasElementType: result &= "[" & typeToString(t.skipModifier) & ']' of tyTuple: # we iterate over t.sons here, because t.n may be nil if t.n != nil: result = "tuple[" for i in 0.. 0: result.add ", " result.add(typeToString(son)) result.add(')') of tyPtr, tyRef, tyVar, tyLent: result = if isOutParam(t): "out " else: typeToStr[t.kind] result.add typeToString(t.elementType) 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.elementType) & ")") 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, a in t.paramTypes: if i > FirstParamAt: result.add(", ") let j = paramTypeToNodeIndex(i) if t.n != nil and j < t.n.len and t.n[j].kind == nkSym: result.add(t.n[j].sym.name.s) result.add(": ") result.add(typeToString(a)) result.add(')') if t.returnType != nil: result.add(": " & typeToString(t.returnType)) var prag = if t.callConv == ccNimCall and tfExplicitCallConv notin t.flags: "" else: $t.callConv var hasImplicitRaises = false if not isNil(t.owner) and not isNil(t.owner.ast) and (t.owner.ast.len - 1) >= pragmasPos: let pragmasNode = t.owner.ast[pragmasPos] let raisesSpec = effectSpec(pragmasNode, wRaises) if not isNil(raisesSpec): addSep(prag) prag.add("raises: ") prag.add($raisesSpec) hasImplicitRaises = true if tfNoSideEffect in t.flags: addSep(prag) prag.add("noSideEffect") if tfThread in t.flags: addSep(prag) prag.add("gcsafe") var effectsOfStr = "" for i, a in t.paramTypes: let j = paramTypeToNodeIndex(i) if t.n != nil and j < t.n.len and t.n[j].kind == nkSym and t.n[j].sym.kind == skParam and sfEffectsDelayed in t.n[j].sym.flags: addSep(effectsOfStr) effectsOfStr.add(t.n[j].sym.name.s) if effectsOfStr != "": addSep(prag) prag.add("effectsOf: ") prag.add(effectsOfStr) if not hasImplicitRaises and prefer == preferInferredEffects and not isNil(t.owner) and not isNil(t.owner.typ) and not isNil(t.owner.typ.n) and (t.owner.typ.n.len > 0): let effects = t.n[0] if effects.kind == nkEffectList and effects.len == effectListLen: var inferredRaisesStr = "" let effs = effects[exceptionEffects] if not isNil(effs): for eff in items(effs): if not isNil(eff): addSep(inferredRaisesStr) inferredRaisesStr.add($eff.typ) addSep(prag) prag.add("raises: [") prag.add(inferredRaisesStr) prag.add("]") if prag.len != 0: result.add("{." & prag & ".}") of tyVarargs: result = typeToStr[t.kind] % typeToString(t.elementType) of tySink: result = "sink " & typeToString(t.skipModifier) of tyOwned: result = "owned " & typeToString(t.elementType) else: result = typeToStr[t.kind] result.addTypeFlags(t) result = typeToString(typ, prefer) proc firstOrd*(conf: ConfigRef; t: PType): Int128 = case t.kind of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyError: result = Zero of tySet, tyVar: result = firstOrd(conf, t.elementType) of tyArray: result = firstOrd(conf, t.indexType) of tyRange: assert(t.n != nil) # range directly given: assert(t.n.kind == nkRange) result = getOrdValue(t.n[0]) of tyInt: if conf != nil: case conf.target.intSize of 8: result = toInt128(0x8000000000000000'i64) of 4: result = toInt128(-2147483648) of 2: result = toInt128(-32768) of 1: result = toInt128(-128) else: result = Zero else: result = toInt128(0x8000000000000000'i64) of tyInt8: result = toInt128(-128) of tyInt16: result = toInt128(-32768) of tyInt32: result = toInt128(-2147483648) of tyInt64: result = toInt128(0x8000000000000000'i64) of tyUInt..tyUInt64: result = Zero of tyEnum: # if basetype <> nil then return firstOrd of basetype if t.baseClass != nil: result = firstOrd(conf, t.baseClass) else: if t.n.len > 0: assert(t.n[0].kind == nkSym) result = toInt128(t.n[0].sym.position) else: result = Zero of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, tyStatic, tyInferred, tyLent: result = firstOrd(conf, skipModifier(t)) of tyUserTypeClasses: result = firstOrd(conf, last(t)) of tyOrdinal: if t.hasElementType: result = firstOrd(conf, skipModifier(t)) else: result = Zero fatal(conf, unknownLineInfo, "invalid kind for firstOrd(" & $t.kind & ')') of tyUncheckedArray, tyCstring: result = Zero else: result = Zero fatal(conf, unknownLineInfo, "invalid kind for firstOrd(" & $t.kind & ')') proc firstFloat*(t: PType): BiggestFloat = case t.kind of tyFloat..tyFloat128: -Inf of tyRange: assert(t.n != nil) # range directly given: assert(t.n.kind == nkRange) getFloatValue(t.n[0]) of tyVar: firstFloat(t.elementType) of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, tyStatic, tyInferred: firstFloat(skipModifier(t)) of tyUserTypeClasses: firstFloat(last(t)) else: internalError(newPartialConfigRef(), "invalid kind for firstFloat(" & $t.kind & ')') NaN proc targetSizeSignedToKind*(conf: ConfigRef): TTypeKind = case conf.target.intSize of 8: result = tyInt64 of 4: result = tyInt32 of 2: result = tyInt16 else: result = tyNone proc targetSizeUnsignedToKind*(conf: ConfigRef): TTypeKind = case conf.target.intSize of 8: result = tyUInt64 of 4: result = tyUInt32 of 2: result = tyUInt16 else: result = tyNone proc normalizeKind*(conf: ConfigRef, k: TTypeKind): TTypeKind = case k of tyInt: result = conf.targetSizeSignedToKind() of tyUInt: result = conf.targetSizeUnsignedToKind() else: result = k proc lastOrd*(conf: ConfigRef; t: PType): Int128 = case t.kind of tyBool: result = toInt128(1'u) of tyChar: result = toInt128(255'u) of tySet, tyVar: result = lastOrd(conf, t.elementType) of tyArray: result = lastOrd(conf, t.indexType) of tyRange: assert(t.n != nil) # range directly given: assert(t.n.kind == nkRange) result = getOrdValue(t.n[1]) of tyInt: if conf != nil: case conf.target.intSize of 8: result = toInt128(0x7FFFFFFFFFFFFFFF'u64) of 4: result = toInt128(0x7FFFFFFF) of 2: result = toInt128(0x00007FFF) of 1: result = toInt128(0x0000007F) else: result = Zero else: result = toInt128(0x7FFFFFFFFFFFFFFF'u64) of tyInt8: result = toInt128(0x0000007F) of tyInt16: result = toInt128(0x00007FFF) of tyInt32: result = toInt128(0x7FFFFFFF) of tyInt64: result = toInt128(0x7FFFFFFFFFFFFFFF'u64) of tyUInt: if conf != nil and conf.target.intSize == 4: result = toInt128(0xFFFFFFFF) else: result = toInt128(0xFFFFFFFFFFFFFFFF'u64) of tyUInt8: result = toInt128(0xFF) of tyUInt16: result = toInt128(0xFFFF) of tyUInt32: result = toInt128(0xFFFFFFFF) of tyUInt64: result = toInt128(0xFFFFFFFFFFFFFFFF'u64) of tyEnum: if t.n.len > 0: assert(t.n[^1].kind == nkSym) result = toInt128(t.n[^1].sym.position) else: result = Zero of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, tyStatic, tyInferred, tyLent: result = lastOrd(conf, skipModifier(t)) of tyUserTypeClasses: result = lastOrd(conf, last(t)) of tyError: result = Zero of tyOrdinal: if t.hasElementType: result = lastOrd(conf, skipModifier(t)) else: result = Zero fatal(conf, unknownLineInfo, "invalid kind for lastOrd(" & $t.kind & ')') of tyUncheckedArray: result = Zero else: result = Zero fatal(conf, unknownLineInfo, "invalid kind for lastOrd(" & $t.kind & ')') proc lastFloat*(t: PType): BiggestFloat = case t.kind of tyFloat..tyFloat128: Inf of tyVar: lastFloat(t.elementType) of tyRange: assert(t.n != nil) # range directly given: assert(t.n.kind == nkRange) getFloatValue(t.n[1]) of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, tyStatic, tyInferred: lastFloat(skipModifier(t)) of tyUserTypeClasses: lastFloat(last(t)) else: internalError(newPartialConfigRef(), "invalid kind for lastFloat(" & $t.kind & ')') NaN proc floatRangeCheck*(x: BiggestFloat, t: PType): bool = case t.kind # This needs to be special cased since NaN is never # part of firstFloat(t)..lastFloat(t) of tyFloat..tyFloat128: true of tyRange: x in firstFloat(t)..lastFloat(t) of tyVar: floatRangeCheck(x, t.elementType) of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, tyStatic, tyInferred: floatRangeCheck(x, skipModifier(t)) of tyUserTypeClasses: floatRangeCheck(x, last(t)) else: internalError(newPartialConfigRef(), "invalid kind for floatRangeCheck:" & $t.kind) false proc lengthOrd*(conf: ConfigRef; t: PType): Int128 = if t.skipTypes(tyUserTypeClasses).kind == tyDistinct: result = lengthOrd(conf, t.skipModifier) else: let last = lastOrd(conf, t) let first = firstOrd(conf, t) result = last - first + One # -------------- type equality ----------------------------------------------- type TDistinctCompare* = enum ## how distinct types are to be compared dcEq, ## a and b should be the same type dcEqIgnoreDistinct, ## compare symmetrically: (distinct a) == b, a == b ## or a == (distinct b) dcEqOrDistinctOf ## a equals b or a is distinct of b TTypeCmpFlag* = enum IgnoreTupleFields ## NOTE: Only set this flag for backends! IgnoreCC ExactTypeDescValues ExactGenericParams ExactConstraints ExactGcSafety AllowCommonBase PickyCAliases # be picky about the distinction between 'cint' and 'int32' IgnoreFlags # used for borrowed functions and methods; ignores the tfVarIsPtr flag PickyBackendAliases # be picky about different aliases IgnoreRangeShallow TTypeCmpFlags* = set[TTypeCmpFlag] TSameTypeClosure = object cmp: TDistinctCompare recCheck: int flags: TTypeCmpFlags s: seq[tuple[a,b: int]] # seq for a set as it's hopefully faster # (few elements expected) proc initSameTypeClosure: TSameTypeClosure = # we do the initialization lazily for performance (avoids memory allocations) result = TSameTypeClosure() proc containsOrIncl(c: var TSameTypeClosure, a, b: PType): bool = result = c.s.len > 0 and c.s.contains((a.id, b.id)) if not result: c.s.add((a.id, b.id)) proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool proc sameTypeOrNilAux(a, b: PType, c: var TSameTypeClosure): bool = if a == b: result = true else: if a == nil or b == nil: result = false else: result = sameTypeAux(a, b, c) proc sameType*(a, b: PType, flags: TTypeCmpFlags = {}): bool = var c = initSameTypeClosure() c.flags = flags result = sameTypeAux(a, b, c) proc sameTypeOrNil*(a, b: PType, flags: TTypeCmpFlags = {}): bool = if a == b: result = true else: if a == nil or b == nil: result = false else: result = sameType(a, b, flags) proc equalParam(a, b: PSym): TParamsEquality = if sameTypeOrNil(a.typ, b.typ, {ExactTypeDescValues}) and exprStructuralEquivalent(a.constraint, b.constraint): if a.ast == b.ast: result = paramsEqual elif a.ast != nil and b.ast != nil: if exprStructuralEquivalent(a.ast, b.ast): result = paramsEqual else: result = paramsIncompatible elif a.ast != nil: result = paramsEqual elif b.ast != nil: result = paramsIncompatible else: result = paramsNotEqual else: result = paramsNotEqual proc sameConstraints(a, b: PNode): bool = if isNil(a) and isNil(b): return true if a.len != b.len: return false for i in 1.. TA[int] != TB[int] if tfFromGeneric in a.flags * b.flags and a.sym.id == b.sym.id: # ok, we need the expensive structural check body else: result = false proc sameObjectTypes*(a, b: PType): bool = # specialized for efficiency (sigmatch uses it) ifFastObjectTypeCheckFailed(a, b): var c = initSameTypeClosure() result = sameTypeAux(a, b, c) proc sameDistinctTypes*(a, b: PType): bool {.inline.} = result = sameObjectTypes(a, b) proc sameEnumTypes*(a, b: PType): bool {.inline.} = result = a.id == b.id proc sameObjectTree(a, b: PNode, c: var TSameTypeClosure): bool = if a == b: result = true elif a != nil and b != nil and a.kind == b.kind: var x = a.typ var y = b.typ if IgnoreTupleFields in c.flags: if x != nil: x = skipTypes(x, {tyRange, tyGenericInst, tyAlias}) if y != nil: y = skipTypes(y, {tyRange, tyGenericInst, tyAlias}) if sameTypeOrNilAux(x, y, c): case a.kind of nkSym: # same symbol as string is enough: result = a.sym.name.id == b.sym.name.id of nkIdent: result = a.ident.id == b.ident.id of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal of nkEmpty, nkNilLit, nkType: result = true else: if a.len == b.len: for i in 0.. 0: gotPragmas.setLen(max(0, gotPragmas.len - 2)) # Remove ", " expectedPragmas.setLen(max(0, expectedPragmas.len - 2)) # Remove ", " message.add "\n Pragma mismatch: got '{.$1.}', but expected '{.$2.}'." % [gotPragmas, expectedPragmas] proc processPragmaAndCallConvMismatch(msg: var string, formal, actual: PType, conf: ConfigRef) = if formal.kind == tyProc and actual.kind == tyProc: msg.addPragmaAndCallConvMismatch(formal, actual, conf) case compatibleEffects(formal, actual) of efCompat: discard of efRaisesDiffer: msg.add "\n.raise effects differ" of efRaisesUnknown: msg.add "\n.raise effect is 'can raise any'" of efTagsDiffer: msg.add "\n.tag effects differ" of efTagsUnknown: msg.add "\n.tag effect is 'any tag allowed'" of efEffectsDelayed: msg.add "\n.effectsOf annotations differ" of efTagsIllegal: msg.add "\n.notTag catched an illegal effect" proc typeNameAndDesc*(t: PType): string = result = typeToString(t) let desc = typeToString(t, preferDesc) if result != desc: result.add(" = ") result.add(desc) proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType, n: PNode) = if formal.kind != tyError and actual.kind != tyError: let actualStr = typeToString(actual) let formalStr = typeToString(formal) let desc = typeToString(formal, preferDesc) let x = if formalStr == desc: formalStr else: formalStr & " = " & desc let verbose = actualStr == formalStr or optDeclaredLocs in conf.globalOptions var msg = "type mismatch:" if verbose: msg.add "\n" if conf.isDefined("nimLegacyTypeMismatch"): msg.add " got <$1>" % actualStr else: msg.add " got '$1' for '$2'" % [actualStr, n.renderTree] if verbose: msg.addDeclaredLoc(conf, actual) msg.add "\n" msg.add " but expected '$1'" % x if verbose: msg.addDeclaredLoc(conf, formal) var a = formal var b = actual if formal.kind == tyArray and actual.kind == tyArray: a = formal[1] b = actual[1] processPragmaAndCallConvMismatch(msg, a, b, conf) elif formal.kind == tySequence and actual.kind == tySequence: a = formal[0] b = actual[0] processPragmaAndCallConvMismatch(msg, a, b, conf) else: processPragmaAndCallConvMismatch(msg, a, b, conf) localError(conf, info, msg) proc isRecursiveStructuralType(t: PType, cycleDetector: var IntSet): bool = if t == nil: return false if cycleDetector.containsOrIncl(t.id): return true case t.kind of tyTuple: result = false var cycleDetectorCopy: IntSet for a in t.kids: cycleDetectorCopy = cycleDetector if isRecursiveStructuralType(a, cycleDetectorCopy): return true of tyProc: result = false var cycleDetectorCopy: IntSet if t.returnType != nil: cycleDetectorCopy = cycleDetector if isRecursiveStructuralType(t.returnType, cycleDetectorCopy): return true for _, a in t.paramTypes: cycleDetectorCopy = cycleDetector if isRecursiveStructuralType(a, cycleDetectorCopy): return true of tyRef, tyPtr, tyVar, tyLent, tySink, tyArray, tyUncheckedArray, tySequence, tyDistinct: return isRecursiveStructuralType(t.elementType, cycleDetector) of tyAlias, tyGenericInst: return isRecursiveStructuralType(t.skipModifier, cycleDetector) else: return false proc isRecursiveStructuralType*(t: PType): bool = var cycleDetector = initIntSet() isRecursiveStructuralType(t, cycleDetector) proc isException*(t: PType): bool = # check if `y` is object type and it inherits from Exception assert(t != nil) var t = t.skipTypes(abstractInst) while t.kind == tyObject: if t.sym != nil and t.sym.magic == mException: return true if t.baseClass == nil: break t = skipTypes(t.baseClass, abstractPtrs) return false proc isDefectException*(t: PType): bool = var t = t.skipTypes(abstractPtrs) while t.kind == tyObject: if t.sym != nil and t.sym.owner != nil and sfSystemModule in t.sym.owner.flags and t.sym.name.s == "Defect": return true if t.baseClass == nil: break t = skipTypes(t.baseClass, abstractPtrs) return false proc isDefectOrCatchableError*(t: PType): bool = var t = t.skipTypes(abstractPtrs) while t.kind == tyObject: if t.sym != nil and t.sym.owner != nil and sfSystemModule in t.sym.owner.flags and (t.sym.name.s == "Defect" or t.sym.name.s == "CatchableError"): return true if t.baseClass == nil: break t = skipTypes(t.baseClass, abstractPtrs) return false proc isSinkTypeForParam*(t: PType): bool = # a parameter like 'seq[owned T]' must not be used only once, but its # elements must, so we detect this case here: result = t.skipTypes({tyGenericInst, tyAlias}).kind in {tySink, tyOwned} when false: if isSinkType(t): if t.skipTypes({tyGenericInst, tyAlias}).kind in {tyArray, tyVarargs, tyOpenArray, tySequence}: result = false else: result = true proc lookupFieldAgain*(ty: PType; field: PSym): PSym = result = nil var ty = ty while ty != nil: ty = ty.skipTypes(skipPtrs) assert(ty.kind in {tyTuple, tyObject}) result = lookupInRecord(ty.n, field.name) if result != nil: break ty = ty.baseClass if result == nil: result = field proc isCharArrayPtr*(t: PType; allowPointerToChar: bool): bool = let t = t.skipTypes(abstractInst) if t.kind == tyPtr: let pointsTo = t.elementType.skipTypes(abstractInst) case pointsTo.kind of tyUncheckedArray: result = pointsTo.elementType.kind == tyChar of tyArray: result = pointsTo.elementType.kind == tyChar and firstOrd(nil, pointsTo.indexType) == 0 and skipTypes(pointsTo.indexType, {tyRange}).kind in {tyInt..tyInt64} of tyChar: result = allowPointerToChar else: result = false else: result = false proc isRefPtrObject*(t: PType): bool = t.kind in {tyRef, tyPtr} and tfRefsAnonObj in t.flags proc nominalRoot*(t: PType): PType = ## the "name" type of a given instance of a nominal type, ## i.e. the type directly associated with the symbol where the root ## nominal type of `t` was defined, skipping things like generic instances, ## aliases, `var`/`sink`/`typedesc` modifiers ## ## instead of returning the uninstantiated body of a generic type, ## returns the type of the symbol instead (with tyGenericBody type) result = nil case t.kind of tyAlias, tyVar, tySink: # varargs? result = nominalRoot(t.skipModifier) of tyTypeDesc: # for proc foo(_: type T) result = nominalRoot(t.skipModifier) of tyGenericInvocation, tyGenericInst: result = t # skip aliases, so this works in the same module but not in another module: # type Foo[T] = object # type Bar[T] = Foo[T] # proc foo[T](x: Bar[T]) = ... # attached to type while result.skipModifier.kind in {tyGenericInvocation, tyGenericInst}: result = result.skipModifier result = nominalRoot(result[0]) of tyGenericBody: result = t # this time skip the aliases but take the generic body while result.skipModifier.kind in {tyGenericInvocation, tyGenericInst}: result = result.skipModifier[0] let val = result.skipModifier if val.kind in {tyDistinct, tyEnum, tyObject} or isRefPtrObject(val): # atomic nominal types, this generic body is attached to them discard else: result = nominalRoot(val) of tyCompositeTypeClass: # parameter with type Foo result = nominalRoot(t.skipModifier) of tyGenericParam: if t.genericParamHasConstraints: # T: Foo result = nominalRoot(t.genericConstraint) else: result = nil of tyDistinct, tyEnum, tyObject: result = t of tyPtr, tyRef: if tfRefsAnonObj in t.flags: # in the case that we have `type Foo = ref object` etc result = t else: # we could allow this in general, but there's things like `seq[Foo]` #result = nominalRoot(t.skipModifier) result = nil of tyStatic: result = nominalRoot(t.base) else: # skips all typeclasses # is this correct for `concept`? result = nil proc genericRoot*(t: PType): PType = ## gets the root generic type (`tyGenericBody`) from `t`, ## if `t` is a generic type or the body of a generic instantiation case t.kind of tyGenericBody: result = t of tyGenericInst, tyGenericInvocation: result = t.genericHead else: if t.typeInst != nil: result = t.typeInst.genericHead elif t.sym != nil and t.sym.typ.kind == tyGenericBody: # can happen if `t` is the last child (body) of the generic body result = t.sym.typ else: result = nil proc reduceToBase*(f: PType): PType = #[ Not recursion safe Returns the lowest order (most general) type that that is compatible with the input. E.g. A[T] = ptr object ... A -> ptr object A[N: static[int]] = array[N, int] ... A -> array ]# case f.kind: of tyGenericParam: if f.len <= 0 or f.skipModifier == nil: result = f else: result = reduceToBase(f.skipModifier) of tyGenericInvocation: result = reduceToBase(f.baseClass) of tyCompositeTypeClass, tyAlias: if not f.hasElementType or f.elementType == nil: result = f else: result = reduceToBase(f.elementType) of tyGenericInst: result = reduceToBase(f.skipModifier) of tyGenericBody: result = reduceToBase(f.typeBodyImpl) of tyUserTypeClass: if f.isResolvedUserTypeClass: result = f.base else: result = f.skipModifier of tyStatic, tyOwned, tyVar, tyLent, tySink: result = reduceToBase(f.base) of tyInferred: # This is not true "After a candidate type is selected" result = reduceToBase(f.base) of tyRange: result = f.elementType else: result = f