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:
Andreas Rumpf 2021-02-24 13:17:33 +01:00 • committed by GitHub
commit 6278b5d89a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 528 additions and 59 deletions

View file

@ -434,9 +434,8 @@ type
# instantiation and prior to this it has the potential to # instantiation and prior to this it has the potential to
# be any type. # be any type.
tyOptDeprecated tyConcept
# 'out' parameter. Comparable to a 'var' parameter but every # new style concept.
# path must assign a value to it before it can be read from.
tyVoid tyVoid
# now different from tyEmpty, hurray! # now different from tyEmpty, hurray!
@ -1975,3 +1974,7 @@ proc toHumanStr*(kind: TTypeKind): string =
proc skipAddr*(n: PNode): PNode {.inline.} = proc skipAddr*(n: PNode): PNode {.inline.} =
(if n.kind == nkHiddenAddr: n[0] else: n) (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
View 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])

View file

@ -211,7 +211,7 @@ proc mapType(typ: PType): TJSTypeKind =
else: result = etyNone else: result = etyNone
of tyProc: result = etyProc of tyProc: result = etyProc
of tyCString: result = etyString of tyCString: result = etyString
of tyOptDeprecated: doAssert false of tyConcept: doAssert false
proc mapType(p: PProc; typ: PType): TJSTypeKind = proc mapType(p: PProc; typ: PType): TJSTypeKind =
result = mapType(typ) result = mapType(typ)

View file

