semobjconstr.nim 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2015 Nim Contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements Nim's object construction rules.
  10. # included from sem.nim
  11. type
  12. ObjConstrContext = object
  13. typ: PType # The constructed type
  14. initExpr: PNode # The init expression (nkObjConstr)
  15. needsFullInit: bool # A `requiresInit` derived type will
  16. # set this to true while visiting
  17. # parent types.
  18. missingFields: seq[PSym] # Fields that the user failed to specify
  19. InitStatus = enum # This indicates the result of object construction
  20. initUnknown
  21. initFull # All of the fields have been initialized
  22. initPartial # Some of the fields have been initialized
  23. initNone # None of the fields have been initialized
  24. initConflict # Fields from different branches have been initialized
  25. proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) =
  26. case newStatus
  27. of initConflict:
  28. existing = newStatus
  29. of initPartial:
  30. if existing in {initUnknown, initFull, initNone}:
  31. existing = initPartial
  32. of initNone:
  33. if existing == initUnknown:
  34. existing = initNone
  35. elif existing == initFull:
  36. existing = initPartial
  37. of initFull:
  38. if existing == initUnknown:
  39. existing = initFull
  40. elif existing == initNone:
  41. existing = initPartial
  42. of initUnknown:
  43. discard
  44. proc invalidObjConstr(c: PContext, n: PNode) =
  45. if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s[0] == ':':
  46. localError(c.config, n.info, "incorrect object construction syntax; use a space after the colon")
  47. else:
  48. localError(c.config, n.info, "incorrect object construction syntax")
  49. proc locateFieldInInitExpr(c: PContext, field: PSym, initExpr: PNode): PNode =
  50. # Returns the assignment nkExprColonExpr node or nil
  51. let fieldId = field.name.id
  52. for i in 1..<initExpr.len:
  53. let assignment = initExpr[i]
  54. if assignment.kind != nkExprColonExpr:
  55. invalidObjConstr(c, assignment)
  56. continue
  57. if fieldId == considerQuotedIdent(c, assignment[0]).id:
  58. return assignment
  59. proc semConstrField(c: PContext, flags: TExprFlags,
  60. field: PSym, initExpr: PNode): PNode =
  61. let assignment = locateFieldInInitExpr(c, field, initExpr)
  62. if assignment != nil:
  63. if nfSem in assignment.flags: return assignment[1]
  64. if not fieldVisible(c, field):
  65. localError(c.config, initExpr.info,
  66. "the field '$1' is not accessible." % [field.name.s])
  67. return
  68. var initValue = semExprFlagDispatched(c, assignment[1], flags)
  69. if initValue != nil:
  70. initValue = fitNode(c, field.typ, initValue, assignment.info)
  71. assignment[0] = newSymNode(field)
  72. assignment[1] = initValue
  73. assignment.flags.incl nfSem
  74. return initValue
  75. proc caseBranchMatchesExpr(branch, matched: PNode): bool =
  76. for i in 0..<branch.len-1:
  77. if branch[i].kind == nkRange:
  78. if overlap(branch[i], matched): return true
  79. elif exprStructuralEquivalent(branch[i], matched):
  80. return true
  81. return false
  82. proc branchVals(c: PContext, caseNode: PNode, caseIdx: int,
  83. isStmtBranch: bool): IntSet =
  84. if caseNode[caseIdx].kind == nkOfBranch:
  85. result = initIntSet()
  86. for val in processBranchVals(caseNode[caseIdx]):
  87. result.incl(val)
  88. else:
  89. result = c.getIntSetOfType(caseNode[0].typ)
  90. for i in 1..<caseNode.len-1:
  91. for val in processBranchVals(caseNode[i]):
  92. result.excl(val)
  93. proc findUsefulCaseContext(c: PContext, discrimator: PNode): (PNode, int) =
  94. for i in countdown(c.p.caseContext.high, 0):
  95. let
  96. (caseNode, index) = c.p.caseContext[i]
  97. skipped = caseNode[0].skipHidden
  98. if skipped.kind == nkSym and skipped.sym == discrimator.sym:
  99. return (caseNode, index)
  100. proc pickCaseBranch(caseExpr, matched: PNode): PNode =
  101. # XXX: Perhaps this proc already exists somewhere
  102. let endsWithElse = caseExpr[^1].kind == nkElse
  103. for i in 1..<caseExpr.len - int(endsWithElse):
  104. if caseExpr[i].caseBranchMatchesExpr(matched):
  105. return caseExpr[i]
  106. if endsWithElse:
  107. return caseExpr[^1]
  108. iterator directFieldsInRecList(recList: PNode): PNode =
  109. # XXX: We can remove this case by making all nkOfBranch nodes
  110. # regular. Currently, they try to avoid using nkRecList if they
  111. # include only a single field
  112. if recList.kind == nkSym:
  113. yield recList
  114. else:
  115. doAssert recList.kind == nkRecList
  116. for field in recList:
  117. if field.kind != nkSym: continue
  118. yield field
  119. template quoteStr(s: string): string = "'" & s & "'"
  120. proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): string =
  121. result = ""
  122. for field in directFieldsInRecList(fieldsRecList):
  123. let assignment = locateFieldInInitExpr(c, field.sym, initExpr)
  124. if assignment != nil:
  125. if result.len != 0: result.add ", "
  126. result.add field.sym.name.s.quoteStr
  127. proc collectMissingFields(c: PContext, fieldsRecList: PNode,
  128. constrCtx: var ObjConstrContext) =
  129. for r in directFieldsInRecList(fieldsRecList):
  130. if constrCtx.needsFullInit or
  131. sfRequiresInit in r.sym.flags or
  132. r.sym.typ.requiresInit:
  133. let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr)
  134. if assignment == nil:
  135. constrCtx.missingFields.add r.sym
  136. proc semConstructFields(c: PContext, recNode: PNode,
  137. constrCtx: var ObjConstrContext,
  138. flags: TExprFlags): InitStatus =
  139. result = initUnknown
  140. case recNode.kind
  141. of nkRecList:
  142. for field in recNode:
  143. let status = semConstructFields(c, field, constrCtx, flags)
  144. mergeInitStatus(result, status)
  145. of nkRecCase:
  146. template fieldsPresentInBranch(branchIdx: int): string =
  147. let branch = recNode[branchIdx]
  148. let fields = branch[^1]
  149. fieldsPresentInInitExpr(c, fields, constrCtx.initExpr)
  150. template collectMissingFields(branchNode: PNode) =
  151. if branchNode != nil:
  152. let fields = branchNode[^1]
  153. collectMissingFields(c, fields, constrCtx)
  154. let discriminator = recNode[0]
  155. internalAssert c.config, discriminator.kind == nkSym
  156. var selectedBranch = -1
  157. for i in 1..<recNode.len:
  158. let innerRecords = recNode[i][^1]
  159. let status = semConstructFields(c, innerRecords, constrCtx, flags)
  160. if status notin {initNone, initUnknown}:
  161. mergeInitStatus(result, status)
  162. if selectedBranch != -1:
  163. let prevFields = fieldsPresentInBranch(selectedBranch)
  164. let currentFields = fieldsPresentInBranch(i)
  165. localError(c.config, constrCtx.initExpr.info,
  166. ("The fields '$1' and '$2' cannot be initialized together, " &
  167. "because they are from conflicting branches in the case object.") %
  168. [prevFields, currentFields])
  169. result = initConflict
  170. else:
  171. selectedBranch = i
  172. if selectedBranch != -1:
  173. template badDiscriminatorError =
  174. let fields = fieldsPresentInBranch(selectedBranch)
  175. localError(c.config, constrCtx.initExpr.info,
  176. ("cannot prove that it's safe to initialize $1 with " &
  177. "the runtime value for the discriminator '$2' ") %
  178. [fields, discriminator.sym.name.s])
  179. mergeInitStatus(result, initNone)
  180. template wrongBranchError(i) =
  181. let fields = fieldsPresentInBranch(i)
  182. localError(c.config, constrCtx.initExpr.info,
  183. "a case selecting discriminator '$1' with value '$2' " &
  184. "appears in the object construction, but the field(s) $3 " &
  185. "are in conflict with this value.",
  186. [discriminator.sym.name.s, discriminatorVal.renderTree, fields])
  187. template valuesInConflictError(valsDiff) =
  188. localError(c.config, discriminatorVal.info, ("possible values " &
  189. "$2 are in conflict with discriminator values for " &
  190. "selected object branch $1.") % [$selectedBranch,
  191. valsDiff.renderAsType(recNode[0].typ)])
  192. let branchNode = recNode[selectedBranch]
  193. let flags = flags*{efAllowDestructor} + {efPreferStatic,
  194. efPreferNilResult}
  195. var discriminatorVal = semConstrField(c, flags,
  196. discriminator.sym,
  197. constrCtx.initExpr)
  198. if discriminatorVal != nil:
  199. discriminatorVal = discriminatorVal.skipHidden
  200. if discriminatorVal.kind notin nkLiterals and (
  201. not isOrdinalType(discriminatorVal.typ, true) or
  202. lengthOrd(c.config, discriminatorVal.typ) > MaxSetElements or
  203. lengthOrd(c.config, recNode[0].typ) > MaxSetElements):
  204. localError(c.config, discriminatorVal.info,
  205. "branch initialization with a runtime discriminator only " &
  206. "supports ordinal types with 2^16 elements or less.")
  207. if discriminatorVal == nil:
  208. badDiscriminatorError()
  209. elif discriminatorVal.kind == nkSym:
  210. let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal)
  211. if ctorCase == nil:
  212. if discriminatorVal.typ.kind == tyRange:
  213. let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
  214. let recBranchVals = branchVals(c, recNode, selectedBranch, false)
  215. let diff = rangeVals - recBranchVals
  216. if diff.len != 0:
  217. valuesInConflictError(diff)
  218. else:
  219. badDiscriminatorError()
  220. elif discriminatorVal.sym.kind notin {skLet, skParam} or
  221. discriminatorVal.sym.typ.kind in {tyVar}:
  222. localError(c.config, discriminatorVal.info,
  223. "runtime discriminator must be immutable if branch fields are " &
  224. "initialized, a 'let' binding is required.")
  225. elif ctorCase[ctorIdx].kind == nkElifBranch:
  226. localError(c.config, discriminatorVal.info, "branch initialization " &
  227. "with a runtime discriminator is not supported inside of an " &
  228. "`elif` branch.")
  229. else:
  230. var
  231. ctorBranchVals = branchVals(c, ctorCase, ctorIdx, true)
  232. recBranchVals = branchVals(c, recNode, selectedBranch, false)
  233. branchValsDiff = ctorBranchVals - recBranchVals
  234. if branchValsDiff.len != 0:
  235. valuesInConflictError(branchValsDiff)
  236. else:
  237. var failedBranch = -1
  238. if branchNode.kind != nkElse:
  239. if not branchNode.caseBranchMatchesExpr(discriminatorVal):
  240. failedBranch = selectedBranch
  241. else:
  242. # With an else clause, check that all other branches don't match:
  243. for i in 1..<recNode.len - 1:
  244. if recNode[i].caseBranchMatchesExpr(discriminatorVal):
  245. failedBranch = i
  246. break
  247. if failedBranch != -1:
  248. if discriminatorVal.typ.kind == tyRange:
  249. let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
  250. let recBranchVals = branchVals(c, recNode, selectedBranch, false)
  251. let diff = rangeVals - recBranchVals
  252. if diff.len != 0:
  253. valuesInConflictError(diff)
  254. else:
  255. wrongBranchError(failedBranch)
  256. # When a branch is selected with a partial match, some of the fields
  257. # that were not initialized may be mandatory. We must check for this:
  258. if result == initPartial:
  259. collectMissingFields branchNode
  260. else:
  261. result = initNone
  262. let discriminatorVal = semConstrField(c, flags + {efPreferStatic},
  263. discriminator.sym,
  264. constrCtx.initExpr)
  265. if discriminatorVal == nil:
  266. # None of the branches were explicitly selected by the user and no
  267. # value was given to the discrimator. We can assume that it will be
  268. # initialized to zero and this will select a particular branch as
  269. # a result:
  270. let defaultValue = newIntLit(c.graph, constrCtx.initExpr.info, 0)
  271. let matchedBranch = recNode.pickCaseBranch defaultValue
  272. collectMissingFields matchedBranch
  273. else:
  274. result = initPartial
  275. if discriminatorVal.kind == nkIntLit:
  276. # When the discriminator is a compile-time value, we also know
  277. # which branch will be selected:
  278. let matchedBranch = recNode.pickCaseBranch discriminatorVal
  279. if matchedBranch != nil: collectMissingFields matchedBranch
  280. else:
  281. # All bets are off. If any of the branches has a mandatory
  282. # fields we must produce an error:
  283. for i in 1..<recNode.len: collectMissingFields recNode[i]
  284. of nkSym:
  285. let field = recNode.sym
  286. let e = semConstrField(c, flags, field, constrCtx.initExpr)
  287. result = if e != nil: initFull else: initNone
  288. else:
  289. internalAssert c.config, false
  290. proc semConstructTypeAux(c: PContext,
  291. constrCtx: var ObjConstrContext,
  292. flags: TExprFlags): InitStatus =
  293. result = initUnknown
  294. var t = constrCtx.typ
  295. while true:
  296. let status = semConstructFields(c, t.n, constrCtx, flags)
  297. mergeInitStatus(result, status)
  298. if status in {initPartial, initNone, initUnknown}:
  299. collectMissingFields c, t.n, constrCtx
  300. let base = t[0]
  301. if base == nil: break
  302. t = skipTypes(base, skipPtrs)
  303. if t.kind != tyObject:
  304. # XXX: This is not supposed to happen, but apparently
  305. # there are some issues in semtypinst. Luckily, it
  306. # seems to affect only `computeRequiresInit`.
  307. return
  308. constrCtx.needsFullInit = constrCtx.needsFullInit or
  309. tfNeedsFullInit in t.flags
  310. proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext =
  311. ObjConstrContext(typ: t, initExpr: initExpr,
  312. needsFullInit: tfNeedsFullInit in t.flags)
  313. proc computeRequiresInit(c: PContext, t: PType): bool =
  314. assert t.kind == tyObject
  315. var constrCtx = initConstrContext(t, newNode(nkObjConstr))
  316. let initResult = semConstructTypeAux(c, constrCtx, {})
  317. constrCtx.missingFields.len > 0
  318. proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
  319. var objType = t
  320. while objType.kind != tyObject:
  321. objType = objType.lastSon
  322. assert objType != nil
  323. var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info))
  324. let initResult = semConstructTypeAux(c, constrCtx, {})
  325. assert constrCtx.missingFields.len > 0
  326. localError(c.config, info,
  327. "The $1 type doesn't have a default value. The following fields must be initialized: $2.",
  328. [typeToString(t), listSymbolNames(constrCtx.missingFields)])
  329. proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
  330. var t = semTypeNode(c, n[0], nil)
  331. result = newNodeIT(nkObjConstr, n.info, t)
  332. for child in n: result.add child
  333. if t == nil:
  334. localError(c.config, n.info, errGenerated, "object constructor needs an object type")
  335. return
  336. t = skipTypes(t, {tyGenericInst, tyAlias, tySink, tyOwned})
  337. if t.kind == tyRef:
  338. t = skipTypes(t[0], {tyGenericInst, tyAlias, tySink, tyOwned})
  339. if optOwnedRefs in c.config.globalOptions:
  340. result.typ = makeVarType(c, result.typ, tyOwned)
  341. # we have to watch out, there are also 'owned proc' types that can be used
  342. # multiple times as long as they don't have closures.
  343. result.typ.flags.incl tfHasOwned
  344. if t.kind != tyObject:
  345. localError(c.config, n.info, errGenerated, "object constructor needs an object type")
  346. return
  347. # Check if the object is fully initialized by recursively testing each
  348. # field (if this is a case object, initialized fields in two different
  349. # branches will be reported as an error):
  350. var constrCtx = initConstrContext(t, result)
  351. let initResult = semConstructTypeAux(c, constrCtx, flags)
  352. # It's possible that the object was not fully initialized while
  353. # specifying a .requiresInit. pragma:
  354. if constrCtx.missingFields.len > 0:
  355. localError(c.config, result.info,
  356. "The $1 type requires the following fields to be initialized: $2.",
  357. [t.sym.name.s, listSymbolNames(constrCtx.missingFields)])
  358. # Since we were traversing the object fields, it's possible that
  359. # not all of the fields specified in the constructor was visited.
  360. # We'll check for such fields here:
  361. for i in 1..<result.len:
  362. let field = result[i]
  363. if nfSem notin field.flags:
  364. if field.kind != nkExprColonExpr:
  365. invalidObjConstr(c, field)
  366. continue
  367. let id = considerQuotedIdent(c, field[0])
  368. # This node was not processed. There are two possible reasons:
  369. # 1) It was shadowed by a field with the same name on the left
  370. for j in 1..<i:
  371. let prevId = considerQuotedIdent(c, result[j][0])
  372. if prevId.id == id.id:
  373. localError(c.config, field.info, errFieldInitTwice % id.s)
  374. return
  375. # 2) No such field exists in the constructed type
  376. localError(c.config, field.info, errUndeclaredFieldX % id.s)
  377. return
  378. if initResult == initFull:
  379. incl result.flags, nfAllFieldsSet