mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-07 13:33:22 +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>
585 lines
20 KiB
Nim
585 lines
20 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2020 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## New styled concepts for Nim. See https://github.com/nim-lang/RFCs/issues/168
|
|
## for details. Note this is a first implementation and only the "Concept matching"
|
|
## section has been implemented.
|
|
|
|
import ast, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
|
|
|
|
import std/intsets
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std/assertions
|
|
|
|
const
|
|
logBindings = when defined(debugConcepts): true else: false
|
|
|
|
## Code dealing with Concept declarations
|
|
## --------------------------------------
|
|
|
|
proc declareSelf(c: PContext; info: TLineInfo) =
|
|
## Adds the magical 'Self' symbols to the current scope.
|
|
let ow = getCurrOwner(c)
|
|
let s = newSym(skType, getIdent(c.cache, "Self"), c.idgen, ow, info)
|
|
s.typ = newType(tyTypeDesc, c.idgen, ow)
|
|
s.typ.flags.incl {tfUnresolved, tfPacked}
|
|
s.typ.add newType(tyEmpty, c.idgen, ow)
|
|
addDecl(c, s, info)
|
|
|
|
proc semConceptDecl(c: PContext; n: PNode): PNode =
|
|
## Recursive helper for semantic checking for the concept declaration.
|
|
## Currently we only support (possibly empty) lists of statements
|
|
## containing 'proc' declarations and the like.
|
|
case n.kind
|
|
of nkStmtList, nkStmtListExpr:
|
|
result = shallowCopy(n)
|
|
for i in 0..<n.len:
|
|
result[i] = semConceptDecl(c, n[i])
|
|
of nkProcDef..nkIteratorDef, nkFuncDef:
|
|
result = c.semExpr(c, n, {efWantStmt})
|
|
of nkTypeClassTy:
|
|
result = shallowCopy(n)
|
|
for i in 0..<n.len-1:
|
|
result[i] = n[i]
|
|
result[^1] = semConceptDecl(c, n[^1])
|
|
of nkCommentStmt:
|
|
result = n
|
|
else:
|
|
localError(c.config, n.info, "unexpected construct in the new-styled concept: " & renderTree(n))
|
|
result = n
|
|
|
|
proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
|
|
## Semantic checking for the concept declaration. Runs
|
|
## when we process the concept itself, not its matching process.
|
|
assert n.kind == nkTypeClassTy
|
|
inc c.inConceptDecl
|
|
openScope(c)
|
|
declareSelf(c, n.info)
|
|
result = semConceptDecl(c, n)
|
|
rawCloseScope(c)
|
|
dec c.inConceptDecl
|
|
|
|
## Concept matching
|
|
## ----------------
|
|
|
|
type
|
|
MatchFlags* = enum
|
|
mfDontBind # Do not bind generic parameters
|
|
mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand
|
|
|
|
MatchCon = object ## Context we pass around during concept matching.
|
|
bindings: LayeredIdTable
|
|
marker: IntSet ## Some protection against wild runaway recursions.
|
|
potentialImplementation: PType ## the concrete type that might match the concept we try to match.
|
|
magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and
|
|
## cannot be fixed that easily.
|
|
## Thus we special case it here.
|
|
concpt: PType ## current concept being evaluated
|
|
depthCount = 0
|
|
flags: set[MatchFlags]
|
|
|
|
MatchKind = enum
|
|
mkNoMatch, mkSubset, mkSame
|
|
|
|
const
|
|
asymmetricConceptParamMods = {tyVar, tySink, tyLent, tyOwned, tyAlias, tyInferred} # param modifiers that to not have to match implementation -> concept
|
|
bindableTypes = {tyGenericParam, tyOr, tyTypeDesc}
|
|
|
|
proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool
|
|
|
|
proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool
|
|
|
|
proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool
|
|
|
|
proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool
|
|
|
|
proc existingBinding(m: MatchCon; key: PType): PType =
|
|
## checks if we bound the type variable 'key' already to some
|
|
## concrete type.
|
|
result = m.bindings.lookup(key)
|
|
if result == nil:
|
|
result = key
|
|
|
|
const
|
|
ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyAlias, tyInferred}
|
|
|
|
proc unrollGenericParam(param: PType): PType =
|
|
result = param.skipTypes(ignorableForArgType)
|
|
while result.kind in {tyGenericParam, tyTypeDesc} and result.hasElementType and result.elementType.kind != tyNone:
|
|
result = result.elementType
|
|
|
|
proc bindParam(c: PContext, m: var MatchCon; key, v: PType): bool {. discardable .} =
|
|
if v.kind == tyTypeDesc:
|
|
return false
|
|
var value = unrollGenericParam(v)
|
|
if value.kind == tyGenericParam:
|
|
value = existingBinding(m, value)
|
|
if value.kind == tyGenericParam:
|
|
if value.hasElementType:
|
|
value = value.elementType
|
|
else:
|
|
return true
|
|
if value.kind == tyStatic:
|
|
return false
|
|
|
|
if m.magic in {mArrPut, mArrGet} and value.kind in arrPutGetMagicApplies:
|
|
value = value.last
|
|
|
|
let old = existingBinding(m, key)
|
|
if old != key:
|
|
# check previously bound value
|
|
if not matchType(c, old, value, m):
|
|
return false
|
|
elif key.hasElementType and key.elementType.kind != tyNone:
|
|
# check constaint
|
|
if matchType(c, unrollGenericParam(key), value, m) == false:
|
|
return false
|
|
|
|
when logBindings: echo "bind table adding '", key, "', ", value
|
|
assert value != nil
|
|
assert value.kind != tyVoid
|
|
m.bindings.put(key, value)
|
|
return true
|
|
|
|
proc defSignatureType(n: PNode): PType = n[0].sym.typ
|
|
|
|
proc conceptBody*(n: PType): PNode = n.n.lastSon
|
|
|
|
proc acceptsAllTypes(t: PType): bool=
|
|
result = false
|
|
if t.kind == tyAnything:
|
|
result = true
|
|
elif t.kind == tyGenericParam:
|
|
if tfImplicitTypeParam in t.flags:
|
|
result = true
|
|
if not t.hasElementType or t.elementType.kind == tyNone:
|
|
result = true
|
|
|
|
proc procDefSignature(s: PSym): PNode {. deprecated .} =
|
|
var nc = s.ast.copyNode()
|
|
for i in 0 .. 5:
|
|
nc.add s.ast[i]
|
|
nc
|
|
|
|
proc matchKids(c: PContext; f, a: PType; m: var MatchCon, start=0): bool=
|
|
result = true
|
|
for i in start ..< f.kidsLen - ord(f.kind in {tyGenericInst, tyGenericInvocation}):
|
|
if not matchType(c, f[i], a[i], m): return false
|
|
|
|
iterator traverseTyOr(t: PType): PType {. closure .}=
|
|
for i in t.kids:
|
|
case i.kind:
|
|
of tyGenericParam:
|
|
if i.hasElementType:
|
|
for s in traverseTyOr(i.elementType):
|
|
yield s
|
|
else:
|
|
yield i
|
|
else:
|
|
yield i
|
|
|
|
proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon): bool =
|
|
assert not(potentialImpl.reduceToBase.kind == tyConcept)
|
|
let concpt = f.reduceToBase
|
|
if m.depthCount > 0:
|
|
# concepts that are more then 2 levels deep are treated like
|
|
# tyAnything to stop dependencies from getting out of control
|
|
return true
|
|
var efPot = potentialImpl
|
|
if potentialImpl.isSelf:
|
|
if m.concpt.n == concpt.n:
|
|
return true
|
|
efPot = m.potentialImplementation
|
|
|
|
var oldBindings = m.bindings
|
|
m.bindings = newTypeMapLayer(m.bindings)
|
|
let oldPotentialImplementation = m.potentialImplementation
|
|
m.potentialImplementation = efPot
|
|
let oldConcept = m.concpt
|
|
m.concpt = concpt
|
|
|
|
var invocation: PType = nil
|
|
if f.kind in {tyGenericInvocation, tyGenericInst}:
|
|
invocation = f
|
|
inc m.depthCount
|
|
result = processConcept(c, concpt, invocation, oldBindings, m)
|
|
dec m.depthCount
|
|
m.potentialImplementation = oldPotentialImplementation
|
|
m.concpt = oldConcept
|
|
m.bindings = oldBindings
|
|
|
|
proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool=
|
|
if fn.kind != an.kind:
|
|
return false
|
|
if fn[namePos].sym.name != an[namePos].sym.name:
|
|
return false
|
|
let
|
|
ft = fn.defSignatureType
|
|
at = an.defSignatureType
|
|
if ft.len != at.len:
|
|
return false
|
|
|
|
for i in 1 ..< ft.n.len:
|
|
m.bindings = m.bindings.newTypeMapLayer()
|
|
|
|
let aType = at.n[i].typ
|
|
let fType = ft.n[i].typ
|
|
|
|
if aType.isSelf and fType.isSelf:
|
|
continue
|
|
|
|
if not matchType(c, fType, aType, m):
|
|
m.bindings.setToPreviousLayer()
|
|
return false
|
|
result = true
|
|
if not matchReturnType(c, ft.returnType, at.returnType, m):
|
|
m.bindings.setToPreviousLayer()
|
|
result = false
|
|
|
|
proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind =
|
|
# XXX: In the future this may need extra parameters to carry info for container types
|
|
if fc.n == ac.n:
|
|
# This will have to take generic parameters into account at some point
|
|
return mkSame
|
|
let
|
|
fn = fc.conceptBody
|
|
an = ac.conceptBody
|
|
sameLen = fc.len == ac.len
|
|
var match = false
|
|
for fdef in fn:
|
|
var cmpResult = false
|
|
for ia, ndef in an:
|
|
match = cmpConceptDefs(c, fdef, ndef, m)
|
|
if match:
|
|
break
|
|
if not match:
|
|
return mkNoMatch
|
|
return mkSubset
|
|
|
|
proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
|
|
## The heart of the concept matching process. 'f' is the formal parameter of some
|
|
## routine inside the concept that we're looking for. 'a' is the formal parameter
|
|
## of a routine that might match.
|
|
|
|
var
|
|
a = ao
|
|
f = fo
|
|
|
|
if a.kind in bindableTypes:
|
|
a = existingBinding(m, ao)
|
|
if a == ao and a.kind == tyGenericParam and a.hasElementType and a.elementType.kind != tyNone:
|
|
a = a.elementType
|
|
|
|
if f.isConcept:
|
|
if a.acceptsAllTypes:
|
|
return false
|
|
if a.isConcept:
|
|
# if f is a subset of a then any match to a will also match f. Not the other way around
|
|
return conceptsMatch(c, a.reduceToBase, f.reduceToBase, m) >= mkSubset
|
|
else:
|
|
return matchConceptToImpl(c, f, a, m)
|
|
|
|
result = false
|
|
|
|
case f.kind
|
|
of tyAlias:
|
|
result = matchType(c, f.skipModifier, a, m)
|
|
of tyTypeDesc:
|
|
if isSelf(f):
|
|
let ua = a.skipTypes(asymmetricConceptParamMods)
|
|
if m.magic in {mArrPut, mArrGet}:
|
|
if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies:
|
|
bindParam(c, m, a, last m.potentialImplementation)
|
|
result = true
|
|
#elif ua.isConcept:
|
|
# result = matchType(c, m.concpt, ua, m)
|
|
else:
|
|
result = matchType(c, a.skipTypes(ignorableForArgType), m.potentialImplementation, m)
|
|
else:
|
|
if a.kind == tyTypeDesc:
|
|
if not(a.hasElementType) or a.elementType.kind == tyNone:
|
|
result = true
|
|
elif f.hasElementType:
|
|
result = matchType(c, f.elementType, a.elementType, m)
|
|
of tyVar, tySink, tyLent, tyOwned:
|
|
# modifiers in the concept must be there in the actual implementation
|
|
# too but not vice versa.
|
|
if a.kind == f.kind:
|
|
result = matchType(c, f.elementType, a.elementType, m)
|
|
elif m.magic == mArrPut:
|
|
result = matchType(c, f.elementType, a, m)
|
|
of tyEnum, tyObject, tyDistinct:
|
|
if a.kind in ignorableForArgType:
|
|
result = matchType(c, f, a.skipTypes(ignorableForArgType), m)
|
|
else:
|
|
result = sameType(f, a)
|
|
of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid:
|
|
result = a.skipTypes(ignorableForArgType).kind == f.kind
|
|
of tyBool, tyChar, tyInt..tyUInt64:
|
|
let ak = a.skipTypes(ignorableForArgType)
|
|
result = ak.kind == f.kind or ak.kind == tyOrdinal or
|
|
(ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal)
|
|
of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr:
|
|
if f.kind == tyArray and f.kidsLen == 3 and a.kind == tyArray:
|
|
# XXX: this is a work-around!
|
|
# system.nim creates these for the magic array typeclass
|
|
result = true
|
|
else:
|
|
let ak = a.skipTypes(ignorableForArgType - {f.kind})
|
|
if ak.kind == f.kind and f.kidsLen == ak.kidsLen:
|
|
result = matchKids(c, f, ak, m)
|
|
of tyGenericInvocation, tyGenericInst:
|
|
result = false
|
|
let ea = a.skipTypes(ignorableForArgType)
|
|
if ea.kind in {tyGenericInst, tyGenericInvocation}:
|
|
var
|
|
k1 = f.kidsLen - ord(f.kind == tyGenericInst)
|
|
k2 = ea.kidsLen - ord(ea.kind == tyGenericInst)
|
|
if sameType(f.genericHead, ea.genericHead) and k1 == k2:
|
|
for i in 1 ..< k2:
|
|
if not matchType(c, f[i], ea[i], m):
|
|
break
|
|
result = true
|
|
of tyOrdinal:
|
|
result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
|
|
of tyStatic:
|
|
var scomp = f.base
|
|
if scomp.kind == tyGenericParam:
|
|
if f.base.kidsLen > 0:
|
|
scomp = scomp.base
|
|
if a.kind == tyStatic:
|
|
result = matchType(c, scomp, a.base, m)
|
|
else:
|
|
result = matchType(c, scomp, a, m)
|
|
of tyGenericParam:
|
|
if a.acceptsAllTypes:
|
|
discard bindParam(c, m, f, a)
|
|
result = f.acceptsAllTypes
|
|
else:
|
|
result = bindParam(c, m, f, a)
|
|
of tyAnything:
|
|
result = true
|
|
of tyNot:
|
|
if a.kind == tyNot:
|
|
result = matchType(c, f.elementType, a.elementType, m)
|
|
else:
|
|
m.bindings = m.bindings.newTypeMapLayer()
|
|
result = not matchType(c, f.elementType, a, m)
|
|
m.bindings.setToPreviousLayer()
|
|
of tyAnd:
|
|
m.bindings = m.bindings.newTypeMapLayer()
|
|
result = true
|
|
for ff in traverseTyOr(f):
|
|
let r = matchType(c, ff, a, m)
|
|
if not r:
|
|
m.bindings.setToPreviousLayer()
|
|
result = false
|
|
break
|
|
of tyGenericBody:
|
|
var ak = a
|
|
if a.kind == tyGenericBody:
|
|
ak = last(a)
|
|
result = matchType(c, last(f), ak, m)
|
|
of tyCompositeTypeClass:
|
|
if a.kind == tyCompositeTypeClass:
|
|
result = matchKids(c, f, a, m)
|
|
else:
|
|
result = matchType(c, last(f), a, m)
|
|
of tyBuiltInTypeClass:
|
|
let target = f.genericHead.kind
|
|
result = a.skipTypes(ignorableForArgType).reduceToBase.kind == target
|
|
of tyOr:
|
|
if a.kind == tyOr:
|
|
var covered = 0
|
|
for ff in traverseTyOr(f):
|
|
for aa in traverseTyOr(a):
|
|
m.bindings = m.bindings.newTypeMapLayer()
|
|
let r = matchType(c, ff, aa, m)
|
|
if r:
|
|
inc covered
|
|
break
|
|
m.bindings.setToPreviousLayer()
|
|
|
|
result = covered >= a.kidsLen
|
|
else:
|
|
for ff in f.kids:
|
|
m.bindings = m.bindings.newTypeMapLayer()
|
|
result = matchType(c, ff, a, m)
|
|
if result: break # and remember the binding!
|
|
m.bindings.setToPreviousLayer()
|
|
else:
|
|
result = false
|
|
if result and ao.kind == tyGenericParam:
|
|
let bf = if f.isSelf: m.potentialImplementation else: f
|
|
if bindParam(c, m, ao, bf):
|
|
when logBindings: echo " ^ reverse binding"
|
|
|
|
proc checkConstraint(c: PContext; f, a: PType; m: var MatchCon): bool =
|
|
result = matchType(c, f, a, m) or matchType(c, a, f, m)
|
|
|
|
proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool =
|
|
## Like 'matchType' but with extra logic dealing with proc return types
|
|
## which can be nil or the 'void' type.
|
|
if f.isEmptyType:
|
|
result = a.isEmptyType
|
|
elif a == nil:
|
|
result = false
|
|
else:
|
|
result = checkConstraint(c, f, a, m)
|
|
|
|
proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool =
|
|
## Checks if 'candidate' matches 'n' from the concept body. 'n' is a nkProcDef
|
|
## or similar.
|
|
|
|
# watch out: only add bindings after a completely successful match.
|
|
m.bindings = m.bindings.newTypeMapLayer()
|
|
|
|
let can = candidate.typ.n
|
|
let con = defSignatureType(n).n
|
|
if can.len < con.len:
|
|
# too few arguments, cannot be a match:
|
|
return false
|
|
|
|
if can.len > con.len:
|
|
# too many arguments (not optional)
|
|
for i in con.len ..< can.len:
|
|
if can[i].sym.ast == nil:
|
|
return false
|
|
|
|
when defined(debugConcepts):
|
|
echo "considering: ", renderTree(candidate.procDefSignature), " ", candidate.magic
|
|
|
|
let common = min(can.len, con.len)
|
|
for i in 1 ..< common:
|
|
if not checkConstraint(c, con[i].typ, can[i].typ, m):
|
|
m.bindings.setToPreviousLayer()
|
|
return false
|
|
|
|
if not matchReturnType(c, n.defSignatureType.returnType, candidate.typ.returnType, m):
|
|
m.bindings.setToPreviousLayer()
|
|
return false
|
|
|
|
# all other parameters have to be optional parameters:
|
|
for i in common ..< can.len:
|
|
assert can[i].kind == nkSym
|
|
if can[i].sym.ast == nil:
|
|
# has too many arguments one of which is not optional:
|
|
m.bindings.setToPreviousLayer()
|
|
return false
|
|
|
|
return true
|
|
|
|
proc matchSyms(c: PContext, n: PNode; kinds: set[TSymKind]; m: var MatchCon): bool =
|
|
## Walk the current scope, extract candidates which the same name as 'n[namePos]',
|
|
## 'n' is the nkProcDef or similar from the concept that we try to match.
|
|
result = false
|
|
var candidates = searchScopes(c, n[namePos].sym.name, kinds)
|
|
searchImportsAll(c, n[namePos].sym.name, kinds, candidates)
|
|
for candidate in candidates:
|
|
m.magic = candidate.magic
|
|
if matchSym(c, candidate, n, m):
|
|
result = true
|
|
break
|
|
|
|
proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
|
|
## Traverse the concept's AST ('n') and see if every declaration inside 'n'
|
|
## can be matched with the current scope.
|
|
case n.kind
|
|
of nkStmtList, nkStmtListExpr:
|
|
for i in 0..<n.len:
|
|
if not conceptMatchNode(c, n[i], m):
|
|
return false
|
|
return true
|
|
of nkProcDef, nkFuncDef:
|
|
# procs match any of: proc, template, macro, func, method, converter.
|
|
# The others are more specific.
|
|
# XXX: Enforce .noSideEffect for 'nkFuncDef'? But then what are the use cases...
|
|
const filter = {skProc, skTemplate, skMacro, skFunc, skMethod, skConverter}
|
|
result = matchSyms(c, n, filter, m)
|
|
of nkTemplateDef:
|
|
result = matchSyms(c, n, {skTemplate}, m)
|
|
of nkMacroDef:
|
|
result = matchSyms(c, n, {skMacro}, m)
|
|
of nkConverterDef:
|
|
result = matchSyms(c, n, {skConverter}, m)
|
|
of nkMethodDef:
|
|
result = matchSyms(c, n, {skMethod}, m)
|
|
of nkIteratorDef:
|
|
result = matchSyms(c, n, {skIterator}, m)
|
|
of nkCommentStmt:
|
|
result = true
|
|
else:
|
|
# error was reported earlier.
|
|
result = false
|
|
|
|
proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; m: var MatchCon) =
|
|
# invocation != nil means we have a non-atomic concept:
|
|
if invocation != nil and invocation.kind == tyGenericInvocation:
|
|
assert concpt.sym.typ.kind == tyGenericBody
|
|
|
|
for i in 0 .. concpt.sym.typ.len - 1:
|
|
let thisSym = concpt.sym.typ[i]
|
|
if lookup(bindings, thisSym) != nil:
|
|
# dont trust the bindings over existing ones
|
|
continue
|
|
let found = m.bindings.lookup(thisSym)
|
|
if found != nil:
|
|
when logBindings: echo "Invocation bind: ", thisSym, " ", found
|
|
bindings.put(thisSym, found)
|
|
|
|
# bind even more generic parameters
|
|
let genBody = invocation.base
|
|
assert genBody.kind == tyGenericBody
|
|
for i in FirstGenericParamAt ..< invocation.kidsLen:
|
|
let bpram = genBody[i - 1]
|
|
if lookup(bindings, invocation[i]) != nil:
|
|
# dont trust the bindings over existing ones
|
|
continue
|
|
let boundV = lookup(bindings, bpram)
|
|
when logBindings: echo "generic body bind: '", invocation[i], "' '", boundV, "'"
|
|
if boundV != nil:
|
|
bindings.put(invocation[i], boundV)
|
|
bindings.put(concpt, m.potentialImplementation)
|
|
|
|
proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool =
|
|
m.bindings = m.bindings.newTypeMapLayer()
|
|
if invocation != nil and invocation.kind == tyGenericInst:
|
|
let genericBody = invocation.base
|
|
for i in 1..<invocation.kidsLen-1:
|
|
# instGenericContainer can bind `tyVoid`
|
|
if invocation[i].kind != tyVoid:
|
|
bindParam(c, m, genericBody[i-1], invocation[i])
|
|
result = conceptMatchNode(c, concpt.conceptBody, m)
|
|
if result and mfDontBind notin m.flags:
|
|
fixBindings(bindings, concpt, invocation, m)
|
|
|
|
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType, flags: set[MatchFlags] = {}): bool =
|
|
## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but
|
|
## we extract its AST via 'concpt.n.lastSon'). 'arg' is the type that might fulfill the
|
|
## concept's requirements. If so, we return true and fill the 'bindings' with pairs of
|
|
## (typeVar, instance) pairs. ('typeVar' is usually simply written as a generic 'T'.)
|
|
## 'invocation' can be nil for atomic concepts. For non-atomic concepts, it contains the
|
|
## `C[S, T]` parent type that we look for. We need this because we need to store bindings
|
|
## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that
|
|
## we do not add any bindings at all on an unsuccessful match!
|
|
var m = MatchCon(bindings: bindings, potentialImplementation: arg, concpt: concpt, flags: flags)
|
|
if arg.isConcept:
|
|
result = conceptsMatch(c, concpt.reduceToBase, arg.reduceToBase, m) >= mkSubset
|
|
elif arg.acceptsAllTypes:
|
|
# XXX: I think this is wrong, or at least partially wrong. Can still test ambiguous types
|
|
result = false
|
|
elif mfCheckGeneric in m.flags:
|
|
# prioritize concepts the least. Specifically if the arg is not a catch all as per above
|
|
result = true
|
|
else:
|
|
result = processConcept(c, concpt, invocation, bindings, m)
|
|
|
|
|