Files
Nim/compiler/types.nim
metagn 7f0e07492f generally disallow recursive structural types, check proc param types (#24893)
fixes #5631, fixes #8938, fixes #18855, fixes #19271, fixes #23885,
fixes #24877

`isTupleRecursive`, previously only called to give an error for illegal
recursions for:

* tuple fields
* types declared in type sections
* explicitly instantiated generic types

did not check for recursions in proc types. It now does, meaning proc
types now need a nominal type layer to recurse over themselves. It is
renamed to `isRecursiveStructuralType` to better reflect what it does,
it is different from a recursive type that cannot exist due to a lack of
pointer indirection which is possible for nominal types.

It is now also called to check the param/return types of procs, similar
to how tuple field types are checked. Pointer indirection checks are not
needed since procs are pointers.

I wondered if this would lead to a slowdown in the compiler but since it
only skips structural types it shouldn't take too many iterations, not
to mention only proc types are newly considered and aren't that common.
But maybe something in the implementation could be inefficient, like the
cycle detector using an IntSet.

Note: The name `isRecursiveStructuralType` is not exactly correct
because it still checks for `distinct` types. If it didn't, then the
compiler would accept this:

```nim
type
  A = distinct B
  B = ref A
```

But this breaks when attempting to write `var x: A`. However this is not
the case for:

```nim
type
  A = object
    x: B
  B = ref A
```

So a better description would be "types that are structural on the
backend".

A future step to deal with #14015 and #23224 might be to check the
arguments of `tyGenericInst` as well but I don't know if this makes
perfect sense.
2025-04-21 09:01:44 +02:00

2131 lines
74 KiB
Nim

#
#
# 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..<n.len:
let p = n[i]
if p.kind == nkSym:
result.add(p.sym.name.s)
result.add(": ")
result.add(typeToString(p.sym.typ, prefer))
if i != n.len-1: result.add(", ")
else:
result.add renderTree(p)
result.add(')')
if n[0].typ != nil:
result.add(": " & typeToString(n[0].typ, prefer))
if getDeclarationPath: result.addDeclaredLoc(conf, sym)
proc elemType*(t: PType): PType =
assert(t != nil)
case t.kind
of tyGenericInst, tyDistinct, tyAlias, tySink: result = elemType(skipModifier(t))
of tyArray: result = t.elementType
of tyError: result = t
else: result = t.elementType
assert(result != nil)
proc enumHasHoles*(t: PType): bool =
var b = t.skipTypes({tyRange, tyGenericInst, tyAlias, tySink})
result = b.kind == tyEnum and tfEnumHasHoles in b.flags
proc isOrdinalType*(t: PType, allowEnumWithHoles: bool = false): bool =
assert(t != nil)
const
baseKinds = {tyChar, tyInt..tyInt64, tyUInt..tyUInt64, tyBool, tyEnum}
parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tySink, tyDistinct}
result = (t.kind in baseKinds and (not t.enumHasHoles or allowEnumWithHoles)) or
(t.kind in parentKinds and isOrdinalType(t.skipModifier, allowEnumWithHoles))
proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter,
closure: RootRef): bool
proc iterOverNode(marker: var IntSet, n: PNode, iter: TTypeIter,
closure: RootRef): bool =
if n != nil:
case n.kind
of nkNone..nkNilLit:
# a leaf
result = iterOverTypeAux(marker, n.typ, iter, closure)
else:
result = iterOverTypeAux(marker, n.typ, iter, closure)
if result: return
for i in 0..<n.len:
result = iterOverNode(marker, n[i], iter, closure)
if result: return
else:
result = false
proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter,
closure: RootRef): bool =
result = false
if t == nil: return
result = iter(t, closure)
if result: return
if not containsOrIncl(marker, t.id):
case t.kind
of tyGenericBody:
# treat as atomic, containsUnresolvedType wants always false,
# containsGenericType always gives true
discard
of tyGenericInst, tyAlias, tySink, tyInferred:
result = iterOverTypeAux(marker, skipModifier(t), iter, closure)
else:
for a in t.kids:
result = iterOverTypeAux(marker, a, iter, closure)
if result: return
if t.n != nil and t.kind != tyProc: result = iterOverNode(marker, t.n, iter, closure)
proc iterOverType(t: PType, iter: TTypeIter, closure: RootRef): bool =
var marker = initIntSet()
result = iterOverTypeAux(marker, t, iter, closure)
proc searchTypeForAux(t: PType, predicate: TTypePredicate,
marker: var IntSet): bool
proc searchTypeNodeForAux(n: PNode, p: TTypePredicate,
marker: var IntSet): bool =
result = false
case n.kind
of nkRecList:
for i in 0..<n.len:
result = searchTypeNodeForAux(n[i], p, marker)
if result: return
of nkRecCase:
assert(n[0].kind == nkSym)
result = searchTypeNodeForAux(n[0], p, marker)
if result: return
for i in 1..<n.len:
case n[i].kind
of nkOfBranch, nkElse:
result = searchTypeNodeForAux(lastSon(n[i]), p, marker)
if result: return
else: discard
of nkSym:
result = searchTypeForAux(n.sym.typ, p, marker)
else: discard
proc searchTypeForAux(t: PType, predicate: TTypePredicate,
marker: var IntSet): bool =
# iterates over VALUE types!
result = false
if t == nil: return
if containsOrIncl(marker, t.id): return
result = predicate(t)
if result: return
case t.kind
of tyObject:
if t.baseClass != nil:
result = searchTypeForAux(t.baseClass.skipTypes(skipPtrs), predicate, marker)
if not result: result = searchTypeNodeForAux(t.n, predicate, marker)
of tyGenericInst, tyDistinct, tyAlias, tySink:
result = searchTypeForAux(skipModifier(t), predicate, marker)
of tyArray, tySet, tyTuple:
for a in t.kids:
result = searchTypeForAux(a, predicate, marker)
if result: return
else:
discard
proc searchTypeFor*(t: PType, predicate: TTypePredicate): bool =
var marker = initIntSet()
result = searchTypeForAux(t, predicate, marker)
proc isObjectPredicate(t: PType): bool =
result = t.kind == tyObject
proc containsObject*(t: PType): bool =
result = searchTypeFor(t, isObjectPredicate)
proc isObjectWithTypeFieldPredicate(t: PType): bool =
result = t.kind == tyObject and t.baseClass == nil and
not (t.sym != nil and {sfPure, sfInfixCall} * t.sym.flags != {}) and
tfFinal notin t.flags
type
TTypeFieldResult* = enum
frNone, # type has no object type field
frHeader, # type has an object type field only in the header
frEmbedded # type has an object type field somewhere embedded
proc analyseObjectWithTypeFieldAux(t: PType,
marker: var IntSet): TTypeFieldResult =
result = frNone
if t == nil: return
case t.kind
of tyObject:
if t.n != nil:
if searchTypeNodeForAux(t.n, isObjectWithTypeFieldPredicate, marker):
return frEmbedded
var x = t.baseClass
if x != nil: x = x.skipTypes(skipPtrs)
let res = analyseObjectWithTypeFieldAux(x, marker)
if res == frEmbedded:
return frEmbedded
if res == frHeader: result = frHeader
if result == frNone:
if isObjectWithTypeFieldPredicate(t): result = frHeader
of tyGenericInst, tyDistinct, tyAlias, tySink:
result = analyseObjectWithTypeFieldAux(skipModifier(t), marker)
of tyArray, tyTuple:
for a in t.kids:
let res = analyseObjectWithTypeFieldAux(a, marker)
if res != frNone:
return frEmbedded
else:
discard
proc analyseObjectWithTypeField*(t: PType): TTypeFieldResult =
# this does a complex analysis whether a call to ``objectInit`` needs to be
# made or initializing of the type field suffices or if there is no type field
# at all in this type.
var marker = initIntSet()
result = analyseObjectWithTypeFieldAux(t, marker)
proc isGCRef(t: PType): bool =
result = t.kind in GcTypeKinds or
(t.kind == tyProc and t.callConv == ccClosure)
if result and t.kind in {tyString, tySequence} and tfHasAsgn in t.flags:
result = false
proc containsGarbageCollectedRef*(typ: PType): bool =
# returns true if typ contains a reference, sequence or string (all the
# things that are garbage-collected)
result = searchTypeFor(typ, isGCRef)
proc isManagedMemory(t: PType): bool =
result = t.kind in GcTypeKinds or
(t.kind == tyProc and t.callConv == ccClosure)
proc containsManagedMemory*(typ: PType): bool =
result = searchTypeFor(typ, isManagedMemory)
proc isTyRef(t: PType): bool =
result = t.kind == tyRef or (t.kind == tyProc and t.callConv == ccClosure)
proc containsTyRef*(typ: PType): bool =
# returns true if typ contains a 'ref'
result = searchTypeFor(typ, isTyRef)
proc isHiddenPointer(t: PType): bool =
result = t.kind in {tyString, tySequence, tyOpenArray, tyVarargs}
proc containsHiddenPointer*(typ: PType): bool =
# returns true if typ contains a string, table or sequence (all the things
# that need to be copied deeply)
result = searchTypeFor(typ, isHiddenPointer)
proc canFormAcycleAux(g: ModuleGraph; marker: var IntSet, typ: PType, orig: PType, withRef: bool, hasTrace: bool): bool
proc canFormAcycleNode(g: ModuleGraph; marker: var IntSet, n: PNode, orig: PType, withRef: bool, hasTrace: bool): bool =
result = false
if n != nil:
var hasCursor = n.kind == nkSym and sfCursor in n.sym.flags
# cursor fields don't own the refs, which cannot form reference cycles
if hasTrace or not hasCursor:
result = canFormAcycleAux(g, marker, n.typ, orig, withRef, hasTrace)
if not result:
case n.kind
of nkNone..nkNilLit:
discard
else:
for i in 0..<n.len:
result = canFormAcycleNode(g, marker, n[i], orig, withRef, hasTrace)
if result: return
proc sameBackendType*(x, y: PType): bool
proc canFormAcycleAux(g: ModuleGraph, marker: var IntSet, typ: PType, orig: PType, withRef: bool, hasTrace: bool): bool =
result = false
if typ == nil: return
if tfAcyclic in typ.flags: return
var t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
if tfAcyclic in t.flags: return
case t.kind
of tyRef, tyPtr, tyUncheckedArray:
if t.kind == tyRef or hasTrace:
if withRef and sameBackendType(t, orig):
result = true
elif not containsOrIncl(marker, t.id):
result = canFormAcycleAux(g, marker, t.elementType, orig, withRef or t.kind != tyUncheckedArray, hasTrace)
of tyObject:
if withRef and sameBackendType(t, orig):
result = true
elif not containsOrIncl(marker, t.id):
var hasTrace = hasTrace
let op = getAttachedOp(g, t.skipTypes({tyRef}), attachedTrace)
if op != nil and sfOverridden in op.flags:
hasTrace = true
if t.baseClass != nil:
result = canFormAcycleAux(g, marker, t.baseClass, orig, withRef, hasTrace)
if result: return
if t.n != nil: result = canFormAcycleNode(g, marker, t.n, orig, withRef, hasTrace)
# Inheritance can introduce cyclic types, however this is not relevant
# as the type that is passed to 'new' is statically known!
# er but we use it also for the write barrier ...
if tfFinal notin t.flags:
# damn inheritance may introduce cycles:
result = true
of tyTuple:
if withRef and sameBackendType(t, orig):
result = true
elif not containsOrIncl(marker, t.id):
for a in t.kids:
result = canFormAcycleAux(g, marker, a, orig, withRef, hasTrace)
if result: return
of tySequence, tyArray, tyOpenArray, tyVarargs:
if withRef and sameBackendType(t, orig):
result = true
elif not containsOrIncl(marker, t.id):
result = canFormAcycleAux(g, marker, t.elementType, orig, withRef, hasTrace)
of tyProc: result = typ.callConv == ccClosure
else: discard
proc isFinal*(t: PType): bool =
let t = t.skipTypes(abstractInst)
result = t.kind != tyObject or tfFinal in t.flags or isPureObject(t)
proc canFormAcycle*(g: ModuleGraph, typ: PType): bool =
var marker = initIntSet()
let t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
result = canFormAcycleAux(g, marker, t, t, false, false)
proc valueToString(a: PNode): string =
case a.kind
of nkCharLit, nkUIntLit..nkUInt64Lit:
result = $cast[uint64](a.intVal)
of nkIntLit..nkInt64Lit:
result = $a.intVal
of nkFloatLit..nkFloat128Lit: result = $a.floatVal
of nkStrLit..nkTripleStrLit: result = a.strVal
of nkStaticExpr: result = "static(" & a[0].renderTree & ")"
else: result = "<invalid value>"
proc rangeToStr(n: PNode): string =
assert(n.kind == nkRange)
result = valueToString(n[0]) & ".." & valueToString(n[1])
const
typeToStr: array[TTypeKind, string] = ["None", "bool", "char", "empty",
"Alias", "typeof(nil)", "untyped", "typed", "typeDesc",
# xxx typeDesc=>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 notin {tySequence, tyInferred}:
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 = "<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 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..<t.n.len:
assert(t.n[i].kind == nkSym)
result.add(t.n[i].sym.name.s & ": " & typeToString(t.n[i].sym.typ))
if i < t.n.len - 1: result.add(", ")
result.add(']')
elif t.isEmptyTupleType:
result = "tuple[]"
elif t.isSingletonTupleType:
result = "("
for son in t.kids:
result.add(typeToString(son))
result.add(",)")
else:
result = "("
for i, son in t.ikids:
if i > 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.owner.typ.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: <inferred> [")
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..<a.len:
if not exprStructuralEquivalent(a[i].sym.constraint,
b[i].sym.constraint):
return false
return true
proc equalParams(a, b: PNode): TParamsEquality =
result = paramsEqual
if a.len != b.len:
result = paramsNotEqual
else:
for i in 1..<a.len:
var m = a[i].sym
var n = b[i].sym
assert((m.kind == skParam) and (n.kind == skParam))
case equalParam(m, n)
of paramsNotEqual:
return paramsNotEqual
of paramsEqual:
discard
of paramsIncompatible:
result = paramsIncompatible
if m.name.id != n.name.id:
# BUGFIX
return paramsNotEqual # paramsIncompatible;
# continue traversal! If not equal, we can return immediately; else
# it stays incompatible
if not sameTypeOrNil(a.typ, b.typ, {ExactTypeDescValues}):
if (a.typ == nil) or (b.typ == nil):
result = paramsNotEqual # one proc has a result, the other not is OK
else:
result = paramsIncompatible # overloading by different
# result types does not work
proc sameTuple(a, b: PType, c: var TSameTypeClosure): bool =
# two tuples are equivalent iff the names, types and positions are the same;
# however, both types may not have any field names (t.n may be nil) which
# complicates the matter a bit.
if sameTupleLengths(a, b):
result = true
for i, aa, bb in tupleTypePairs(a, b):
var x = aa
var y = bb
if IgnoreTupleFields in c.flags:
x = skipTypes(x, {tyRange, tyGenericInst, tyAlias})
y = skipTypes(y, {tyRange, tyGenericInst, tyAlias})
result = sameTypeAux(x, y, c)
if not result: return
if a.n != nil and b.n != nil and IgnoreTupleFields notin c.flags:
for i in 0..<a.n.len:
# check field names:
if a.n[i].kind == nkSym and b.n[i].kind == nkSym:
var x = a.n[i].sym
var y = b.n[i].sym
result = x.name.id == y.name.id
if not result: break
else:
return false
elif a.n != b.n and (a.n == nil or b.n == nil) and IgnoreTupleFields notin c.flags:
result = false
else:
result = false
template ifFastObjectTypeCheckFailed(a, b: PType, body: untyped) =
if tfFromGeneric notin a.flags + b.flags:
# fast case: id comparison suffices:
result = a.id == b.id
else:
# expensive structural equality test; however due to the way generic and
# objects work, if one of the types does **not** contain tfFromGeneric,
# they cannot be equal. The check ``a.sym.id == b.sym.id`` checks
# for the same origin and is essential because we don't want "pure"
# structural type equivalence:
#
# type
# TA[T] = object
# TB[T] = object
# --> 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..<a.len:
if not sameObjectTree(a[i], b[i], c): return
result = true
else:
result = false
else:
result = false
else:
result = false
proc sameObjectStructures(a, b: PType, c: var TSameTypeClosure): bool =
if not sameTypeOrNilAux(a.baseClass, b.baseClass, c): return false
if not sameObjectTree(a.n, b.n, c): return false
result = true
proc sameChildrenAux(a, b: PType, c: var TSameTypeClosure): bool =
if not sameTupleLengths(a, b): return false
# XXX This is not tuple specific.
result = true
for _, x, y in tupleTypePairs(a, b):
result = sameTypeOrNilAux(x, y, c)
if not result: return
proc isGenericAlias*(t: PType): bool =
return t.kind == tyGenericInst and t.skipModifier.skipTypes({tyAlias}).kind == tyGenericInst
proc genericAliasDepth*(t: PType): int =
result = 0
var it = t.skipTypes({tyAlias})
while it.isGenericAlias:
it = it.skipModifier.skipTypes({tyAlias})
inc result
proc skipGenericAlias*(t: PType): PType =
result = t.skipTypes({tyAlias})
if result.isGenericAlias:
result = result.skipModifier.skipTypes({tyAlias})
proc sameFlags*(a, b: PType): bool {.inline.} =
result = eqTypeFlags*a.flags == eqTypeFlags*b.flags
proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
result = false
template cycleCheck() =
# believe it or not, the direct check for ``containsOrIncl(c, a, b)``
# increases bootstrapping time from 2.4s to 3.3s on my laptop! So we cheat
# again: Since the recursion check is only to not get caught in an endless
# recursion, we use a counter and only if it's value is over some
# threshold we perform the expensive exact cycle check:
if c.recCheck < 3:
inc c.recCheck
else:
if containsOrIncl(c, a, b): return true
template maybeSkipRange(x: set[TTypeKind]): set[TTypeKind] =
if IgnoreRangeShallow in c.flags:
x + {tyRange}
else:
x
template withoutShallowFlags(body) =
let oldFlags = c.flags
c.flags.excl IgnoreRangeShallow
body
c.flags = oldFlags
if x == y: return true
let aliasSkipSet = maybeSkipRange({tyAlias})
var a = skipTypes(x, aliasSkipSet)
while a.kind == tyUserTypeClass and tfResolved in a.flags:
a = skipTypes(a.last, aliasSkipSet)
var b = skipTypes(y, aliasSkipSet)
while b.kind == tyUserTypeClass and tfResolved in b.flags:
b = skipTypes(b.last, aliasSkipSet)
assert(a != nil)
assert(b != nil)
case c.cmp
of dcEq:
if a.kind != b.kind: return false
of dcEqIgnoreDistinct:
let distinctSkipSet = maybeSkipRange({tyDistinct, tyGenericInst})
a = a.skipTypes(distinctSkipSet)
b = b.skipTypes(distinctSkipSet)
if a.kind != b.kind: return false
of dcEqOrDistinctOf:
let distinctSkipSet = maybeSkipRange({tyDistinct, tyGenericInst})
a = a.skipTypes(distinctSkipSet)
if a.kind != b.kind: return false
#[
The following code should not run in the case either side is an generic alias,
but it's not presently possible to distinguish the genericinsts from aliases of
objects ie `type A[T] = SomeObject`
]#
# this is required by tunique_type but makes no sense really:
if c.cmp == dcEq and x.kind == tyGenericInst and
IgnoreTupleFields notin c.flags and tyDistinct != y.kind:
let
lhs = x.skipGenericAlias
rhs = y.skipGenericAlias
if rhs.kind != tyGenericInst or lhs.base != rhs.base or rhs.kidsLen != lhs.kidsLen:
return false
withoutShallowFlags:
for ff, aa in underspecifiedPairs(rhs, lhs, 1, -1):
if not sameTypeAux(ff, aa, c): return false
return true
case a.kind
of tyEmpty, tyChar, tyBool, tyNil, tyPointer, tyString, tyCstring,
tyInt..tyUInt64, tyTyped, tyUntyped, tyVoid:
result = sameFlags(a, b)
if result and {PickyCAliases, ExactTypeDescValues} <= c.flags:
# additional requirement for the caching of generics for importc'ed types:
# the symbols must be identical too:
let symFlagsA = if a.sym != nil: a.sym.flags else: {}
let symFlagsB = if b.sym != nil: b.sym.flags else: {}
if (symFlagsA+symFlagsB) * {sfImportc, sfExportc} != {}:
result = symFlagsA == symFlagsB
elif result and PickyBackendAliases in c.flags:
let symFlagsA = if a.sym != nil: a.sym.flags else: {}
let symFlagsB = if b.sym != nil: b.sym.flags else: {}
if (symFlagsA+symFlagsB) * {sfImportc, sfExportc} != {}:
result = a.id == b.id
of tyStatic, tyFromExpr:
result = exprStructuralEquivalent(a.n, b.n) and sameFlags(a, b)
if result and sameTupleLengths(a, b) and a.hasElementType:
cycleCheck()
result = sameTypeAux(a.skipModifier, b.skipModifier, c)
of tyObject:
result = sameFlags(a, b)
if result:
ifFastObjectTypeCheckFailed(a, b):
cycleCheck()
# should be generic, and belong to the same generic head type:
assert a.typeInst != nil, "generic object " & $a & " has no typeInst"
assert b.typeInst != nil, "generic object " & $b & " has no typeInst"
if result:
withoutShallowFlags:
# this is required because of generic `ref object`s,
# the value of their dereferences are not wrapped in `tyGenericInst`,
# so we need to check the generic parameters here
for ff, aa in underspecifiedPairs(a.typeInst, b.typeInst, 1, -1):
if not sameTypeAux(ff, aa, c): return false
of tyDistinct:
cycleCheck()
if c.cmp == dcEq:
result = sameFlags(a, b)
if result:
ifFastObjectTypeCheckFailed(a, b):
# should be generic, and belong to the same generic head type:
assert a.typeInst != nil, "generic distinct type " & $a & " has no typeInst"
assert b.typeInst != nil, "generic distinct type " & $b & " has no typeInst"
withoutShallowFlags:
# just in case `tyGenericInst` was skipped at some point,
# we need to check the generic parameters here
for ff, aa in underspecifiedPairs(a.typeInst, b.typeInst, 1, -1):
if not sameTypeAux(ff, aa, c): return false
else:
result = sameTypeAux(a.elementType, b.elementType, c) and sameFlags(a, b)
of tyEnum, tyForward:
# XXX generic enums do not make much sense, but require structural checking
result = a.id == b.id and sameFlags(a, b)
of tyError:
result = b.kind == tyError
of tyTuple:
withoutShallowFlags:
cycleCheck()
result = sameTuple(a, b, c) and sameFlags(a, b)
of tyTypeDesc:
if c.cmp == dcEqIgnoreDistinct: result = false
elif ExactTypeDescValues in c.flags:
cycleCheck()
result = sameChildrenAux(x, y, c) and sameFlags(a, b)
else:
result = sameFlags(a, b)
of tyGenericParam:
result = sameChildrenAux(a, b, c) and sameFlags(a, b)
if result and {ExactGenericParams, ExactTypeDescValues} * c.flags != {}:
result = a.sym.position == b.sym.position
of tyBuiltInTypeClass:
result = a.elementType.kind == b.elementType.kind and sameFlags(a.elementType, b.elementType)
if result and a.elementType.kind == tyProc and IgnoreCC notin c.flags:
let ecc = a.elementType.flags * {tfExplicitCallConv}
result = ecc == b.elementType.flags * {tfExplicitCallConv} and
(ecc == {} or a.elementType.callConv == b.elementType.callConv)
of tyGenericInvocation, tyGenericBody, tySequence, tyOpenArray, tySet, tyRef,
tyPtr, tyVar, tyLent, tySink, tyUncheckedArray, tyArray, tyProc, tyVarargs,
tyOrdinal, tyCompositeTypeClass, tyUserTypeClass, tyUserTypeClassInst,
tyAnd, tyOr, tyNot, tyAnything, tyOwned:
cycleCheck()
if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n
withoutShallowFlags:
result = sameChildrenAux(a, b, c)
if result and IgnoreFlags notin c.flags:
if IgnoreTupleFields in c.flags:
result = a.flags * {tfVarIsPtr, tfIsOutParam} == b.flags * {tfVarIsPtr, tfIsOutParam}
else:
result = sameFlags(a, b)
if result and ExactGcSafety in c.flags:
result = a.flags * {tfThread} == b.flags * {tfThread}
if result and a.kind == tyProc:
result = ((IgnoreCC in c.flags) or a.callConv == b.callConv) and
((ExactConstraints notin c.flags) or sameConstraints(a.n, b.n))
of tyRange:
cycleCheck()
result = sameTypeOrNilAux(a.elementType, b.elementType, c)
if result and IgnoreRangeShallow notin c.flags:
result = sameValue(a.n[0], b.n[0]) and
sameValue(a.n[1], b.n[1])
of tyAlias, tyInferred, tyIterable:
cycleCheck()
result = sameTypeAux(a.skipModifier, b.skipModifier, c)
of tyGenericInst:
# BUG #23445
# The type system must distinguish between `T[int] = object #[empty]#`
# and `T[float] = object #[empty]#`!
cycleCheck()
withoutShallowFlags:
for ff, aa in underspecifiedPairs(a, b, 1, -1):
if not sameTypeAux(ff, aa, c): return false
result = sameTypeAux(a.skipModifier, b.skipModifier, c)
of tyNone: result = false
of tyConcept:
result = exprStructuralEquivalent(a.n, b.n)
proc sameBackendType*(x, y: PType): bool =
var c = initSameTypeClosure()
c.flags.incl IgnoreTupleFields
c.cmp = dcEqIgnoreDistinct
result = sameTypeAux(x, y, c)
proc sameBackendTypeIgnoreRange*(x, y: PType): bool =
var c = initSameTypeClosure()
c.flags.incl IgnoreTupleFields
c.flags.incl IgnoreRangeShallow
c.cmp = dcEqIgnoreDistinct
result = sameTypeAux(x, y, c)
proc sameBackendTypePickyAliases*(x, y: PType): bool =
var c = initSameTypeClosure()
c.flags.incl {IgnoreTupleFields, IgnoreRangeShallow, PickyCAliases, PickyBackendAliases}
c.cmp = dcEqIgnoreDistinct
result = sameTypeAux(x, y, c)
proc compareTypes*(x, y: PType,
cmp: TDistinctCompare = dcEq,
flags: TTypeCmpFlags = {}): bool =
## compares two type for equality (modulo type distinction)
var c = initSameTypeClosure()
c.cmp = cmp
c.flags = flags
if x == y: result = true
elif x.isNil or y.isNil: result = false
else: result = sameTypeAux(x, y, c)
proc inheritanceDiff*(a, b: PType): int =
# | returns: 0 iff `a` == `b`
# | returns: -x iff `a` is the x'th direct superclass of `b`
# | returns: +x iff `a` is the x'th direct subclass of `b`
# | returns: `maxint` iff `a` and `b` are not compatible at all
if a == b or a.kind == tyError or b.kind == tyError: return 0
assert a.kind in {tyObject} + skipPtrs
assert b.kind in {tyObject} + skipPtrs
var x = a
result = 0
while x != nil:
x = skipTypes(x, skipPtrs)
if sameObjectTypes(x, b): return
x = x.baseClass
dec(result)
var y = b
result = 0
while y != nil:
y = skipTypes(y, skipPtrs)
if sameObjectTypes(y, a): return
y = y.baseClass
inc(result)
result = high(int)
proc commonSuperclass*(a, b: PType): PType =
result = nil
# quick check: are they the same?
if sameObjectTypes(a, b): return a
# simple algorithm: we store all ancestors of 'a' in a ID-set and walk 'b'
# up until the ID is found:
assert a.kind == tyObject
assert b.kind == tyObject
var x = a
var ancestors = initIntSet()
while x != nil:
x = skipTypes(x, skipPtrs)
ancestors.incl(x.id)
x = x.baseClass
var y = b
while y != nil:
var t = y # bug #7818, save type before skip
y = skipTypes(y, skipPtrs)
if ancestors.contains(y.id):
# bug #7818, defer the previous skipTypes
if t.kind != tyGenericInst: t = y
return t
y = y.baseClass
proc lacksMTypeField*(typ: PType): bool {.inline.} =
## Returns true if the type is an object that lacks a m_type field.
## It doesn't check base classes.
(typ.sym != nil and sfPure in typ.sym.flags) or tfFinal in typ.flags
proc isObjLackingTypeField*(typ: PType): bool {.inline.} =
## Returns true if the type is an object that lacks a type field.
## Object types that store type headers are not final or pure and
## have inheritable root types, which are not pure, neither.
result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and
(typ.baseClass == nil) or isPureObject(typ))
include sizealignoffsetimpl
proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt =
computeSizeAlign(conf, typ)
result = typ.size
proc getReturnType*(s: PSym): PType =
# Obtains the return type of a iterator/proc/macro/template
assert s.kind in skProcKinds
result = s.typ.returnType
proc getAlign*(conf: ConfigRef; typ: PType): BiggestInt =
computeSizeAlign(conf, typ)
result = typ.align
proc getSize*(conf: ConfigRef; typ: PType): BiggestInt =
computeSizeAlign(conf, typ)
result = typ.size
proc setImportedTypeSize*(conf: ConfigRef, t: PType, size: int) =
t.size = size
if tfPacked in t.flags or size <= 1:
t.align = 1
elif size <= 2:
t.align = 2
elif size <= 4:
t.align = 4
else:
t.align = floatInt64Align(conf)
proc isConcept*(t: PType): bool=
case t.kind
of tyConcept: true
of tyCompositeTypeClass:
t.hasElementType and isConcept(t.elementType)
of tyGenericBody:
t.typeBodyImpl.kind == tyConcept
of tyGenericInvocation, tyGenericInst:
if t.baseClass.kind == tyGenericBody:
t.baseClass.typeBodyImpl.kind == tyConcept
else:
t.baseClass.kind == tyConcept
else: false
proc containsGenericTypeIter(t: PType, closure: RootRef): bool =
case t.kind
of tyStatic:
return t.n == nil
of tyTypeDesc:
if t.base.kind == tyNone: return true
if containsGenericTypeIter(t.base, closure): return true
return false
of GenericTypes + tyTypeClasses + {tyFromExpr}:
return true
of tyGenericInst:
return t.isConcept
else:
return false
proc containsGenericType*(t: PType): bool =
result = iterOverType(t, containsGenericTypeIter, nil)
proc containsUnresolvedTypeIter(t: PType, closure: RootRef): bool =
if tfUnresolved in t.flags: return true
case t.kind
of tyStatic:
return t.n == nil
of tyTypeDesc:
if t.base.kind == tyNone: return true
if containsUnresolvedTypeIter(t.base, closure): return true
return false
of tyGenericInvocation, tyGenericParam, tyFromExpr, tyAnything:
return true
else:
return false
proc containsUnresolvedType*(t: PType): bool =
result = iterOverType(t, containsUnresolvedTypeIter, nil)
proc baseOfDistinct*(t: PType; g: ModuleGraph; idgen: IdGenerator): PType =
if t.kind == tyDistinct:
result = t.elementType
else:
result = copyType(t, idgen, t.owner)
copyTypeProps(g, idgen.module, result, t)
var parent: PType = nil
var it = result
while it.kind in {tyPtr, tyRef, tyOwned}:
parent = it
it = it.elementType
if it.kind == tyDistinct and parent != nil:
parent[0] = it[0]
proc safeInheritanceDiff*(a, b: PType): int =
# same as inheritanceDiff but checks for tyError:
if a.kind == tyError or b.kind == tyError:
result = -1
else:
result = inheritanceDiff(a.skipTypes(skipPtrs), b.skipTypes(skipPtrs))
proc compatibleEffectsAux(se, re: PNode): bool =
if re.isNil: return false
for r in items(re):
block search:
for s in items(se):
if safeInheritanceDiff(r.typ, s.typ) <= 0:
break search
return false
result = true
proc isDefectException*(t: PType): bool
proc compatibleExceptions(se, re: PNode): bool =
if re.isNil: return false
for r in items(re):
block search:
if isDefectException(r.typ):
break search
for s in items(se):
if safeInheritanceDiff(r.typ, s.typ) <= 0:
break search
return false
result = true
proc hasIncompatibleEffect(se, re: PNode): bool =
result = false
if re.isNil: return false
for r in items(re):
for s in items(se):
if safeInheritanceDiff(r.typ, s.typ) != high(int):
return true
type
EffectsCompat* = enum
efCompat
efRaisesDiffer
efRaisesUnknown
efTagsDiffer
efTagsUnknown
efEffectsDelayed
efTagsIllegal
proc compatibleEffects*(formal, actual: PType): EffectsCompat =
# for proc type compatibility checking:
assert formal.kind == tyProc and actual.kind == tyProc
#if tfEffectSystemWorkaround in actual.flags:
# return efCompat
if formal.n[0].kind != nkEffectList or
actual.n[0].kind != nkEffectList:
return efTagsUnknown
var spec = formal.n[0]
if spec.len != 0:
var real = actual.n[0]
let se = spec[exceptionEffects]
# if 'se.kind == nkArgList' it is no formal type really, but a
# computed effect and as such no spec:
# 'r.msgHandler = if isNil(msgHandler): defaultMsgHandler else: msgHandler'
if not isNil(se) and se.kind != nkArgList:
# spec requires some exception or tag, but we don't know anything:
if real.len == 0: return efRaisesUnknown
let res = compatibleExceptions(se, real[exceptionEffects])
if not res: return efRaisesDiffer
let st = spec[tagEffects]
if not isNil(st) and st.kind != nkArgList:
# spec requires some exception or tag, but we don't know anything:
if real.len == 0: return efTagsUnknown
let res = compatibleEffectsAux(st, real[tagEffects])
if not res:
#if tfEffectSystemWorkaround notin actual.flags:
return efTagsDiffer
let sn = spec[forbiddenEffects]
if not isNil(sn) and sn.kind != nkArgList:
if 0 == real.len:
return efTagsUnknown
elif hasIncompatibleEffect(sn, real[tagEffects]):
return efTagsIllegal
for i in 1 ..< min(formal.n.len, actual.n.len):
if formal.n[i].sym.flags * {sfEffectsDelayed} != actual.n[i].sym.flags * {sfEffectsDelayed}:
result = efEffectsDelayed
break
result = efCompat
proc isCompileTimeOnly*(t: PType): bool {.inline.} =
result = t.kind in {tyTypeDesc, tyStatic, tyGenericParam}
proc containsCompileTimeOnly*(t: PType): bool =
if isCompileTimeOnly(t): return true
for a in t.kids:
if a != nil and isCompileTimeOnly(a):
return true
return false
proc safeSkipTypes*(t: PType, kinds: TTypeKinds): PType =
## same as 'skipTypes' but with a simple cycle detector.
result = t
var seen = initIntSet()
while result.kind in kinds and not containsOrIncl(seen, result.id):
result = skipModifier(result)
type
OrdinalType* = enum
NoneLike, IntLike, FloatLike
proc classify*(t: PType): OrdinalType =
## for convenient type checking:
if t == nil:
result = NoneLike
else:
case skipTypes(t, abstractVarRange).kind
of tyFloat..tyFloat128: result = FloatLike
of tyInt..tyInt64, tyUInt..tyUInt64, tyBool, tyChar, tyEnum:
result = IntLike
else: result = NoneLike
proc skipConv*(n: PNode): PNode =
result = n
case n.kind
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
# only skip the conversion if it doesn't lose too important information
# (see bug #1334)
if n[0].typ.classify == n.typ.classify:
result = n[0]
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
if n[1].typ.classify == n.typ.classify:
result = n[1]
else: discard
proc skipHidden*(n: PNode): PNode =
result = n
while true:
case result.kind
of nkHiddenStdConv, nkHiddenSubConv:
if result[1].typ.classify == result.typ.classify:
result = result[1]
else: break
of nkHiddenDeref, nkHiddenAddr:
result = result[0]
else: break
proc skipConvTakeType*(n: PNode): PNode =
result = n.skipConv
result.typ() = n.typ
proc isEmptyContainer*(t: PType): bool =
case t.kind
of tyUntyped, tyNil: result = true
of tyArray, tySet, tySequence, tyOpenArray, tyVarargs:
result = t.elementType.kind == tyEmpty
of tyGenericInst, tyAlias, tySink: result = isEmptyContainer(t.skipModifier)
else: result = false
proc takeType*(formal, arg: PType; g: ModuleGraph; idgen: IdGenerator): PType =
# param: openArray[string] = []
# [] is an array constructor of length 0 of type string!
if arg.kind == tyNil:
# and not (formal.kind == tyProc and formal.callConv == ccClosure):
result = formal
elif formal.kind in {tyOpenArray, tyVarargs, tySequence} and
arg.isEmptyContainer:
let a = copyType(arg.skipTypes({tyGenericInst, tyAlias}), idgen, arg.owner)
copyTypeProps(g, idgen.module, a, arg)
a[ord(arg.kind == tyArray)] = formal[0]
result = a
elif formal.kind in {tyTuple, tySet} and arg.kind == formal.kind:
result = formal
else:
result = arg
proc skipHiddenSubConv*(n: PNode; g: ModuleGraph; idgen: IdGenerator): PNode =
if n.kind == nkHiddenSubConv:
# param: openArray[string] = []
# [] is an array constructor of length 0 of type string!
let formal = n.typ
result = n[1]
let arg = result.typ
let dest = takeType(formal, arg, g, idgen)
if dest == arg and formal.kind != tyUntyped:
#echo n.info, " came here for ", formal.typeToString
result = n
else:
result = copyTree(result)
result.typ() = dest
else:
result = n
proc getProcConvMismatch*(c: ConfigRef, f, a: PType, rel = isNone): (set[ProcConvMismatch], TTypeRelation) =
## Returns a set of the reason of mismatch, and the relation for conversion.
result[1] = rel
if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
# Formal is pure, but actual is not
result[0].incl pcmNoSideEffect
result[1] = isNone
if tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {} and
optThreadAnalysis in c.globalOptions:
# noSideEffect implies ``tfThread``!
result[0].incl pcmNotGcSafe
result[1] = isNone
if f.flags * {tfIterator} != a.flags * {tfIterator}:
# One of them is an iterator so not convertible
result[0].incl pcmNotIterator
result[1] = isNone
if f.callConv != a.callConv:
# valid to pass a 'nimcall' thingie to 'closure':
if f.callConv == ccClosure and a.callConv == ccNimCall:
case result[1]
of isInferred: result[1] = isInferredConvertible
of isBothMetaConvertible: result[1] = isBothMetaConvertible
elif result[1] != isNone: result[1] = isConvertible
else: result[0].incl pcmDifferentCallConv
else:
result[1] = isNone
result[0].incl pcmDifferentCallConv
proc addPragmaAndCallConvMismatch*(message: var string, formal, actual: PType, conf: ConfigRef) =
assert formal.kind == tyProc and actual.kind == tyProc
let (convMismatch, _) = getProcConvMismatch(conf, formal, actual)
var
gotPragmas = ""
expectedPragmas = ""
for reason in convMismatch:
case reason
of pcmDifferentCallConv:
message.add "\n Calling convention mismatch: got '{.$1.}', but expected '{.$2.}'." % [$actual.callConv, $formal.callConv]
of pcmNoSideEffect:
expectedPragmas.add "noSideEffect, "
of pcmNotGcSafe:
expectedPragmas.add "gcsafe, "
of pcmNotIterator: discard
if expectedPragmas.len > 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