1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
|
#
#
# The Nim Compiler
# (c) Copyright 2015 Nim Contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements Nim's object construction rules.
# included from sem.nim
from std/sugar import dup
type
ObjConstrContext = object
typ: PType # The constructed type
initExpr: PNode # The init expression (nkObjConstr)
needsFullInit: bool # A `requiresInit` derived type will
# set this to true while visiting
# parent types.
missingFields: seq[PSym] # Fields that the user failed to specify
checkDefault: bool # Checking defaults
InitStatus = enum # This indicates the result of object construction
initUnknown
initFull # All of the fields have been initialized
initPartial # Some of the fields have been initialized
initNone # None of the fields have been initialized
initConflict # Fields from different branches have been initialized
proc semConstructFields(c: PContext, n: PNode, constrCtx: var ObjConstrContext,
flags: TExprFlags): tuple[status: InitStatus, defaults: seq[PNode]]
proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) =
case newStatus
of initConflict:
existing = newStatus
of initPartial:
if existing in {initUnknown, initFull, initNone}:
existing = initPartial
of initNone:
if existing == initUnknown:
existing = initNone
elif existing == initFull:
existing = initPartial
of initFull:
if existing == initUnknown:
existing = initFull
elif existing == initNone:
existing = initPartial
of initUnknown:
discard
proc invalidObjConstr(c: PContext, n: PNode) =
if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s[0] == ':':
localError(c.config, n.info, "incorrect object construction syntax; use a space after the colon")
else:
localError(c.config, n.info, "incorrect object construction syntax")
proc locateFieldInInitExpr(c: PContext, field: PSym, initExpr: PNode): PNode =
# Returns the assignment nkExprColonExpr node or nil
result = nil
let fieldId = field.name.id
for i in 1..<initExpr.len:
let assignment = initExpr[i]
if assignment.kind != nkExprColonExpr:
invalidObjConstr(c, assignment)
elif fieldId == considerQuotedIdent(c, assignment[0]).id:
return assignment
proc semConstrField(c: PContext, flags: TExprFlags,
field: PSym, initExpr: PNode): PNode =
let assignment = locateFieldInInitExpr(c, field, initExpr)
if assignment != nil:
if nfSem in assignment.flags: return assignment[1]
if nfSkipFieldChecking in assignment[1].flags:
discard
elif not fieldVisible(c, field):
localError(c.config, initExpr.info,
"the field '$1' is not accessible." % [field.name.s])
return
var initValue = semExprFlagDispatched(c, assignment[1], flags, field.typ)
if initValue != nil:
initValue = fitNodeConsiderViewType(c, field.typ, initValue, assignment.info)
initValue.flags.incl nfSkipFieldChecking
assignment[0] = newSymNode(field)
assignment[1] = initValue
assignment.flags.incl nfSem
result = initValue
else:
result = nil
proc branchVals(c: PContext, caseNode: PNode, caseIdx: int,
isStmtBranch: bool): IntSet =
if caseNode[caseIdx].kind == nkOfBranch:
result = initIntSet()
for val in processBranchVals(caseNode[caseIdx]):
result.incl(val)
else:
result = c.getIntSetOfType(caseNode[0].typ)
for i in 1..<caseNode.len-1:
for val in processBranchVals(caseNode[i]):
result.excl(val)
proc findUsefulCaseContext(c: PContext, discrimator: PNode): (PNode, int) =
result = (nil, 0)
for i in countdown(c.p.caseContext.high, 0):
let
(caseNode, index) = c.p.caseContext[i]
skipped = caseNode[0].skipHidden
if skipped.kind == nkSym and skipped.sym == discrimator.sym:
return (caseNode, index)
proc pickCaseBranch(caseExpr, matched: PNode): PNode =
# XXX: Perhaps this proc already exists somewhere
let endsWithElse = caseExpr[^1].kind == nkElse
for i in 1..<caseExpr.len - int(endsWithElse):
if caseExpr[i].caseBranchMatchesExpr(matched):
return caseExpr[i]
if endsWithElse:
result = caseExpr[^1]
else:
result = nil
iterator directFieldsInRecList(recList: PNode): PNode =
# XXX: We can remove this case by making all nkOfBranch nodes
# regular. Currently, they try to avoid using nkRecList if they
# include only a single field
if recList.kind == nkSym:
yield recList
else:
doAssert recList.kind == nkRecList
for field in recList:
if field.kind == nkSym:
yield field
template quoteStr(s: string): string = "'" & s & "'"
proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): string =
result = ""
for field in directFieldsInRecList(fieldsRecList):
if locateFieldInInitExpr(c, field.sym, initExpr) != nil:
if result.len != 0: result.add ", "
result.add field.sym.name.s.quoteStr
proc locateFieldInDefaults(sym: PSym, defaults: seq[PNode]): bool =
result = false
for d in defaults:
if sym.id == d[0].sym.id:
return true
proc collectMissingFields(c: PContext, fieldsRecList: PNode,
constrCtx: var ObjConstrContext, defaults: seq[PNode]
): seq[PSym] =
result = @[]
for r in directFieldsInRecList(fieldsRecList):
let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr)
if assignment == nil and not locateFieldInDefaults(r.sym, defaults):
if constrCtx.needsFullInit or
sfRequiresInit in r.sym.flags or
r.sym.typ.requiresInit:
constrCtx.missingFields.add r.sym
else:
result.add r.sym
proc collectMissingCaseFields(c: PContext, branchNode: PNode,
constrCtx: var ObjConstrContext, defaults: seq[PNode]): seq[PSym] =
if branchNode != nil:
let fieldsRecList = branchNode[^1]
result = collectMissingFields(c, fieldsRecList, constrCtx, defaults)
else:
result = @[]
proc collectOrAddMissingCaseFields(c: PContext, branchNode: PNode,
constrCtx: var ObjConstrContext, defaults: var seq[PNode]) =
let res = collectMissingCaseFields(c, branchNode, constrCtx, defaults)
for sym in res:
let asgnType = newType(tyTypeDesc, c.idgen, sym.typ.owner)
let recTyp = sym.typ.skipTypes(defaultFieldsSkipTypes)
rawAddSon(asgnType, recTyp)
let asgnExpr = newTree(nkCall,
newSymNode(getSysMagic(c.graph, constrCtx.initExpr.info, "zeroDefault", mZeroDefault)),
newNodeIT(nkType, constrCtx.initExpr.info, asgnType)
)
asgnExpr.flags.incl nfSkipFieldChecking
asgnExpr.typ = recTyp
defaults.add newTree(nkExprColonExpr, newSymNode(sym), asgnExpr)
proc collectBranchFields(c: PContext, n: PNode, discriminatorVal: PNode,
constrCtx: var ObjConstrContext, flags: TExprFlags) =
# All bets are off. If any of the branches has a mandatory
# fields we must produce an error:
for i in 1..<n.len:
let branchNode = n[i]
if branchNode != nil:
let oldCheckDefault = constrCtx.checkDefault
constrCtx.checkDefault = true
let (_, defaults) = semConstructFields(c, branchNode[^1], constrCtx, flags)
constrCtx.checkDefault = oldCheckDefault
if len(defaults) > 0:
localError(c.config, discriminatorVal.info, "branch initialization " &
"with a runtime discriminator is not supported " &
"for a branch whose fields have default values.")
discard collectMissingCaseFields(c, n[i], constrCtx, @[])
proc semConstructFields(c: PContext, n: PNode, constrCtx: var ObjConstrContext,
flags: TExprFlags): tuple[status: InitStatus, defaults: seq[PNode]] =
result = (initUnknown, @[])
case n.kind
of nkRecList:
for field in n:
let (subSt, subDf) = semConstructFields(c, field, constrCtx, flags)
result.status.mergeInitStatus subSt
result.defaults.add subDf
of nkRecCase:
template fieldsPresentInBranch(branchIdx: int): string =
let branch = n[branchIdx]
let fields = branch[^1]
fieldsPresentInInitExpr(c, fields, constrCtx.initExpr)
let discriminator = n[0]
internalAssert c.config, discriminator.kind == nkSym
var selectedBranch = -1
for i in 1..<n.len:
let innerRecords = n[i][^1]
let (status, _) = semConstructFields(c, innerRecords, constrCtx, flags) # todo
if status notin {initNone, initUnknown}:
result.status.mergeInitStatus status
if selectedBranch != -1:
let prevFields = fieldsPresentInBranch(selectedBranch)
let currentFields = fieldsPresentInBranch(i)
localError(c.config, constrCtx.initExpr.info,
("The fields '$1' and '$2' cannot be initialized together, " &
"because they are from conflicting branches in the case object.") %
[prevFields, currentFields])
result.status = initConflict
else:
selectedBranch = i
if selectedBranch != -1:
template badDiscriminatorError =
if c.inUncheckedAssignSection == 0:
let fields = fieldsPresentInBranch(selectedBranch)
localError(c.config, constrCtx.initExpr.info,
("cannot prove that it's safe to initialize $1 with " &
"the runtime value for the discriminator '$2' ") %
[fields, discriminator.sym.name.s])
mergeInitStatus(result.status, initNone)
template wrongBranchError(i) =
if c.inUncheckedAssignSection == 0:
let fields = fieldsPresentInBranch(i)
localError(c.config, constrCtx.initExpr.info,
("a case selecting discriminator '$1' with value '$2' " &
"appears in the object construction, but the field(s) $3 " &
"are in conflict with this value.") %
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
template valuesInConflictError(valsDiff) =
localError(c.config, discriminatorVal.info, ("possible values " &
"$2 are in conflict with discriminator values for " &
"selected object branch $1.") % [$selectedBranch,
valsDiff.renderAsType(n[0].typ)])
let branchNode = n[selectedBranch]
let flags = {efPreferStatic, efPreferNilResult}
var discriminatorVal = semConstrField(c, flags,
discriminator.sym,
constrCtx.initExpr)
if discriminatorVal != nil:
discriminatorVal = discriminatorVal.skipHidden
if discriminatorVal.kind notin nkLiterals and (
not isOrdinalType(discriminatorVal.typ, true) or
lengthOrd(c.config, discriminatorVal.typ) > MaxSetElements or
lengthOrd(c.config, n[0].typ) > MaxSetElements):
localError(c.config, discriminatorVal.info,
"branch initialization with a runtime discriminator only " &
"supports ordinal types with 2^16 elements or less.")
if discriminatorVal == nil:
badDiscriminatorError()
elif discriminatorVal.kind == nkSym:
let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal)
if ctorCase == nil:
if discriminatorVal.typ.kind == tyRange:
let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
let recBranchVals = branchVals(c, n, selectedBranch, false)
let diff = rangeVals - recBranchVals
if diff.len != 0:
valuesInConflictError(diff)
else:
badDiscriminatorError()
elif discriminatorVal.sym.kind notin {skLet, skParam} or
discriminatorVal.sym.typ.kind in {tyVar}:
if c.inUncheckedAssignSection == 0:
localError(c.config, discriminatorVal.info,
"runtime discriminator must be immutable if branch fields are " &
"initialized, a 'let' binding is required.")
elif ctorCase[ctorIdx].kind == nkElifBranch:
localError(c.config, discriminatorVal.info, "branch initialization " &
"with a runtime discriminator is not supported inside of an " &
"`elif` branch.")
else:
var
ctorBranchVals = branchVals(c, ctorCase, ctorIdx, true)
recBranchVals = branchVals(c, n, selectedBranch, false)
branchValsDiff = ctorBranchVals - recBranchVals
if branchValsDiff.len != 0:
valuesInConflictError(branchValsDiff)
else:
var failedBranch = -1
if branchNode.kind != nkElse:
if not branchNode.caseBranchMatchesExpr(discriminatorVal):
failedBranch = selectedBranch
else:
# With an else clause, check that all other branches don't match:
for i in 1..<n.len - 1:
if n[i].caseBranchMatchesExpr(discriminatorVal):
failedBranch = i
break
if failedBranch != -1:
if discriminatorVal.typ.kind == tyRange:
let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
let recBranchVals = branchVals(c, n, selectedBranch, false)
let diff = rangeVals - recBranchVals
if diff.len != 0:
valuesInConflictError(diff)
else:
wrongBranchError(failedBranch)
let (_, defaults) = semConstructFields(c, branchNode[^1], constrCtx, flags)
result.defaults.add defaults
# When a branch is selected with a partial match, some of the fields
# that were not initialized may be mandatory. We must check for this:
if result.status == initPartial:
collectOrAddMissingCaseFields(c, branchNode, constrCtx, result.defaults)
else:
result.status = initNone
let discriminatorVal = semConstrField(c, flags + {efPreferStatic},
discriminator.sym,
constrCtx.initExpr)
if discriminatorVal == nil:
if discriminator.sym.ast != nil:
# branch is selected by the default field value of discriminator
let discriminatorDefaultVal = discriminator.sym.ast
result.status = initUnknown
result.defaults.add newTree(nkExprColonExpr, n[0], discriminatorDefaultVal)
if discriminatorDefaultVal.kind == nkIntLit:
let matchedBranch = n.pickCaseBranch discriminatorDefaultVal
if matchedBranch != nil:
let (_, defaults) = semConstructFields(c, matchedBranch[^1], constrCtx, flags)
result.defaults.add defaults
collectOrAddMissingCaseFields(c, matchedBranch, constrCtx, result.defaults)
else:
collectBranchFields(c, n, discriminatorDefaultVal, constrCtx, flags)
else:
# None of the branches were explicitly selected by the user and no
# value was given to the discrimator. We can assume that it will be
# initialized to zero and this will select a particular branch as
# a result:
let defaultValue = newIntLit(c.graph, constrCtx.initExpr.info, 0)
let matchedBranch = n.pickCaseBranch defaultValue
discard collectMissingCaseFields(c, matchedBranch, constrCtx, @[])
else:
result.status = initPartial
if discriminatorVal.kind == nkIntLit:
# When the discriminator is a compile-time value, we also know
# which branch will be selected:
let matchedBranch = n.pickCaseBranch discriminatorVal
if matchedBranch != nil:
let (_, defaults) = semConstructFields(c, matchedBranch[^1], constrCtx, flags)
result.defaults.add defaults
collectOrAddMissingCaseFields(c, matchedBranch, constrCtx, result.defaults)
else:
collectBranchFields(c, n, discriminatorVal, constrCtx, flags)
of nkSym:
let field = n.sym
let e = semConstrField(c, flags, field, constrCtx.initExpr)
if e != nil:
result.status = initFull
elif field.ast != nil:
if efIgnoreDefaults notin flags:
result.status = initUnknown
result.defaults.add newTree(nkExprColonExpr, n, field.ast)
else:
result.status = initNone
else:
if {efWantNoDefaults, efIgnoreDefaults} * flags == {}: # cannot compute defaults at the typeRightPass
let defaultExpr = defaultNodeField(c, n, constrCtx.checkDefault)
if defaultExpr != nil:
result.status = initUnknown
result.defaults.add newTree(nkExprColonExpr, n, defaultExpr)
else:
result.status = initNone
else:
result.status = initNone
else:
internalAssert c.config, false
proc semConstructTypeAux(c: PContext,
constrCtx: var ObjConstrContext,
flags: TExprFlags): tuple[status: InitStatus, defaults: seq[PNode]] =
result = (initUnknown, @[])
var t = constrCtx.typ
while true:
let (status, defaults) = semConstructFields(c, t.n, constrCtx, flags)
result.status.mergeInitStatus status
result.defaults.add defaults
if status in {initPartial, initNone, initUnknown}:
discard collectMissingFields(c, t.n, constrCtx, result.defaults)
let base = t.baseClass
if base == nil or base.id == t.id or
base.kind in {tyRef, tyPtr} and base.elementType.id == t.id:
break
t = skipTypes(base, skipPtrs)
if t.kind != tyObject:
# XXX: This is not supposed to happen, but apparently
# there are some issues in semtypinst. Luckily, it
# seems to affect only `computeRequiresInit`.
return
constrCtx.needsFullInit = constrCtx.needsFullInit or
tfNeedsFullInit in t.flags
proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext =
ObjConstrContext(typ: t, initExpr: initExpr,
needsFullInit: tfNeedsFullInit in t.flags)
proc computeRequiresInit(c: PContext, t: PType): bool =
assert t.kind == tyObject
var constrCtx = initConstrContext(t, newNode(nkObjConstr))
let initResult = semConstructTypeAux(c, constrCtx, {efWantNoDefaults})
constrCtx.missingFields.len > 0
proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
var objType = t
while objType.kind notin {tyObject, tyDistinct}:
objType = objType.last
assert objType != nil
if objType.kind == tyObject:
var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info))
let initResult = semConstructTypeAux(c, constrCtx, {efIgnoreDefaults})
if constrCtx.missingFields.len > 0:
localError(c.config, info,
"The $1 type doesn't have a default value. The following fields must be initialized: $2." % [typeToString(t), listSymbolNames(constrCtx.missingFields)])
elif objType.kind == tyDistinct:
localError(c.config, info,
"The $1 distinct type doesn't have a default value." % typeToString(t))
else:
assert false, "Must not enter here."
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode =
var t = semTypeNode(c, n[0], nil)
result = newNodeIT(nkObjConstr, n.info, t)
for i in 0..<n.len:
result.add n[i]
if t == nil:
return localErrorNode(c, result, "object constructor needs an object type")
if t.skipTypes({tyGenericInst,
tyAlias, tySink, tyOwned, tyRef}).kind != tyObject and
expectedType != nil and expectedType.skipTypes({tyGenericInst,
tyAlias, tySink, tyOwned, tyRef}).kind == tyObject:
t = expectedType
t = skipTypes(t, {tyGenericInst, tyAlias, tySink, tyOwned})
if t.kind == tyRef:
t = skipTypes(t.elementType, {tyGenericInst, tyAlias, tySink, tyOwned})
if optOwnedRefs in c.config.globalOptions:
result.typ = makeVarType(c, result.typ, tyOwned)
# we have to watch out, there are also 'owned proc' types that can be used
# multiple times as long as they don't have closures.
result.typ.flags.incl tfHasOwned
if t.kind != tyObject:
return localErrorNode(c, result, if t.kind != tyGenericBody:
"object constructor needs an object type".dup(addTypeNodeDeclaredLoc(c.config, t))
else: "cannot instantiate: '" &
typeToString(t, preferDesc) &
"'; the object's generic parameters cannot be inferred and must be explicitly given"
)
# Check if the object is fully initialized by recursively testing each
# field (if this is a case object, initialized fields in two different
# branches will be reported as an error):
var constrCtx = initConstrContext(t, result)
let (initResult, defaults) = semConstructTypeAux(c, constrCtx, flags)
var hasError = false # needed to split error detect/report for better msgs
# It's possible that the object was not fully initialized while
# specifying a .requiresInit. pragma:
if constrCtx.missingFields.len > 0:
hasError = true
localError(c.config, result.info,
"The $1 type requires the following fields to be initialized: $2." %
[t.sym.name.s, listSymbolNames(constrCtx.missingFields)])
# Since we were traversing the object fields, it's possible that
# not all of the fields specified in the constructor was visited.
# We'll check for such fields here:
for i in 1..<result.len:
let field = result[i]
if nfSem notin field.flags:
if field.kind != nkExprColonExpr:
invalidObjConstr(c, field)
hasError = true
continue
let id = considerQuotedIdent(c, field[0])
# This node was not processed. There are two possible reasons:
# 1) It was shadowed by a field with the same name on the left
for j in 1..<i:
let prevId = considerQuotedIdent(c, result[j][0])
if prevId.id == id.id:
localError(c.config, field.info, errFieldInitTwice % id.s)
hasError = true
break
# 2) No such field exists in the constructed type
let msg = errUndeclaredField % id.s & " for type " & getProcHeader(c.config, t.sym)
localError(c.config, field.info, msg)
hasError = true
break
result.sons.add defaults
if initResult == initFull:
incl result.flags, nfAllFieldsSet
# wrap in an error see #17437
if hasError: result = errorNode(c, result)
|