@ -896,7 +896,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of tyOrdinal, tyRange, tyInferred, of tyOrdinal, tyRange, tyInferred,
tyGenericInst, tyAlias, tySink: tyGenericInst, tyAlias, tySink:
fillBody(c, lastSon(t), body, x, y) fillBody(c, lastSon(t), body, x, y)
of tyOptDeprecated: doAssert false of tyConcept: doAssert false
proc produceSymDistinctType(g: ModuleGraph; c: PContext; typ: PType; proc produceSymDistinctType(g: ModuleGraph; c: PContext; typ: PType;
kind: TTypeAttachedOp; info: TLineInfo; kind: TTypeAttachedOp; info: TLineInfo;

View file

@ -2000,12 +2000,18 @@ proc parseTypeClass(p: var Parser): PNode =
#| &IND{>} stmt #| &IND{>} stmt
result = newNodeP(nkTypeClassTy, p) result = newNodeP(nkTypeClassTy, p)
getTok(p) getTok(p)
if p.tok.tokType == tkComment:
skipComment(p, result)
if p.tok.indent < 0:
var args = newNodeP(nkArgList, p) var args = newNodeP(nkArgList, p)
result.add(args) result.add(args)
args.add(p.parseTypeClassParam) args.add(p.parseTypeClassParam)
while p.tok.tokType == tkComma: while p.tok.tokType == tkComma:
getTok(p) getTok(p)
args.add(p.parseTypeClassParam) args.add(p.parseTypeClassParam)
else:
result.add(p.emptyNode) # see ast.isNewStyleConcept
if p.tok.tokType == tkCurlyDotLe and p.validInd: if p.tok.tokType == tkCurlyDotLe and p.validInd:
result.add(parsePragma(p)) result.add(parsePragma(p))
else: else:

View file

@ -17,7 +17,7 @@ import
intsets, transf, vmdef, vm, aliases, cgmeth, lambdalifting, intsets, transf, vmdef, vm, aliases, cgmeth, lambdalifting,
evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity, evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity,
lowerings, plugins/active, lineinfos, strtabs, int128, lowerings, plugins/active, lineinfos, strtabs, int128,
isolation_check, typeallowed, modulegraphs, enumtostr isolation_check, typeallowed, modulegraphs, enumtostr, concepts
when defined(nimfix): when defined(nimfix):
import nimfix/prettybase import nimfix/prettybase

View file

@ -153,7 +153,7 @@ type
suggestionsMade*: bool suggestionsMade*: bool
isAmbiguous*: bool # little hack isAmbiguous*: bool # little hack
features*: set[Feature] features*: set[Feature]
inTypeContext*: int inTypeContext*, inConceptDecl*: int
unusedImports*: seq[(PSym, TLineInfo)] unusedImports*: seq[(PSym, TLineInfo)]
exportIndirections*: HashSet[(int, int)] exportIndirections*: HashSet[(int, int)]
lastTLineInfo*: TLineInfo lastTLineInfo*: TLineInfo
@ -410,14 +410,6 @@ proc makeVarType*(owner: PSym, baseType: PType; idgen: IdGenerator; kind = tyVar
result = newType(kind, nextTypeId(idgen), owner) result = newType(kind, nextTypeId(idgen), owner)
addSonSkipIntLit(result, baseType, idgen) 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 = proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
let typedesc = newTypeS(tyTypeDesc, c) let typedesc = newTypeS(tyTypeDesc, c)
incl typedesc.flags, tfCheckedForDestructor incl typedesc.flags, tfCheckedForDestructor

View file

@ -62,10 +62,9 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym
for i, a in n.pairs: for i, a in n.pairs:
internalAssert c.config, a.kind == nkSym internalAssert c.config, a.kind == nkSym
var q = a.sym var q = a.sym
if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses: if q.typ.kind in {tyTypeDesc, tyGenericParam, tyStatic, tyConcept}+tyTypeClasses:
continue
let symKind = if q.typ.kind == tyStatic: skConst else: skType let symKind = if q.typ.kind == tyStatic: skConst else: skType
var s = newSym(symKind, q.name, nextSymId c.idgen, getCurrOwner(c), q.info) var s = newSym(symKind, q.name, nextSymId(c.idgen), getCurrOwner(c), q.info)
s.flags.incl {sfUsed, sfFromGeneric} s.flags.incl {sfUsed, sfFromGeneric}
var t = PType(idTableGet(pt, q.typ)) var t = PType(idTableGet(pt, q.typ))
if t == nil: if t == nil:
@ -76,7 +75,7 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym
else: else:
localError(c.config, a.info, errCannotInstantiateX % s.name.s) localError(c.config, a.info, errCannotInstantiateX % s.name.s)
t = errorType(c) t = errorType(c)
elif t.kind == tyGenericParam: elif t.kind in {tyGenericParam, tyConcept}:
localError(c.config, a.info, errCannotInstantiateX % q.name.s) localError(c.config, a.info, errCannotInstantiateX % q.name.s)
t = errorType(c) t = errorType(c)
elif t.kind == tyGenericInvocation: elif t.kind == tyGenericInvocation:

View file

@ -2076,7 +2076,7 @@ proc semIterator(c: PContext, n: PNode): PNode =
incl(s.typ.flags, tfCapturesEnv) incl(s.typ.flags, tfCapturesEnv)
else: else:
s.typ.callConv = ccInline 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) localError(c.config, n.info, errImplOfXexpected % s.name.s)
if optOwnedRefs in c.config.globalOptions and result.typ != nil: if optOwnedRefs in c.config.globalOptions and result.typ != nil:
result.typ = makeVarType(c, result.typ, tyOwned) result.typ = makeVarType(c, result.typ, tyOwned)

View file

@ -200,7 +200,9 @@ proc semVarargs(c: PContext, n: PNode, prev: PType): PType =
proc semVarOutType(c: PContext, n: PNode, prev: PType; kind: TTypeKind): PType = proc semVarOutType(c: PContext, n: PNode, prev: PType; kind: TTypeKind): PType =
if n.len == 1: if n.len == 1:
result = newOrPrevType(kind, prev, c) 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: if base.kind == tyVar:
localError(c.config, n.info, "type 'var var' is not allowed") localError(c.config, n.info, "type 'var var' is not allowed")
base = base[0] 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 var paramTypId = if not anon and paramType.sym != nil: paramType.sym.name
else: nil else: nil
case paramType.kind: case paramType.kind
of tyAnything: of tyAnything:
result = addImplicitGeneric(c, newTypeS(tyGenericParam, c), nil, info, genericParams, paramName) 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) result = recurse(expanded, true)
of tyUserTypeClasses, tyBuiltInTypeClass, tyCompositeTypeClass, of tyUserTypeClasses, tyBuiltInTypeClass, tyCompositeTypeClass,
tyAnd, tyOr, tyNot: tyAnd, tyOr, tyNot, tyConcept:
result = addImplicitGeneric(c, result = addImplicitGeneric(c,
copyType(paramType, nextTypeId c.idgen, getCurrOwner(c)), paramTypId, copyType(paramType, nextTypeId c.idgen, getCurrOwner(c)), paramTypId,
info, genericParams, paramName) info, genericParams, paramName)
@ -1548,6 +1550,12 @@ template modifierTypeKindOfNode(n: PNode): TTypeKind =
proc semTypeClass(c: PContext, n: PNode, prev: PType): PType = proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
# if n.len == 0: return newConstraint(c, tyTypeClass) # 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 let
pragmas = n[1] pragmas = n[1]
inherited = n[2] inherited = n[2]

View file

@ -12,6 +12,8 @@
import ast, astalgo, msgs, types, magicsys, semdata, renderer, options, import ast, astalgo, msgs, types, magicsys, semdata, renderer, options,
lineinfos, modulegraphs lineinfos, modulegraphs
from concepts import makeTypeDesc
const tfInstClearedFlags = {tfHasMeta, tfUnresolved} const tfInstClearedFlags = {tfHasMeta, tfUnresolved}
proc checkPartialConstructedType(conf: ConfigRef; info: TLineInfo, t: PType) = proc checkPartialConstructedType(conf: ConfigRef; info: TLineInfo, t: PType) =
@ -505,7 +507,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
result = t result = t
if t == nil: return 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) let lookup = cl.typeMap.lookup(t)
if lookup != nil: return lookup if lookup != nil: return lookup

View file

