diff options
Diffstat (limited to 'compiler/semobjconstr.nim')
-rw-r--r-- | compiler/semobjconstr.nim | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim new file mode 100644 index 000000000..f4c225526 --- /dev/null +++ b/compiler/semobjconstr.nim @@ -0,0 +1,292 @@ +# +# +# 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 + +type + InitStatus = enum + 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 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 locateFieldInInitExpr(field: PSym, initExpr: PNode): PNode = + # Returns the assignment nkExprColonExpr node or nil + let fieldId = field.name.id + for i in 1 .. <initExpr.len: + let assignment = initExpr[i] + internalAssert assignment.kind == nkExprColonExpr + + if fieldId == considerQuotedIdent(assignment[0]).id: + return assignment + +proc semConstrField(c: PContext, flags: TExprFlags, + field: PSym, initExpr: PNode): PNode = + let assignment = locateFieldInInitExpr(field, initExpr) + if assignment != nil: + if nfSem in assignment.flags: return assignment[1] + if not fieldVisible(c, field): + localError(initExpr.info, + "the field '$1' is not accessible.", [field.name.s]) + return + + var initValue = semExprFlagDispatched(c, assignment[1], flags) + if initValue != nil: + initValue = fitNode(c, field.typ, initValue, assignment.info) + assignment.sons[0] = newSymNode(field) + assignment.sons[1] = initValue + assignment.flags.incl nfSem + return initValue + +proc caseBranchMatchesExpr(branch, matched: PNode): bool = + for i in 0 .. (branch.len - 2): + if exprStructuralEquivalent(branch[i], matched): + return true + + return false + +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 - 1 - int(endsWithElse): + if caseExpr[i].caseBranchMatchesExpr(matched): + return caseExpr[i] + + if endsWithElse: + return caseExpr{-1} + +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: + internalAssert recList.kind == nkRecList + for field in recList: + if field.kind != nkSym: continue + yield field + +template quoteStr(s: string): string = "'" & s & "'" + +proc fieldsPresentInInitExpr(fieldsRecList, initExpr: PNode): string = + result = "" + for field in directFieldsInRecList(fieldsRecList): + let assignment = locateFieldInInitExpr(field.sym, initExpr) + if assignment != nil: + if result.len != 0: result.add ", " + result.add field.sym.name.s.quoteStr + +proc missingMandatoryFields(fieldsRecList, initExpr: PNode): string = + for r in directFieldsInRecList(fieldsRecList): + if {tfNotNil, tfNeedsInit} * r.sym.typ.flags != {}: + let assignment = locateFieldInInitExpr(r.sym, initExpr) + if assignment == nil: + if result == nil: + result = r.sym.name.s + else: + result.add ", " + result.add r.sym.name.s + +proc checkForMissingFields(recList, initExpr: PNode) = + let missing = missingMandatoryFields(recList, initExpr) + if missing != nil: + localError(initExpr.info, "fields not initialized: $1.", [missing]) + +proc semConstructFields(c: PContext, recNode: PNode, + initExpr: PNode, flags: TExprFlags): InitStatus = + result = initUnknown + + case recNode.kind + of nkRecList: + for field in recNode: + let status = semConstructFields(c, field, initExpr, flags) + mergeInitStatus(result, status) + + of nkRecCase: + template fieldsPresentInBranch(branchIdx: int): string = + fieldsPresentInInitExpr(recNode[branchIdx]{-1}, initExpr) + + template checkMissingFields(branchNode: PNode) = + checkForMissingFields(branchNode{-1}, initExpr) + + let discriminator = recNode.sons[0]; + internalAssert discriminator.kind == nkSym + var selectedBranch = -1 + + for i in 1 .. <recNode.len: + let innerRecords = recNode[i]{-1} + let status = semConstructFields(c, innerRecords, initExpr, flags) + if status notin {initNone, initUnknown}: + mergeInitStatus(result, status) + if selectedBranch != -1: + let prevFields = fieldsPresentInBranch(selectedBranch) + let currentFields = fieldsPresentInBranch(i) + localError(initExpr.info, + "The fields ($1) and ($2) cannot be initialized together, " & + "because they are from conflicting branches in the case object.", + [prevFields, currentFields]) + result = initConflict + else: + selectedBranch = i + + if selectedBranch != -1: + let branchNode = recNode[selectedBranch] + let flags = flags*{efAllowDestructor} + {efNeedStatic, efPreferNilResult} + let discriminatorVal = semConstrField(c, flags, + discriminator.sym, initExpr) + if discriminatorVal == nil: + let fields = fieldsPresentInBranch(selectedBranch) + localError(initExpr.info, + "you must provide a compile-time value for the discriminator '$1' " & + "in order to prove that it's safe to initialize $2.", + [discriminator.sym.name.s, fields]) + mergeInitStatus(result, initNone) + else: + let discriminatorVal = discriminatorVal.skipHidden + + template wrongBranchError(i) = + let fields = fieldsPresentInBranch(i) + localError(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]) + + if branchNode.kind != nkElse: + if not branchNode.caseBranchMatchesExpr(discriminatorVal): + wrongBranchError(selectedBranch) + else: + # With an else clause, check that all other branches don't match: + for i in 1 .. (recNode.len - 2): + if recNode[i].caseBranchMatchesExpr(discriminatorVal): + wrongBranchError(i) + break + + # 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 == initPartial: + checkMissingFields branchNode + + else: + result = initNone + let discriminatorVal = semConstrField(c, flags + {efPreferStatic}, + discriminator.sym, initExpr) + if discriminatorVal == nil: + # 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 matchedBranch = recNode.pickCaseBranch newIntLit(0) + checkMissingFields matchedBranch + else: + result = initPartial + if discriminatorVal.kind == nkIntLit: + # When the discriminator is a compile-time value, we also know + # which brach will be selected: + let matchedBranch = recNode.pickCaseBranch discriminatorVal + if matchedBranch != nil: checkMissingFields matchedBranch + else: + # All bets are off. If any of the branches has a mandatory + # fields we must produce an error: + for i in 1 .. <recNode.len: checkMissingFields recNode[i] + + of nkSym: + let field = recNode.sym + let e = semConstrField(c, flags, field, initExpr) + result = if e != nil: initFull else: initNone + + else: + internalAssert false + +proc semConstructType(c: PContext, initExpr: PNode, + t: PType, flags: TExprFlags): InitStatus = + var t = t + result = initUnknown + + while true: + let status = semConstructFields(c, t.n, initExpr, flags) + mergeInitStatus(result, status) + + if status in {initPartial, initNone, initUnknown}: + checkForMissingFields t.n, initExpr + + let base = t.sons[0] + if base == nil: break + t = skipTypes(base, skipPtrs) + +proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = + var t = semTypeNode(c, n.sons[0], nil) + result = newNodeIT(nkObjConstr, n.info, t) + for child in n: result.add child + + t = skipTypes(t, {tyGenericInst, tyAlias}) + if t.kind == tyRef: t = skipTypes(t.sons[0], {tyGenericInst, tyAlias}) + if t.kind != tyObject: + localError(n.info, errGenerated, "object constructor needs an object type") + return + + # 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): + let initResult = semConstructType(c, result, t, flags) + + # It's possible that the object was not fully initialized while + # specifying a .requiresInit. pragma. + # XXX: Turn this into an error in the next release + if tfNeedsInit in t.flags and initResult != initFull: + # XXX: Disable this warning for now, because tfNeedsInit is propagated + # too aggressively from fields to object types (and this is not correct + # in case objects) + when false: message(n.info, warnUser, + "object type uses the 'requiresInit' pragma, but not all fields " & + "have been initialized. future versions of Nim will treat this as " & + "an error") + + # 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: + let id = considerQuotedIdent(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(result[j][0]) + if prevId.id == id.id: + localError(field.info, errFieldInitTwice, id.s) + return + # 2) No such field exists in the constructed type + localError(field.info, errUndeclaredFieldX, id.s) + return + |