123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- #
- #
- # 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")
- when false:
- # attempted type inference for generic object types,
- # doesn't work since n[0] isn't set and seems underspecified
- 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)
|