From 6278b5d89af38e90aa30cfc4c217a2f4b1323339 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 24 Feb 2021 13:17:33 +0100 Subject: [PATCH] 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) --- compiler/ast.nim | 9 +- compiler/concepts.nim | 340 ++++++++++++++++++ compiler/jsgen.nim | 2 +- compiler/liftdestructors.nim | 2 +- compiler/parser.nim | 16 +- compiler/sem.nim | 2 +- compiler/semdata.nim | 10 +- compiler/seminst.nim | 45 ++- compiler/semstmts.nim | 2 +- compiler/semtypes.nim | 14 +- compiler/semtypinst.nim | 4 +- compiler/sigmatch.nim | 17 +- compiler/typeallowed.nim | 4 +- compiler/types.nim | 4 +- compiler/vmdeps.nim | 4 +- tests/concepts/tconcepts.nim | 2 +- tests/concepts/tconcepts_issues.nim | 2 +- .../tconcepts_overload_precedence.nim | 2 +- tests/concepts/tspec.nim | 106 ++++++ 19 files changed, 528 insertions(+), 59 deletions(-) create mode 100644 compiler/concepts.nim create mode 100644 tests/concepts/tspec.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index b02eb99e02..2b1f76e2d3 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 diff --git a/compiler/concepts.nim b/compiler/concepts.nim new file mode 100644 index 0000000000..54579f73f6 --- /dev/null +++ b/compiler/concepts.nim @@ -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.. 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..= a.len + if not result: + m.inferred.setLen oldLen + else: + for i in 0..} 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: diff --git a/compiler/sem.nim b/compiler/sem.nim index 9b43063d43..70fd7b8944 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -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 diff --git a/compiler/semdata.nim b/compiler/semdata.nim index c9140c4f70..9dedd98df7 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -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 diff --git a/compiler/seminst.nim b/compiler/seminst.nim index dcb672e63c..8459296483 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -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: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 2c4672dfcb..47d9061e62 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 7297826fbf..b8dd19c51d 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -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] diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index f9ac6ccca1..c9b9e39ead 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -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 diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 744a24935d..26124e81ce 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -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 diff --git a/compiler/typeallowed.nim b/compiler/typeallowed.nim index c818192503..db37353599 100644 --- a/compiler/typeallowed.nim +++ b/compiler/typeallowed.nim @@ -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 diff --git a/compiler/types.nim b/compiler/types.nim index 6a7b4dbc96..a0d43ec095 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -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 = diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index f8765c4dc8..a9157bc03b 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -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) diff --git a/tests/concepts/tconcepts.nim b/tests/concepts/tconcepts.nim index d0bc76c20f..acdff6f246 100644 --- a/tests/concepts/tconcepts.nim +++ b/tests/concepts/tconcepts.nim @@ -98,7 +98,7 @@ block tconceptinclosure: block overload_precedence: type ParameterizedType[T] = object - type CustomTypeClass = concept + type CustomTypeClass = concept c true # 3 competing procs diff --git a/tests/concepts/tconcepts_issues.nim b/tests/concepts/tconcepts_issues.nim index ec7612b341..c76049bdde 100644 --- a/tests/concepts/tconcepts_issues.nim +++ b/tests/concepts/tconcepts_issues.nim @@ -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 diff --git a/tests/concepts/tconcepts_overload_precedence.nim b/tests/concepts/tconcepts_overload_precedence.nim index 9eed6256a3..c580d2688c 100644 --- a/tests/concepts/tconcepts_overload_precedence.nim +++ b/tests/concepts/tconcepts_overload_precedence.nim @@ -9,7 +9,7 @@ x as CustomTypeClass''' type ParameterizedType[T] = object -type CustomTypeClass = concept +type CustomTypeClass = concept c true # 3 competing procs diff --git a/tests/concepts/tspec.nim b/tests/concepts/tspec.nim new file mode 100644 index 0000000000..1bca3c11b8 --- /dev/null +++ b/tests/concepts/tspec.nim @@ -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) +