semobjconstr.nim 19 KB

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