@ -13,7 +13,7 @@
import import
intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst, intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst,
magicsys, idents, lexer, options, parampatterns, strutils, trees, magicsys, idents, lexer, options, parampatterns, strutils, trees,
linter, lineinfos, lowerings, modulegraphs linter, lineinfos, lowerings, modulegraphs, concepts
type type
MismatchKind* = enum MismatchKind* = enum
@ -363,7 +363,7 @@ proc concreteType(c: TCandidate, t: PType; f: PType = nil): PType =
of tySequence, tySet: of tySequence, tySet:
if t[0].kind == tyEmpty: result = nil if t[0].kind == tyEmpty: result = nil
else: result = t else: result = t
of tyGenericParam, tyAnything: of tyGenericParam, tyAnything, tyConcept:
result = t result = t
while true: while true:
result = PType(idTableGet(c.bindings, t)) result = PType(idTableGet(c.bindings, t))
@ -1505,8 +1505,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
of tyGenericInvocation: of tyGenericInvocation:
var x = a.skipGenericAlias var x = a.skipGenericAlias
let concpt = f[0].skipTypes({tyGenericBody})
var preventHack = false var preventHack = concpt.kind == tyConcept
if x.kind == tyOwned and f[0].kind != tyOwned: if x.kind == tyOwned and f[0].kind != tyOwned:
preventHack = true preventHack = true
x = x.lastSon x = x.lastSon
@ -1533,6 +1533,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
# Workaround for regression #4589 # Workaround for regression #4589
if f[i].kind != tyTypeDesc: return if f[i].kind != tyTypeDesc: return
result = isGeneric 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: else:
let genericBody = f[0] let genericBody = f[0]
var askip = skippedNone var askip = skippedNone
@ -1654,6 +1657,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
else: else:
result = isNone result = isNone
of tyConcept:
result = if concepts.conceptMatch(c.c, f, a, c.bindings, nil): isGeneric
else: isNone
of tyCompositeTypeClass: of tyCompositeTypeClass:
considerPreviousT: considerPreviousT:
let roota = a.skipGenericAlias let roota = a.skipGenericAlias

View file

@ -187,7 +187,9 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
result = typeAllowedAux(marker, t.lastSon, kind, c, flags+{taHeap}) result = typeAllowedAux(marker, t.lastSon, kind, c, flags+{taHeap})
else: else:
result = t 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 = proc typeAllowed*(t: PType, kind: TSymKind; c: PContext; flags: TTypeAllowedFlags = {}): PType =
# returns 'nil' on success and otherwise the part of the type that is # returns 'nil' on success and otherwise the part of the type that is

View file

@ -1195,7 +1195,8 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
cycleCheck() cycleCheck()
result = sameTypeAux(a.lastSon, b.lastSon, c) result = sameTypeAux(a.lastSon, b.lastSon, c)
of tyNone: result = false of tyNone: result = false
of tyOptDeprecated: doAssert false of tyConcept:
result = exprStructuralEquivalent(a.n, b.n)
proc sameBackendType*(x, y: PType): bool = proc sameBackendType*(x, y: PType): bool =
var c = initSameTypeClosure() var c = initSameTypeClosure()
@ -1271,6 +1272,7 @@ proc matchType*(a: PType, pattern: openArray[tuple[k:TTypeKind, i:int]],
a = a[i] a = a[i]
result = a.kind == last result = a.kind == last
include sizealignoffsetimpl include sizealignoffsetimpl
proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt = proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt =

View file

@ -299,7 +299,9 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo;
if t.n != nil: if t.n != nil:
result.add t.n.copyTree result.add t.n.copyTree
of tyOwned: result = mapTypeToBracket("owned", mBuiltinType, t, info) 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 = proc opMapTypeToAst*(cache: IdentCache; t: PType; info: TLineInfo; idgen: IdGenerator): PNode =
result = mapTypeToAstX(cache, t, info, idgen, inst=false, allowRecursionX=true) result = mapTypeToAstX(cache, t, info, idgen, inst=false, allowRecursionX=true)

View file

@ -98,7 +98,7 @@ block tconceptinclosure:
block overload_precedence: block overload_precedence:
type ParameterizedType[T] = object type ParameterizedType[T] = object
type CustomTypeClass = concept type CustomTypeClass = concept c
true true
# 3 competing procs # 3 competing procs

View file

@ -397,7 +397,7 @@ block misc_issues:
echo p2.x is float and p2.y is float # true echo p2.x is float and p2.y is float # true
# https://github.com/nim-lang/Nim/issues/2018 # https://github.com/nim-lang/Nim/issues/2018
type ProtocolFollower = concept type ProtocolFollower = concept c
true # not a particularly involved protocol true # not a particularly involved protocol
type ImplementorA = object type ImplementorA = object

View file

@ -9,7 +9,7 @@ x as CustomTypeClass'''
type ParameterizedType[T] = object type ParameterizedType[T] = object
type CustomTypeClass = concept type CustomTypeClass = concept c
true true
# 3 competing procs # 3 competing procs

106
tests/concepts/tspec.nim Normal file
View 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)