semobjconstr.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. InitStatus = enum
  13. initUnknown
  14. initFull # All of the fields have been initialized
  15. initPartial # Some of the fields have been initialized
  16. initNone # None of the fields have been initialized
  17. initConflict # Fields from different branches have been initialized
  18. proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) =
  19. case newStatus
  20. of initConflict:
  21. existing = newStatus
  22. of initPartial:
  23. if existing in {initUnknown, initFull, initNone}:
  24. existing = initPartial
  25. of initNone:
  26. if existing == initUnknown:
  27. existing = initNone
  28. elif existing == initFull:
  29. existing = initPartial
  30. of initFull:
  31. if existing == initUnknown:
  32. existing = initFull
  33. elif existing == initNone:
  34. existing = initPartial
  35. of initUnknown:
  36. discard
  37. proc locateFieldInInitExpr(field: PSym, initExpr: PNode): PNode =
  38. # Returns the assignment nkExprColonExpr node or nil
  39. let fieldId = field.name.id
  40. for i in 1 .. <initExpr.len:
  41. let assignment = initExpr[i]
  42. if assignment.kind != nkExprColonExpr:
  43. localError(initExpr.info, "incorrect object construction syntax")
  44. continue
  45. if fieldId == considerQuotedIdent(assignment[0]).id:
  46. return assignment
  47. proc semConstrField(c: PContext, flags: TExprFlags,
  48. field: PSym, initExpr: PNode): PNode =
  49. let assignment = locateFieldInInitExpr(field, initExpr)
  50. if assignment != nil:
  51. if nfSem in assignment.flags: return assignment[1]
  52. if not fieldVisible(c, field):
  53. localError(initExpr.info,
  54. "the field '$1' is not accessible.", [field.name.s])
  55. return
  56. var initValue = semExprFlagDispatched(c, assignment[1], flags)
  57. if initValue != nil:
  58. initValue = fitNode(c, field.typ, initValue, assignment.info)
  59. assignment.sons[0] = newSymNode(field)
  60. assignment.sons[1] = initValue
  61. assignment.flags.incl nfSem
  62. return initValue
  63. proc caseBranchMatchesExpr(branch, matched: PNode): bool =
  64. for i in 0 .. (branch.len - 2):
  65. if exprStructuralEquivalent(branch[i], matched):
  66. return true
  67. return false
  68. proc pickCaseBranch(caseExpr, matched: PNode): PNode =
  69. # XXX: Perhaps this proc already exists somewhere
  70. let endsWithElse = caseExpr{-1}.kind == nkElse
  71. for i in 1 .. caseExpr.len - 1 - int(endsWithElse):
  72. if caseExpr[i].caseBranchMatchesExpr(matched):
  73. return caseExpr[i]
  74. if endsWithElse:
  75. return caseExpr{-1}
  76. iterator directFieldsInRecList(recList: PNode): PNode =
  77. # XXX: We can remove this case by making all nkOfBranch nodes
  78. # regular. Currently, they try to avoid using nkRecList if they
  79. # include only a single field
  80. if recList.kind == nkSym:
  81. yield recList
  82. else:
  83. internalAssert recList.kind == nkRecList
  84. for field in recList:
  85. if field.kind != nkSym: continue
  86. yield field
  87. template quoteStr(s: string): string = "'" & s & "'"
  88. proc fieldsPresentInInitExpr(fieldsRecList, initExpr: PNode): string =
  89. result = ""
  90. for field in directFieldsInRecList(fieldsRecList):
  91. let assignment = locateFieldInInitExpr(field.sym, initExpr)
  92. if assignment != nil:
  93. if result.len != 0: result.add ", "
  94. result.add field.sym.name.s.quoteStr
  95. proc missingMandatoryFields(fieldsRecList, initExpr: PNode): string =
  96. for r in directFieldsInRecList(fieldsRecList):
  97. if {tfNotNil, tfNeedsInit} * r.sym.typ.flags != {}:
  98. let assignment = locateFieldInInitExpr(r.sym, initExpr)
  99. if assignment == nil:
  100. if result == nil:
  101. result = r.sym.name.s
  102. else:
  103. result.add ", "
  104. result.add r.sym.name.s
  105. proc checkForMissingFields(recList, initExpr: PNode) =
  106. let missing = missingMandatoryFields(recList, initExpr)
  107. if missing != nil:
  108. localError(initExpr.info, "fields not initialized: $1.", [missing])
  109. proc semConstructFields(c: PContext, recNode: PNode,
  110. initExpr: PNode, flags: TExprFlags): InitStatus =
  111. result = initUnknown
  112. case recNode.kind
  113. of nkRecList:
  114. for field in recNode:
  115. let status = semConstructFields(c, field, initExpr, flags)
  116. mergeInitStatus(result, status)
  117. of nkRecCase:
  118. template fieldsPresentInBranch(branchIdx: int): string =
  119. fieldsPresentInInitExpr(recNode[branchIdx]{-1}, initExpr)
  120. template checkMissingFields(branchNode: PNode) =
  121. checkForMissingFields(branchNode{-1}, initExpr)
  122. let discriminator = recNode.sons[0];
  123. internalAssert discriminator.kind == nkSym
  124. var selectedBranch = -1
  125. for i in 1 .. <recNode.len:
  126. let innerRecords = recNode[i]{-1}
  127. let status = semConstructFields(c, innerRecords, initExpr, flags)
  128. if status notin {initNone, initUnknown}:
  129. mergeInitStatus(result, status)
  130. if selectedBranch != -1:
  131. let prevFields = fieldsPresentInBranch(selectedBranch)
  132. let currentFields = fieldsPresentInBranch(i)
  133. localError(initExpr.info,
  134. "The fields ($1) and ($2) cannot be initialized together, " &
  135. "because they are from conflicting branches in the case object.",
  136. [prevFields, currentFields])
  137. result = initConflict
  138. else:
  139. selectedBranch = i
  140. if selectedBranch != -1:
  141. let branchNode = recNode[selectedBranch]
  142. let flags = flags*{efAllowDestructor} + {efNeedStatic, efPreferNilResult}
  143. let discriminatorVal = semConstrField(c, flags,
  144. discriminator.sym, initExpr)
  145. if discriminatorVal == nil:
  146. let fields = fieldsPresentInBranch(selectedBranch)
  147. localError(initExpr.info,
  148. "you must provide a compile-time value for the discriminator '$1' " &
  149. "in order to prove that it's safe to initialize $2.",
  150. [discriminator.sym.name.s, fields])
  151. mergeInitStatus(result, initNone)
  152. else:
  153. let discriminatorVal = discriminatorVal.skipHidden
  154. template wrongBranchError(i) =
  155. let fields = fieldsPresentInBranch(i)
  156. localError(initExpr.info,
  157. "a case selecting discriminator '$1' with value '$2' " &
  158. "appears in the object construction, but the field(s) $3 " &
  159. "are in conflict with this value.",
  160. [discriminator.sym.name.s, discriminatorVal.renderTree, fields])
  161. if branchNode.kind != nkElse:
  162. if not branchNode.caseBranchMatchesExpr(discriminatorVal):
  163. wrongBranchError(selectedBranch)
  164. else:
  165. # With an else clause, check that all other branches don't match:
  166. for i in 1 .. (recNode.len - 2):
  167. if recNode[i].caseBranchMatchesExpr(discriminatorVal):
  168. wrongBranchError(i)
  169. break
  170. # When a branch is selected with a partial match, some of the fields
  171. # that were not initialized may be mandatory. We must check for this:
  172. if result == initPartial:
  173. checkMissingFields branchNode
  174. else:
  175. result = initNone
  176. let discriminatorVal = semConstrField(c, flags + {efPreferStatic},
  177. discriminator.sym, initExpr)
  178. if discriminatorVal == nil:
  179. # None of the branches were explicitly selected by the user and no
  180. # value was given to the discrimator. We can assume that it will be
  181. # initialized to zero and this will select a particular branch as
  182. # a result:
  183. let matchedBranch = recNode.pickCaseBranch newIntLit(0)
  184. checkMissingFields matchedBranch
  185. else:
  186. result = initPartial
  187. if discriminatorVal.kind == nkIntLit:
  188. # When the discriminator is a compile-time value, we also know
  189. # which brach will be selected:
  190. let matchedBranch = recNode.pickCaseBranch discriminatorVal
  191. if matchedBranch != nil: checkMissingFields matchedBranch
  192. else:
  193. # All bets are off. If any of the branches has a mandatory
  194. # fields we must produce an error:
  195. for i in 1 .. <recNode.len: checkMissingFields recNode[i]
  196. of nkSym:
  197. let field = recNode.sym
  198. let e = semConstrField(c, flags, field, initExpr)
  199. result = if e != nil: initFull else: initNone
  200. else:
  201. internalAssert false
  202. proc semConstructType(c: PContext, initExpr: PNode,
  203. t: PType, flags: TExprFlags): InitStatus =
  204. var t = t
  205. result = initUnknown
  206. while true:
  207. let status = semConstructFields(c, t.n, initExpr, flags)
  208. mergeInitStatus(result, status)
  209. if status in {initPartial, initNone, initUnknown}:
  210. checkForMissingFields t.n, initExpr
  211. let base = t.sons[0]
  212. if base == nil: break
  213. t = skipTypes(base, skipPtrs)
  214. proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
  215. var t = semTypeNode(c, n.sons[0], nil)
  216. result = newNodeIT(nkObjConstr, n.info, t)
  217. for child in n: result.add child
  218. t = skipTypes(t, {tyGenericInst, tyAlias})
  219. if t.kind == tyRef: t = skipTypes(t.sons[0], {tyGenericInst, tyAlias})
  220. if t.kind != tyObject:
  221. localError(n.info, errGenerated, "object constructor needs an object type")
  222. return
  223. # Check if the object is fully initialized by recursively testing each
  224. # field (if this is a case object, initialized fields in two different
  225. # branches will be reported as an error):
  226. let initResult = semConstructType(c, result, t, flags)
  227. # It's possible that the object was not fully initialized while
  228. # specifying a .requiresInit. pragma.
  229. # XXX: Turn this into an error in the next release
  230. if tfNeedsInit in t.flags and initResult != initFull:
  231. # XXX: Disable this warning for now, because tfNeedsInit is propagated
  232. # too aggressively from fields to object types (and this is not correct
  233. # in case objects)
  234. when false: message(n.info, warnUser,
  235. "object type uses the 'requiresInit' pragma, but not all fields " &
  236. "have been initialized. future versions of Nim will treat this as " &
  237. "an error")
  238. # Since we were traversing the object fields, it's possible that
  239. # not all of the fields specified in the constructor was visited.
  240. # We'll check for such fields here:
  241. for i in 1.. <result.len:
  242. let field = result[i]
  243. if nfSem notin field.flags:
  244. if field.kind != nkExprColonExpr:
  245. localError(n.info, "incorrect object construction syntax")
  246. continue
  247. let id = considerQuotedIdent(field[0])
  248. # This node was not processed. There are two possible reasons:
  249. # 1) It was shadowed by a field with the same name on the left
  250. for j in 1 .. <i:
  251. let prevId = considerQuotedIdent(result[j][0])
  252. if prevId.id == id.id:
  253. localError(field.info, errFieldInitTwice, id.s)
  254. return
  255. # 2) No such field exists in the constructed type
  256. localError(field.info, errUndeclaredFieldX, id.s)
  257. return