# # # 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 import std/intsets when defined(nimPreviewSlimSystem): import std/assertions 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"), 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..= a.kidsLen if not result: m.inferred.setLen oldLen else: result = false for ff in f.kids: result = matchType(c, ff, a, m) if result: break # and remember the binding! m.inferred.setLen oldLen of tyNot: if a.kind == tyNot: result = matchType(c, f.elementType, a.elementType, m) else: let oldLen = m.inferred.len result = not matchType(c, f.elementType, 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.returnType, candidate.typ.returnType, 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 = searchInScopesAllCandidatesFilterBy(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..