mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-14 07:33:45 +00:00
new-style concepts implementation, WIP (#15251)
* fixes #15210 [backport:1.2] * make tests green * make ordinal work * makes Swapable test compile * make Indexable example work * concepts: 'self' is now 'Self' * concepts: make Dictionary example compile * document the new concept implementation * concepts: make typeDesc work properly * concepts: allow documentation comments (d'oh)
This commit is contained in:
@@ -434,9 +434,8 @@ type
|
||||
# instantiation and prior to this it has the potential to
|
||||
# be any type.
|
||||
|
||||
tyOptDeprecated
|
||||
# 'out' parameter. Comparable to a 'var' parameter but every
|
||||
# path must assign a value to it before it can be read from.
|
||||
tyConcept
|
||||
# new style concept.
|
||||
|
||||
tyVoid
|
||||
# now different from tyEmpty, hurray!
|
||||
@@ -1975,3 +1974,7 @@ proc toHumanStr*(kind: TTypeKind): string =
|
||||
|
||||
proc skipAddr*(n: PNode): PNode {.inline.} =
|
||||
(if n.kind == nkHiddenAddr: n[0] else: n)
|
||||
|
||||
proc isNewStyleConcept*(n: PNode): bool {.inline.} =
|
||||
assert n.kind == nkTypeClassTy
|
||||
result = n[0].kind == nkEmpty
|
||||
|
||||
340
compiler/concepts.nim
Normal file
340
compiler/concepts.nim
Normal file
@@ -0,0 +1,340 @@
|
||||
#
|
||||
#
|
||||
# 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, intsets
|
||||
|
||||
from magicsys import addSonSkipIntLit
|
||||
|
||||
const
|
||||
logBindings = 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"), nextSymId(c.idgen), ow, info)
|
||||
s.typ = newType(tyTypeDesc, nextTypeId(c.idgen), ow)
|
||||
s.typ.flags.incl {tfUnresolved, tfPacked}
|
||||
s.typ.add newType(tyEmpty, nextTypeId(c.idgen), ow)
|
||||
addDecl(c, s, info)
|
||||
|
||||
proc isSelf*(t: PType): bool {.inline.} =
|
||||
## is this the magical 'Self' type?
|
||||
t.kind == tyTypeDesc and tfPacked in t.flags
|
||||
|
||||
proc makeTypeDesc*(c: PContext, typ: PType): PType =
|
||||
if typ.kind == tyTypeDesc and not isSelf(typ):
|
||||
result = typ
|
||||
else:
|
||||
result = newTypeS(tyTypeDesc, c)
|
||||
incl result.flags, tfCheckedForDestructor
|
||||
result.addSonSkipIntLit(typ, c.idgen)
|
||||
|
||||
proc semConceptDecl(c: PContext; n: PNode): PNode =
|
||||
## Recursive helper for semantic checking for the concept declaration.
|
||||
## Currently we only support 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])
|
||||
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
|
||||
MatchCon = object ## Context we pass around during concept matching.
|
||||
inferred: seq[(PType, PType)] ## we need a seq here so that we can easily undo inferences \
|
||||
## that turned out to be wrong.
|
||||
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.
|
||||
|
||||
proc existingBinding(m: MatchCon; key: PType): PType =
|
||||
## checks if we bound the type variable 'key' already to some
|
||||
## concrete type.
|
||||
for i in 0..<m.inferred.len:
|
||||
if m.inferred[i][0] == key: return m.inferred[i][1]
|
||||
return nil
|
||||
|
||||
proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool
|
||||
|
||||
proc matchType(c: PContext; f, a: 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.
|
||||
const
|
||||
ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred}
|
||||
case f.kind
|
||||
of tyAlias:
|
||||
result = matchType(c, f.lastSon, a, m)
|
||||
of tyTypeDesc:
|
||||
if isSelf(f):
|
||||
#let oldLen = m.inferred.len
|
||||
result = matchType(c, a, m.potentialImplementation, m)
|
||||
#echo "self is? ", result, " ", a.kind, " ", a, " ", m.potentialImplementation, " ", m.potentialImplementation.kind
|
||||
#m.inferred.setLen oldLen
|
||||
#echo "A for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)
|
||||
else:
|
||||
if a.kind == tyTypeDesc and f.len == a.len:
|
||||
for i in 0..<a.len:
|
||||
if not matchType(c, f[i], a[i], m): return false
|
||||
return true
|
||||
|
||||
of tyGenericInvocation:
|
||||
if a.kind == tyGenericInst and a[0].kind == tyGenericBody:
|
||||
if sameType(f[0], a[0]) and f.len == a.len-1:
|
||||
for i in 1 ..< f.len:
|
||||
if not matchType(c, f[i], a[i], m): return false
|
||||
return true
|
||||
of tyGenericParam:
|
||||
let ak = a.skipTypes({tyVar, tySink, tyLent, tyOwned})
|
||||
if ak.kind in {tyTypeDesc, tyStatic} and not isSelf(ak):
|
||||
result = false
|
||||
else:
|
||||
let old = existingBinding(m, f)
|
||||
if old == nil:
|
||||
if f.len > 0 and f[0].kind != tyNone:
|
||||
# also check the generic's constraints:
|
||||
let oldLen = m.inferred.len
|
||||
result = matchType(c, f[0], a, m)
|
||||
m.inferred.setLen oldLen
|
||||
if result:
|
||||
when logBindings: echo "A adding ", f, " ", ak
|
||||
m.inferred.add((f, ak))
|
||||
elif m.magic == mArrGet and ak.kind in {tyArray, tyOpenArray, tySequence, tyVarargs, tyCString, tyString}:
|
||||
when logBindings: echo "B adding ", f, " ", lastSon ak
|
||||
m.inferred.add((f, lastSon ak))
|
||||
result = true
|
||||
else:
|
||||
when logBindings: echo "C adding ", f, " ", ak
|
||||
m.inferred.add((f, ak))
|
||||
#echo "binding ", typeToString(ak), " to ", typeToString(f)
|
||||
result = true
|
||||
elif not m.marker.containsOrIncl(old.id):
|
||||
result = matchType(c, old, ak, m)
|
||||
if m.magic == mArrPut and ak.kind == tyGenericParam:
|
||||
result = true
|
||||
#echo "B for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)
|
||||
|
||||
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.sons[0], a.sons[0], m)
|
||||
elif m.magic == mArrPut:
|
||||
result = matchType(c, f.sons[0], a, m)
|
||||
else:
|
||||
result = false
|
||||
of tyEnum, tyObject, tyDistinct:
|
||||
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.len > 0 and ak[0].kind == tyOrdinal)
|
||||
of tyConcept:
|
||||
let oldLen = m.inferred.len
|
||||
let oldPotentialImplementation = m.potentialImplementation
|
||||
m.potentialImplementation = a
|
||||
result = conceptMatchNode(c, f.n.lastSon, m)
|
||||
m.potentialImplementation = oldPotentialImplementation
|
||||
if not result:
|
||||
m.inferred.setLen oldLen
|
||||
of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr,
|
||||
tyGenericInst:
|
||||
let ak = a.skipTypes(ignorableForArgType - {f.kind})
|
||||
if ak.kind == f.kind and f.len == ak.len:
|
||||
for i in 0..<ak.len:
|
||||
if not matchType(c, f[i], ak[i], m): return false
|
||||
return true
|
||||
of tyOr:
|
||||
let oldLen = m.inferred.len
|
||||
if a.kind == tyOr:
|
||||
# say the concept requires 'int|float|string' if the potentialImplementation
|
||||
# says 'int|string' that is good enough.
|
||||
var covered = 0
|
||||
for i in 0..<f.len:
|
||||
for j in 0..<a.len:
|
||||
let oldLenB = m.inferred.len
|
||||
let r = matchType(c, f[i], a[j], m)
|
||||
if r:
|
||||
inc covered
|
||||
break
|
||||
m.inferred.setLen oldLenB
|
||||
|
||||
result = covered >= a.len
|
||||
if not result:
|
||||
m.inferred.setLen oldLen
|
||||
else:
|
||||
for i in 0..<f.len:
|
||||
result = matchType(c, f[i], a, m)
|
||||
if result: break # and remember the binding!
|
||||
m.inferred.setLen oldLen
|
||||
of tyNot:
|
||||
if a.kind == tyNot:
|
||||
result = matchType(c, f[0], a[0], m)
|
||||
else:
|
||||
let oldLen = m.inferred.len
|
||||
result = not matchType(c, f[0], a, m)
|
||||
m.inferred.setLen oldLen
|
||||
of tyAnything:
|
||||
result = true
|
||||
of tyOrdinal:
|
||||
result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
|
||||
else:
|
||||
result = false
|
||||
|
||||
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 = matchType(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.
|
||||
let oldLen = m.inferred.len
|
||||
|
||||
let can = candidate.typ.n
|
||||
let con = n[0].sym.typ.n
|
||||
|
||||
if can.len < con.len:
|
||||
# too few arguments, cannot be a match:
|
||||
return false
|
||||
|
||||
let common = min(can.len, con.len)
|
||||
for i in 1 ..< common:
|
||||
if not matchType(c, con[i].typ, can[i].typ, m):
|
||||
m.inferred.setLen oldLen
|
||||
return false
|
||||
|
||||
if not matchReturnType(c, n[0].sym.typ.sons[0], candidate.typ.sons[0], m):
|
||||
m.inferred.setLen oldLen
|
||||
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.inferred.setLen oldLen
|
||||
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.
|
||||
let candidates = searchInScopesFilterBy(c, n[namePos].sym.name, kinds)
|
||||
for candidate in candidates:
|
||||
#echo "considering ", typeToString(candidate.typ), " ", candidate.magic
|
||||
m.magic = candidate.magic
|
||||
if matchSym(c, candidate, n, m): return true
|
||||
result = false
|
||||
|
||||
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)
|
||||
else:
|
||||
# error was reported earlier.
|
||||
result = false
|
||||
|
||||
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var TIdTable; invocation: PType): 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 fullfill 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(inferred: @[], potentialImplementation: arg)
|
||||
result = conceptMatchNode(c, concpt.n.lastSon, m)
|
||||
if result:
|
||||
for (a, b) in m.inferred:
|
||||
if b.kind == tyGenericParam:
|
||||
var dest = b
|
||||
while true:
|
||||
dest = existingBinding(m, dest)
|
||||
if dest == nil or dest.kind != tyGenericParam: break
|
||||
if dest != nil:
|
||||
bindings.idTablePut(a, dest)
|
||||
when logBindings: echo "A bind ", a, " ", dest
|
||||
else:
|
||||
bindings.idTablePut(a, b)
|
||||
when logBindings: echo "B bind ", a, " ", b
|
||||
# we have a match, so bind 'arg' itself to 'concpt':
|
||||
bindings.idTablePut(concpt, arg)
|
||||
# invocation != nil means we have a non-atomic concept:
|
||||
if invocation != nil and arg.kind == tyGenericInst and invocation.len == arg.len-1:
|
||||
# bind even more generic parameters
|
||||
assert invocation.kind == tyGenericInvocation
|
||||
for i in 1 ..< invocation.len:
|
||||
bindings.idTablePut(invocation[i], arg[i])
|
||||
@@ -211,7 +211,7 @@ proc mapType(typ: PType): TJSTypeKind =
|
||||
else: result = etyNone
|
||||
of tyProc: result = etyProc
|
||||
of tyCString: result = etyString
|
||||
of tyOptDeprecated: doAssert false
|
||||
of tyConcept: doAssert false
|
||||
|
||||
proc mapType(p: PProc; typ: PType): TJSTypeKind =
|
||||
result = mapType(typ)
|
||||
|
||||
@@ -896,7 +896,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of tyOrdinal, tyRange, tyInferred,
|
||||
tyGenericInst, tyAlias, tySink:
|
||||
fillBody(c, lastSon(t), body, x, y)
|
||||
of tyOptDeprecated: doAssert false
|
||||
of tyConcept: doAssert false
|
||||
|
||||
proc produceSymDistinctType(g: ModuleGraph; c: PContext; typ: PType;
|
||||
kind: TTypeAttachedOp; info: TLineInfo;
|
||||
|
||||
@@ -2000,12 +2000,18 @@ proc parseTypeClass(p: var Parser): PNode =
|
||||
#| &IND{>} stmt
|
||||
result = newNodeP(nkTypeClassTy, p)
|
||||
getTok(p)
|
||||
var args = newNodeP(nkArgList, p)
|
||||
result.add(args)
|
||||
args.add(p.parseTypeClassParam)
|
||||
while p.tok.tokType == tkComma:
|
||||
getTok(p)
|
||||
if p.tok.tokType == tkComment:
|
||||
skipComment(p, result)
|
||||
|
||||
if p.tok.indent < 0:
|
||||
var args = newNodeP(nkArgList, p)
|
||||
result.add(args)
|
||||
args.add(p.parseTypeClassParam)
|
||||
while p.tok.tokType == tkComma:
|
||||
getTok(p)
|
||||
args.add(p.parseTypeClassParam)
|
||||
else:
|
||||
result.add(p.emptyNode) # see ast.isNewStyleConcept
|
||||
if p.tok.tokType == tkCurlyDotLe and p.validInd:
|
||||
result.add(parsePragma(p))
|
||||
else:
|
||||
|
||||
@@ -17,7 +17,7 @@ import
|
||||
intsets, transf, vmdef, vm, aliases, cgmeth, lambdalifting,
|
||||
evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity,
|
||||
lowerings, plugins/active, lineinfos, strtabs, int128,
|
||||
isolation_check, typeallowed, modulegraphs, enumtostr
|
||||
isolation_check, typeallowed, modulegraphs, enumtostr, concepts
|
||||
|
||||
when defined(nimfix):
|
||||
import nimfix/prettybase
|
||||
|
||||
@@ -153,7 +153,7 @@ type
|
||||
suggestionsMade*: bool
|
||||
isAmbiguous*: bool # little hack
|
||||
features*: set[Feature]
|
||||
inTypeContext*: int
|
||||
inTypeContext*, inConceptDecl*: int
|
||||
unusedImports*: seq[(PSym, TLineInfo)]
|
||||
exportIndirections*: HashSet[(int, int)]
|
||||
lastTLineInfo*: TLineInfo
|
||||
@@ -410,14 +410,6 @@ proc makeVarType*(owner: PSym, baseType: PType; idgen: IdGenerator; kind = tyVar
|
||||
result = newType(kind, nextTypeId(idgen), owner)
|
||||
addSonSkipIntLit(result, baseType, idgen)
|
||||
|
||||
proc makeTypeDesc*(c: PContext, typ: PType): PType =
|
||||
if typ.kind == tyTypeDesc:
|
||||
result = typ
|
||||
else:
|
||||
result = newTypeS(tyTypeDesc, c)
|
||||
incl result.flags, tfCheckedForDestructor
|
||||
result.addSonSkipIntLit(typ, c.idgen)
|
||||
|
||||
proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
|
||||
let typedesc = newTypeS(tyTypeDesc, c)
|
||||
incl typedesc.flags, tfCheckedForDestructor
|
||||
|
||||
@@ -62,30 +62,29 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym
|
||||
for i, a in n.pairs:
|
||||
internalAssert c.config, a.kind == nkSym
|
||||
var q = a.sym
|
||||
if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses:
|
||||
continue
|
||||
let symKind = if q.typ.kind == tyStatic: skConst else: skType
|
||||
var s = newSym(symKind, q.name, nextSymId c.idgen, getCurrOwner(c), q.info)
|
||||
s.flags.incl {sfUsed, sfFromGeneric}
|
||||
var t = PType(idTableGet(pt, q.typ))
|
||||
if t == nil:
|
||||
if tfRetType in q.typ.flags:
|
||||
# keep the generic type and allow the return type to be bound
|
||||
# later by semAsgn in return type inference scenario
|
||||
t = q.typ
|
||||
else:
|
||||
localError(c.config, a.info, errCannotInstantiateX % s.name.s)
|
||||
if q.typ.kind in {tyTypeDesc, tyGenericParam, tyStatic, tyConcept}+tyTypeClasses:
|
||||
let symKind = if q.typ.kind == tyStatic: skConst else: skType
|
||||
var s = newSym(symKind, q.name, nextSymId(c.idgen), getCurrOwner(c), q.info)
|
||||
s.flags.incl {sfUsed, sfFromGeneric}
|
||||
var t = PType(idTableGet(pt, q.typ))
|
||||
if t == nil:
|
||||
if tfRetType in q.typ.flags:
|
||||
# keep the generic type and allow the return type to be bound
|
||||
# later by semAsgn in return type inference scenario
|
||||
t = q.typ
|
||||
else:
|
||||
localError(c.config, a.info, errCannotInstantiateX % s.name.s)
|
||||
t = errorType(c)
|
||||
elif t.kind in {tyGenericParam, tyConcept}:
|
||||
localError(c.config, a.info, errCannotInstantiateX % q.name.s)
|
||||
t = errorType(c)
|
||||
elif t.kind == tyGenericParam:
|
||||
localError(c.config, a.info, errCannotInstantiateX % q.name.s)
|
||||
t = errorType(c)
|
||||
elif t.kind == tyGenericInvocation:
|
||||
#t = instGenericContainer(c, a, t)
|
||||
t = generateTypeInstance(c, pt, a, t)
|
||||
#t = ReplaceTypeVarsT(cl, t)
|
||||
s.typ = t
|
||||
if t.kind == tyStatic: s.ast = t.n
|
||||
yield s
|
||||
elif t.kind == tyGenericInvocation:
|
||||
#t = instGenericContainer(c, a, t)
|
||||
t = generateTypeInstance(c, pt, a, t)
|
||||
#t = ReplaceTypeVarsT(cl, t)
|
||||
s.typ = t
|
||||
if t.kind == tyStatic: s.ast = t.n
|
||||
yield s
|
||||
|
||||
proc sameInstantiation(a, b: TInstantiation): bool =
|
||||
if a.concreteTypes.len == b.concreteTypes.len:
|
||||
|
||||
@@ -2076,7 +2076,7 @@ proc semIterator(c: PContext, n: PNode): PNode =
|
||||
incl(s.typ.flags, tfCapturesEnv)
|
||||
else:
|
||||
s.typ.callConv = ccInline
|
||||
if n[bodyPos].kind == nkEmpty and s.magic == mNone:
|
||||
if n[bodyPos].kind == nkEmpty and s.magic == mNone and c.inConceptDecl == 0:
|
||||
localError(c.config, n.info, errImplOfXexpected % s.name.s)
|
||||
if optOwnedRefs in c.config.globalOptions and result.typ != nil:
|
||||
result.typ = makeVarType(c, result.typ, tyOwned)
|
||||
|
||||
@@ -200,7 +200,9 @@ proc semVarargs(c: PContext, n: PNode, prev: PType): PType =
|
||||
proc semVarOutType(c: PContext, n: PNode, prev: PType; kind: TTypeKind): PType =
|
||||
if n.len == 1:
|
||||
result = newOrPrevType(kind, prev, c)
|
||||
var base = semTypeNode(c, n[0], nil).skipTypes({tyTypeDesc})
|
||||
var base = semTypeNode(c, n[0], nil)
|
||||
if base.kind == tyTypeDesc and not isSelf(base):
|
||||
base = base[0]
|
||||
if base.kind == tyVar:
|
||||
localError(c.config, n.info, "type 'var var' is not allowed")
|
||||
base = base[0]
|
||||
@@ -1025,7 +1027,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
var paramTypId = if not anon and paramType.sym != nil: paramType.sym.name
|
||||
else: nil
|
||||
|
||||
case paramType.kind:
|
||||
case paramType.kind
|
||||
of tyAnything:
|
||||
result = addImplicitGeneric(c, newTypeS(tyGenericParam, c), nil, info, genericParams, paramName)
|
||||
|
||||
@@ -1154,7 +1156,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
|
||||
result = recurse(expanded, true)
|
||||
|
||||
of tyUserTypeClasses, tyBuiltInTypeClass, tyCompositeTypeClass,
|
||||
tyAnd, tyOr, tyNot:
|
||||
tyAnd, tyOr, tyNot, tyConcept:
|
||||
result = addImplicitGeneric(c,
|
||||
copyType(paramType, nextTypeId c.idgen, getCurrOwner(c)), paramTypId,
|
||||
info, genericParams, paramName)
|
||||
@@ -1548,6 +1550,12 @@ template modifierTypeKindOfNode(n: PNode): TTypeKind =
|
||||
|
||||
proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
|
||||
# if n.len == 0: return newConstraint(c, tyTypeClass)
|
||||
if isNewStyleConcept(n):
|
||||
result = newOrPrevType(tyConcept, prev, c)
|
||||
result.flags.incl tfCheckedForDestructor
|
||||
result.n = semConceptDeclaration(c, n)
|
||||
return result
|
||||
|
||||
let
|
||||
pragmas = n[1]
|
||||
inherited = n[2]
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
import ast, astalgo, msgs, types, magicsys, semdata, renderer, options,
|
||||
lineinfos, modulegraphs
|
||||
|
||||
from concepts import makeTypeDesc
|
||||
|
||||
const tfInstClearedFlags = {tfHasMeta, tfUnresolved}
|
||||
|
||||
proc checkPartialConstructedType(conf: ConfigRef; info: TLineInfo, t: PType) =
|
||||
@@ -505,7 +507,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
|
||||
result = t
|
||||
if t == nil: return
|
||||
|
||||
if t.kind in {tyStatic, tyGenericParam} + tyTypeClasses:
|
||||
if t.kind in {tyStatic, tyGenericParam, tyConcept} + tyTypeClasses:
|
||||
let lookup = cl.typeMap.lookup(t)
|
||||
if lookup != nil: return lookup
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import
|
||||
intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst,
|
||||
magicsys, idents, lexer, options, parampatterns, strutils, trees,
|
||||
linter, lineinfos, lowerings, modulegraphs
|
||||
linter, lineinfos, lowerings, modulegraphs, concepts
|
||||
|
||||
type
|
||||
MismatchKind* = enum
|
||||
@@ -363,7 +363,7 @@ proc concreteType(c: TCandidate, t: PType; f: PType = nil): PType =
|
||||
of tySequence, tySet:
|
||||
if t[0].kind == tyEmpty: result = nil
|
||||
else: result = t
|
||||
of tyGenericParam, tyAnything:
|
||||
of tyGenericParam, tyAnything, tyConcept:
|
||||
result = t
|
||||
while true:
|
||||
result = PType(idTableGet(c.bindings, t))
|
||||
@@ -1505,8 +1505,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
|
||||
of tyGenericInvocation:
|
||||
var x = a.skipGenericAlias
|
||||
|
||||
var preventHack = false
|
||||
let concpt = f[0].skipTypes({tyGenericBody})
|
||||
var preventHack = concpt.kind == tyConcept
|
||||
if x.kind == tyOwned and f[0].kind != tyOwned:
|
||||
preventHack = true
|
||||
x = x.lastSon
|
||||
@@ -1533,6 +1533,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
# Workaround for regression #4589
|
||||
if f[i].kind != tyTypeDesc: return
|
||||
result = isGeneric
|
||||
elif x.kind == tyGenericInst and concpt.kind == tyConcept:
|
||||
result = if concepts.conceptMatch(c.c, concpt, x, c.bindings, f): isGeneric
|
||||
else: isNone
|
||||
else:
|
||||
let genericBody = f[0]
|
||||
var askip = skippedNone
|
||||
@@ -1654,6 +1657,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
else:
|
||||
result = isNone
|
||||
|
||||
of tyConcept:
|
||||
result = if concepts.conceptMatch(c.c, f, a, c.bindings, nil): isGeneric
|
||||
else: isNone
|
||||
|
||||
of tyCompositeTypeClass:
|
||||
considerPreviousT:
|
||||
let roota = a.skipGenericAlias
|
||||
@@ -2500,7 +2507,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
|
||||
noMatch()
|
||||
checkConstraint(n[a])
|
||||
|
||||
if m.state == csMatch and not(m.calleeSym != nil and m.calleeSym.kind in {skTemplate, skMacro}):
|
||||
if m.state == csMatch and not (m.calleeSym != nil and m.calleeSym.kind in {skTemplate, skMacro}):
|
||||
c.mergeShadowScope
|
||||
else:
|
||||
c.closeShadowScope
|
||||
|
||||
@@ -187,7 +187,9 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
|
||||
result = typeAllowedAux(marker, t.lastSon, kind, c, flags+{taHeap})
|
||||
else:
|
||||
result = t
|
||||
of tyOptDeprecated: doAssert false
|
||||
of tyConcept:
|
||||
if kind != skParam: result = t
|
||||
else: result = nil
|
||||
|
||||
proc typeAllowed*(t: PType, kind: TSymKind; c: PContext; flags: TTypeAllowedFlags = {}): PType =
|
||||
# returns 'nil' on success and otherwise the part of the type that is
|
||||
|
||||
@@ -1195,7 +1195,8 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
|
||||
cycleCheck()
|
||||
result = sameTypeAux(a.lastSon, b.lastSon, c)
|
||||
of tyNone: result = false
|
||||
of tyOptDeprecated: doAssert false
|
||||
of tyConcept:
|
||||
result = exprStructuralEquivalent(a.n, b.n)
|
||||
|
||||
proc sameBackendType*(x, y: PType): bool =
|
||||
var c = initSameTypeClosure()
|
||||
@@ -1271,6 +1272,7 @@ proc matchType*(a: PType, pattern: openArray[tuple[k:TTypeKind, i:int]],
|
||||
a = a[i]
|
||||
result = a.kind == last
|
||||
|
||||
|
||||
include sizealignoffsetimpl
|
||||
|
||||
proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt =
|
||||
|
||||
@@ -299,7 +299,9 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo;
|
||||
if t.n != nil:
|
||||
result.add t.n.copyTree
|
||||
of tyOwned: result = mapTypeToBracket("owned", mBuiltinType, t, info)
|
||||
of tyOptDeprecated: doAssert false
|
||||
of tyConcept:
|
||||
result = mapTypeToBracket("concept", mNone, t, info)
|
||||
result.add t.n.copyTree
|
||||
|
||||
proc opMapTypeToAst*(cache: IdentCache; t: PType; info: TLineInfo; idgen: IdGenerator): PNode =
|
||||
result = mapTypeToAstX(cache, t, info, idgen, inst=false, allowRecursionX=true)
|
||||
|
||||
@@ -98,7 +98,7 @@ block tconceptinclosure:
|
||||
block overload_precedence:
|
||||
type ParameterizedType[T] = object
|
||||
|
||||
type CustomTypeClass = concept
|
||||
type CustomTypeClass = concept c
|
||||
true
|
||||
|
||||
# 3 competing procs
|
||||
|
||||
@@ -397,7 +397,7 @@ block misc_issues:
|
||||
echo p2.x is float and p2.y is float # true
|
||||
|
||||
# https://github.com/nim-lang/Nim/issues/2018
|
||||
type ProtocolFollower = concept
|
||||
type ProtocolFollower = concept c
|
||||
true # not a particularly involved protocol
|
||||
|
||||
type ImplementorA = object
|
||||
|
||||
@@ -9,7 +9,7 @@ x as CustomTypeClass'''
|
||||
|
||||
type ParameterizedType[T] = object
|
||||
|
||||
type CustomTypeClass = concept
|
||||
type CustomTypeClass = concept c
|
||||
true
|
||||
|
||||
# 3 competing procs
|
||||
|
||||
106
tests/concepts/tspec.nim
Normal file
106
tests/concepts/tspec.nim
Normal file
@@ -0,0 +1,106 @@
|
||||
discard """
|
||||
output: '''4
|
||||
0
|
||||
4
|
||||
4
|
||||
1
|
||||
2
|
||||
3
|
||||
yes int
|
||||
string int
|
||||
true'''
|
||||
joinable: false
|
||||
"""
|
||||
|
||||
import hashes
|
||||
|
||||
type
|
||||
Comparable = concept # no T, an atom
|
||||
proc cmp(a, b: Self): int
|
||||
|
||||
ToStringable = concept
|
||||
proc `$`(a: Self): string
|
||||
|
||||
Hashable = concept ## the most basic of identity assumptions
|
||||
proc hash(x: Self): int
|
||||
proc `==`(x, y: Self): bool
|
||||
|
||||
Swapable = concept
|
||||
proc swap(x, y: var Self)
|
||||
|
||||
|
||||
proc h(x: Hashable) =
|
||||
echo x
|
||||
|
||||
h(4)
|
||||
|
||||
when true:
|
||||
proc compare(a: Comparable) =
|
||||
echo cmp(a, a)
|
||||
|
||||
compare(4)
|
||||
|
||||
proc dollar(x: ToStringable) =
|
||||
echo x
|
||||
|
||||
when true:
|
||||
dollar 4
|
||||
dollar "4"
|
||||
|
||||
#type D = distinct int
|
||||
|
||||
#dollar D(4)
|
||||
|
||||
when true:
|
||||
type
|
||||
Iterable[Ix] = concept
|
||||
iterator items(c: Self): Ix
|
||||
|
||||
proc g[Tu](it: Iterable[Tu]) =
|
||||
for x in it:
|
||||
echo x
|
||||
|
||||
g(@[1, 2, 3])
|
||||
|
||||
proc hs(x: Swapable) =
|
||||
var y = x
|
||||
swap y, y
|
||||
|
||||
hs(4)
|
||||
|
||||
type
|
||||
Indexable[T] = concept # has a T, a collection
|
||||
proc `[]`(a: Self; index: int): T # we need to describe how to infer 'T'
|
||||
# and then we can use the 'T' and it must match:
|
||||
proc `[]=`(a: var Self; index: int; value: T)
|
||||
proc len(a: Self): int
|
||||
|
||||
proc indexOf[T](a: Indexable[T]; value: T) =
|
||||
echo "yes ", T
|
||||
|
||||
block:
|
||||
var x = @[1, 2, 3]
|
||||
indexOf(x, 4)
|
||||
|
||||
import tables, typetraits
|
||||
|
||||
type
|
||||
Dict[K, V] = concept
|
||||
proc `[]`(s: Self; k: K): V
|
||||
proc `[]=`(s: var Self; k: K; v: V)
|
||||
|
||||
proc d[K2, V2](x: Dict[K2, V2]) =
|
||||
echo K2, " ", V2
|
||||
|
||||
var x = initTable[string, int]()
|
||||
d(x)
|
||||
|
||||
|
||||
type Monoid = concept
|
||||
proc `+`(x, y: Self): Self
|
||||
proc z(t: typedesc[Self]): Self
|
||||
|
||||
proc z(x: typedesc[int]): int = 0
|
||||
|
||||
echo(int is Monoid)
|
||||
|
||||
Reference in New Issue
Block a user