semobjconstr.nim 16 KB

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