mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
Yet another one of these. Multiple changes piled up in this one. I've
only minimally cleaned it for now (debug code is still here etc). Just
want to start putting this up so I might get feedback. I know this is a
lot and you all are busy with bigger things. As per my last PR, this
might just contain changes that are not ready.
### concept instantiation uniqueness
It has already been said that concepts like `ArrayLike[int]` is not
unique for each matching type of that concept. Likewise the compiler
needs to instantiate a new proc for each unique *bound* type not each
unique invocation of `ArrayLike`
### generic parameter bindings
Couple of things here. The code in sigmatch has to give it's bindings to
the code in concepts, else the information is lost in that step. The
code that prepares the generic variables bound in concepts was also
changed slightly. Net effect is that it works better.
I did choose to use the `LayedIdTable` instead of the `seq`s in
`concepts.nim`. This was mostly to avoid confusing myself. It also
avoids some unnecessary movings around. I wouldn't doubt this is
slightly less performant, but not much in the grand scheme of things and
I would prefer to keep things as easy to understand as possible for as
long as possible because this stuff can get confusing.
### various fixes in the matching logic
Certain forms of modifiers like `var` and generic types like
`tyGenericInst` and `tyGenericInvocation` have logic adjustments based
on my testing and usage
### signature matching method adjustment
This is the weird one, like my last PR. I thought a lot about the
feedback from my last attempt and this is what I came up with. Perhaps
unfortunately I am preoccupied with a slight grey area. consider the
follwing:
```nim
type
C1 = concept
proc p[T](s: Self; x: T)
C2[T] = concept
proc p(s: Self; x: T)
```
It would be temping to say that these are the same, but I don't think
they are. `C2` makes each invocation distinct, and this has important
implications in the type system. eg `C2[int]` is not the same type as
`C2[string]` and this means that signatures are meant to accept a type
that only matches `p` for a single type per unique binding. For `C1` all
are the same and the binding `p` accepts multiple types. There are
multiple variations of this type classes, `tyAnything` and the like.
The make things more complicated, an implementation might match:
```nim
type
A = object
C3 = concept
proc p(s: Self; x: A)
```
if the implementation defines:
```nim
proc p(x: Impl; y: object)
```
while a concept that fits `C2` may be satisfied by something like:
```nim
proc p(x: Impl; y: int)
proc spring[T](x: C2[T])
```
it just depends. None of this is really a problem, it just seems to
provoke some more logic in `concepts.nim` that makes all of this (appear
to?) work. The logic checks for both kinds of matches with a couple of
caveats. The fist is that some unbind-able arrangements may be matched
during overload resolution. I don't think this is avoidable and I
actually think this is a good way to get a failed compilation. So, first
note imo is that failing during binding is preferred to forcing the
programming to write annoying stub procs and putting insane gymnastics
in the compiler. Second thing is: I think this logic is way to accepting
for some parts of overload resolutions. Particularly in `checkGeneric`
when disambiguation is happening. Things get hard to understand for me
here. ~~I made it so the implicit bindings to not count during
disambiguation~~. I still need to test this more, but the thought is
that it would help curb excessive ambiguity errors.
Again, I'm sorry for this being so many changes. It's probably
inconvenient.
---------
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
2109 lines
73 KiB
Nim
2109 lines
73 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 != 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 = "<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, 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 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 isTupleRecursive(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 isTupleRecursive(a, cycleDetectorCopy):
|
|
return true
|
|
of tyRef, tyPtr, tyVar, tyLent, tySink,
|
|
tyArray, tyUncheckedArray, tySequence, tyDistinct:
|
|
return isTupleRecursive(t.elementType, cycleDetector)
|
|
of tyAlias, tyGenericInst:
|
|
return isTupleRecursive(t.skipModifier, cycleDetector)
|
|
else:
|
|
return false
|
|
|
|
proc isTupleRecursive*(t: PType): bool =
|
|
var cycleDetector = initIntSet()
|
|
isTupleRecursive(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
|