concepts.nim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2020 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## New styled concepts for Nim. See https://github.com/nim-lang/RFCs/issues/168
  10. ## for details. Note this is a first implementation and only the "Concept matching"
  11. ## section has been implemented.
  12. import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
  13. import std/intsets
  14. when defined(nimPreviewSlimSystem):
  15. import std/assertions
  16. const
  17. logBindings = false
  18. ## Code dealing with Concept declarations
  19. ## --------------------------------------
  20. proc declareSelf(c: PContext; info: TLineInfo) =
  21. ## Adds the magical 'Self' symbols to the current scope.
  22. let ow = getCurrOwner(c)
  23. let s = newSym(skType, getIdent(c.cache, "Self"), c.idgen, ow, info)
  24. s.typ = newType(tyTypeDesc, c.idgen, ow)
  25. s.typ.flags.incl {tfUnresolved, tfPacked}
  26. s.typ.add newType(tyEmpty, c.idgen, ow)
  27. addDecl(c, s, info)
  28. proc semConceptDecl(c: PContext; n: PNode): PNode =
  29. ## Recursive helper for semantic checking for the concept declaration.
  30. ## Currently we only support (possibly empty) lists of statements
  31. ## containing 'proc' declarations and the like.
  32. case n.kind
  33. of nkStmtList, nkStmtListExpr:
  34. result = shallowCopy(n)
  35. for i in 0..<n.len:
  36. result[i] = semConceptDecl(c, n[i])
  37. of nkProcDef..nkIteratorDef, nkFuncDef:
  38. result = c.semExpr(c, n, {efWantStmt})
  39. of nkTypeClassTy:
  40. result = shallowCopy(n)
  41. for i in 0..<n.len-1:
  42. result[i] = n[i]
  43. result[^1] = semConceptDecl(c, n[^1])
  44. of nkCommentStmt:
  45. result = n
  46. else:
  47. localError(c.config, n.info, "unexpected construct in the new-styled concept: " & renderTree(n))
  48. result = n
  49. proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
  50. ## Semantic checking for the concept declaration. Runs
  51. ## when we process the concept itself, not its matching process.
  52. assert n.kind == nkTypeClassTy
  53. inc c.inConceptDecl
  54. openScope(c)
  55. declareSelf(c, n.info)
  56. result = semConceptDecl(c, n)
  57. rawCloseScope(c)
  58. dec c.inConceptDecl
  59. ## Concept matching
  60. ## ----------------
  61. type
  62. MatchCon = object ## Context we pass around during concept matching.
  63. inferred: seq[(PType, PType)] ## we need a seq here so that we can easily undo inferences \
  64. ## that turned out to be wrong.
  65. marker: IntSet ## Some protection against wild runaway recursions.
  66. potentialImplementation: PType ## the concrete type that might match the concept we try to match.
  67. magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and
  68. ## cannot be fixed that easily.
  69. ## Thus we special case it here.
  70. concpt: PType
  71. proc existingBinding(m: MatchCon; key: PType): PType =
  72. ## checks if we bound the type variable 'key' already to some
  73. ## concrete type.
  74. for i in 0..<m.inferred.len:
  75. if m.inferred[i][0] == key: return m.inferred[i][1]
  76. return nil
  77. proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool
  78. proc matchType(c: PContext; f, ao: PType; m: var MatchCon): bool
  79. proc acceptsAllTypes(t: PType): bool=
  80. result = false
  81. if t.kind == tyAnything:
  82. result = true
  83. elif t.kind == tyGenericParam:
  84. if tfImplicitTypeParam in t.flags:
  85. result = true
  86. if not t.hasElementType or t.elementType.kind == tyNone:
  87. result = true
  88. proc matchKids(c: PContext; f, a: PType; m: var MatchCon, start=0): bool=
  89. result = true
  90. for i in start..<f.kidsLen - ord(f.kind == tyGenericInst):
  91. if not matchType(c, f[i], a[i], m): return false
  92. proc matchType(c: PContext; f, ao: PType; m: var MatchCon): bool =
  93. ## The heart of the concept matching process. 'f' is the formal parameter of some
  94. ## routine inside the concept that we're looking for. 'a' is the formal parameter
  95. ## of a routine that might match.
  96. const
  97. ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred}
  98. var a = ao
  99. case a.kind
  100. of tyGenericParam:
  101. let binding = m.existingBinding(a)
  102. if binding != nil:
  103. a = binding
  104. else:
  105. discard
  106. case f.kind
  107. of tyAlias:
  108. result = matchType(c, f.skipModifier, a, m)
  109. of tyTypeDesc:
  110. if isSelf(f):
  111. if m.magic in {mArrPut, mArrGet}:
  112. result = false
  113. if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies:
  114. m.inferred.add((a, last m.potentialImplementation))
  115. result = true
  116. else:
  117. result = matchType(c, a, m.potentialImplementation, m)
  118. else:
  119. if a.kind == tyTypeDesc and f.hasElementType == a.hasElementType:
  120. if f.hasElementType:
  121. result = matchType(c, f.elementType, a.elementType, m)
  122. else:
  123. result = true # both lack it
  124. else:
  125. result = false
  126. of tyGenericInvocation:
  127. result = false
  128. if a.kind == tyGenericInst and a.genericHead.kind == tyGenericBody:
  129. if sameType(f.genericHead, a.genericHead) and f.kidsLen == a.kidsLen-1:
  130. result = matchKids(c, f, a, m, start=FirstGenericParamAt)
  131. of tyGenericParam:
  132. let ak = a.skipTypes({tyVar, tySink, tyLent, tyOwned})
  133. if ak.kind in {tyTypeDesc, tyStatic} and not isSelf(ak):
  134. result = false
  135. else:
  136. let old = existingBinding(m, f)
  137. if old == nil:
  138. if f.hasElementType and f.elementType.kind != tyNone:
  139. # also check the generic's constraints:
  140. let oldLen = m.inferred.len
  141. result = matchType(c, f.elementType, a, m)
  142. m.inferred.setLen oldLen
  143. if result:
  144. when logBindings: echo "A adding ", f, " ", ak
  145. m.inferred.add((f, ak))
  146. elif m.magic == mArrGet and ak.kind in {tyArray, tyOpenArray, tySequence, tyVarargs, tyCstring, tyString}:
  147. when logBindings: echo "B adding ", f, " ", last ak
  148. m.inferred.add((f, last ak))
  149. result = true
  150. else:
  151. when logBindings: echo "C adding ", f, " ", ak
  152. m.inferred.add((f, ak))
  153. #echo "binding ", typeToString(ak), " to ", typeToString(f)
  154. result = true
  155. elif not m.marker.containsOrIncl(old.id):
  156. result = matchType(c, old, ak, m)
  157. if m.magic == mArrPut and ak.kind == tyGenericParam:
  158. result = true
  159. else:
  160. result = false
  161. #echo "B for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)
  162. of tyVar, tySink, tyLent, tyOwned:
  163. # modifiers in the concept must be there in the actual implementation
  164. # too but not vice versa.
  165. if a.kind == f.kind:
  166. result = matchType(c, f.elementType, a.elementType, m)
  167. elif m.magic == mArrPut:
  168. result = matchType(c, f.elementType, a, m)
  169. else:
  170. result = false
  171. of tyEnum, tyObject, tyDistinct:
  172. result = sameType(f, a)
  173. of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid:
  174. result = a.skipTypes(ignorableForArgType).kind == f.kind
  175. of tyBool, tyChar, tyInt..tyUInt64:
  176. let ak = a.skipTypes(ignorableForArgType)
  177. result = ak.kind == f.kind or ak.kind == tyOrdinal or
  178. (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal)
  179. of tyConcept:
  180. if a.kind == tyConcept and f.n == a.n:
  181. result = true
  182. elif m.concpt.size == szIllegalRecursion:
  183. result = false
  184. else:
  185. let oldLen = m.inferred.len
  186. let oldPotentialImplementation = m.potentialImplementation
  187. m.potentialImplementation = a
  188. m.concpt.size = szIllegalRecursion
  189. let oldConcept = m.concpt
  190. m.concpt = f
  191. result = conceptMatchNode(c, f.n.lastSon, m)
  192. m.potentialImplementation = oldPotentialImplementation
  193. m.concpt = oldConcept
  194. m.concpt.size = szUnknownSize
  195. if not result:
  196. m.inferred.setLen oldLen
  197. of tyGenericBody:
  198. var ak = a
  199. if a.kind == tyGenericBody:
  200. ak = last(a)
  201. result = matchType(c, last(f), ak, m)
  202. of tyCompositeTypeClass:
  203. var ak = if a.kind == tyCompositeTypeClass: a.last else: a
  204. result = matchType(c, last(f), ak, m)
  205. of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr,
  206. tyGenericInst:
  207. # ^ XXX Rewrite this logic, it's more complex than it needs to be.
  208. if f.kind == tyArray and f.kidsLen == 3:
  209. # XXX: this is a work-around!
  210. # system.nim creates these for the magic array typeclass
  211. result = true
  212. else:
  213. result = false
  214. let ak = a.skipTypes(ignorableForArgType - {f.kind})
  215. if ak.kind == f.kind and f.kidsLen == ak.kidsLen:
  216. result = matchKids(c, f, ak, m)
  217. of tyOr:
  218. let oldLen = m.inferred.len
  219. if a.kind == tyOr:
  220. # say the concept requires 'int|float|string' if the potentialImplementation
  221. # says 'int|string' that is good enough.
  222. var covered = 0
  223. for ff in f.kids:
  224. for aa in a.kids:
  225. let oldLenB = m.inferred.len
  226. let r = matchType(c, ff, aa, m)
  227. if r:
  228. inc covered
  229. break
  230. m.inferred.setLen oldLenB
  231. result = covered >= a.kidsLen
  232. if not result:
  233. m.inferred.setLen oldLen
  234. else:
  235. result = false
  236. for ff in f.kids:
  237. result = matchType(c, ff, a, m)
  238. if result: break # and remember the binding!
  239. m.inferred.setLen oldLen
  240. of tyNot:
  241. if a.kind == tyNot:
  242. result = matchType(c, f.elementType, a.elementType, m)
  243. else:
  244. let oldLen = m.inferred.len
  245. result = not matchType(c, f.elementType, a, m)
  246. m.inferred.setLen oldLen
  247. of tyAnything:
  248. result = true
  249. of tyOrdinal:
  250. result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
  251. of tyStatic:
  252. result = false
  253. var scomp = f.base
  254. if scomp.kind == tyGenericParam:
  255. if f.base.kidsLen > 0:
  256. scomp = scomp.base
  257. if a.kind == tyStatic:
  258. result = matchType(c, scomp, a.base, m)
  259. else:
  260. result = matchType(c, scomp, a, m)
  261. else:
  262. result = false
  263. proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool =
  264. ## Like 'matchType' but with extra logic dealing with proc return types
  265. ## which can be nil or the 'void' type.
  266. if f.isEmptyType:
  267. result = a.isEmptyType
  268. elif a == nil:
  269. result = false
  270. else:
  271. result = matchType(c, f, a, m)
  272. proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool =
  273. ## Checks if 'candidate' matches 'n' from the concept body. 'n' is a nkProcDef
  274. ## or similar.
  275. # watch out: only add bindings after a completely successful match.
  276. let oldLen = m.inferred.len
  277. let can = candidate.typ.n
  278. let con = n[0].sym.typ.n
  279. if can.len < con.len:
  280. # too few arguments, cannot be a match:
  281. return false
  282. let common = min(can.len, con.len)
  283. for i in 1 ..< common:
  284. if not matchType(c, con[i].typ, can[i].typ, m):
  285. m.inferred.setLen oldLen
  286. return false
  287. if not matchReturnType(c, n[0].sym.typ.returnType, candidate.typ.returnType, m):
  288. m.inferred.setLen oldLen
  289. return false
  290. # all other parameters have to be optional parameters:
  291. for i in common ..< can.len:
  292. assert can[i].kind == nkSym
  293. if can[i].sym.ast == nil:
  294. # has too many arguments one of which is not optional:
  295. m.inferred.setLen oldLen
  296. return false
  297. return true
  298. proc matchSyms(c: PContext, n: PNode; kinds: set[TSymKind]; m: var MatchCon): bool =
  299. ## Walk the current scope, extract candidates which the same name as 'n[namePos]',
  300. ## 'n' is the nkProcDef or similar from the concept that we try to match.
  301. var candidates = searchScopes(c, n[namePos].sym.name, kinds)
  302. searchImportsAll(c, n[namePos].sym.name, kinds, candidates)
  303. for candidate in candidates:
  304. #echo "considering ", typeToString(candidate.typ), " ", candidate.magic
  305. m.magic = candidate.magic
  306. if matchSym(c, candidate, n, m): return true
  307. result = false
  308. proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
  309. ## Traverse the concept's AST ('n') and see if every declaration inside 'n'
  310. ## can be matched with the current scope.
  311. case n.kind
  312. of nkStmtList, nkStmtListExpr:
  313. for i in 0..<n.len:
  314. if not conceptMatchNode(c, n[i], m):
  315. return false
  316. return true
  317. of nkProcDef, nkFuncDef:
  318. # procs match any of: proc, template, macro, func, method, converter.
  319. # The others are more specific.
  320. # XXX: Enforce .noSideEffect for 'nkFuncDef'? But then what are the use cases...
  321. const filter = {skProc, skTemplate, skMacro, skFunc, skMethod, skConverter}
  322. result = matchSyms(c, n, filter, m)
  323. of nkTemplateDef:
  324. result = matchSyms(c, n, {skTemplate}, m)
  325. of nkMacroDef:
  326. result = matchSyms(c, n, {skMacro}, m)
  327. of nkConverterDef:
  328. result = matchSyms(c, n, {skConverter}, m)
  329. of nkMethodDef:
  330. result = matchSyms(c, n, {skMethod}, m)
  331. of nkIteratorDef:
  332. result = matchSyms(c, n, {skIterator}, m)
  333. of nkCommentStmt:
  334. result = true
  335. else:
  336. # error was reported earlier.
  337. result = false
  338. proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType): bool =
  339. ## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but
  340. ## we extract its AST via 'concpt.n.lastSon'). 'arg' is the type that might fulfill the
  341. ## concept's requirements. If so, we return true and fill the 'bindings' with pairs of
  342. ## (typeVar, instance) pairs. ('typeVar' is usually simply written as a generic 'T'.)
  343. ## 'invocation' can be nil for atomic concepts. For non-atomic concepts, it contains the
  344. ## `C[S, T]` parent type that we look for. We need this because we need to store bindings
  345. ## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that
  346. ## we do not add any bindings at all on an unsuccessful match!
  347. if arg.containsUnresolvedType:
  348. return false
  349. var m = MatchCon(inferred: @[], potentialImplementation: arg, concpt: concpt)
  350. result = conceptMatchNode(c, concpt.n.lastSon, m)
  351. if result:
  352. for (a, b) in m.inferred:
  353. if b.kind == tyGenericParam:
  354. var dest = b
  355. while true:
  356. dest = existingBinding(m, dest)
  357. if dest == nil or dest.kind != tyGenericParam: break
  358. if dest != nil:
  359. bindings.put(a, dest)
  360. when logBindings: echo "A bind ", a, " ", dest
  361. else:
  362. bindings.put(a, b)
  363. when logBindings: echo "B bind ", a, " ", b
  364. # we have a match, so bind 'arg' itself to 'concpt':
  365. bindings.put(concpt, arg)
  366. # invocation != nil means we have a non-atomic concept:
  367. if invocation != nil and arg.kind == tyGenericInst and invocation.kidsLen == arg.kidsLen-1:
  368. # bind even more generic parameters
  369. assert invocation.kind == tyGenericInvocation
  370. for i in FirstGenericParamAt ..< invocation.kidsLen:
  371. bindings.put(invocation[i], arg[i])