diff options
author | Zahary Karadjov <zahary@gmail.com> | 2017-04-06 00:44:46 +0300 |
---|---|---|
committer | Zahary Karadjov <zahary@gmail.com> | 2017-04-06 00:46:18 +0300 |
commit | 34c34cb49b63b04ddb2b0b2680210da742a4545d (patch) | |
tree | f61a91bda15948b4626392ecebd618303bb267c6 /compiler | |
parent | f162214d5d509945a36f70b7ea6ffeb33ad17fe7 (diff) | |
download | Nim-34c34cb49b63b04ddb2b0b2680210da742a4545d.tar.gz |
move the object construction logic to a separate file
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/semdata.nim | 2 | ||||
-rw-r--r-- | compiler/semexprs.nim | 278 | ||||
-rw-r--r-- | compiler/semobjconstr.nim | 292 |
3 files changed, 294 insertions, 278 deletions
diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 3abfeac21..8f2c802de 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -57,7 +57,7 @@ type # but you don't want to trigger a hard error. For example, # you may be in position to supply a better error message # to the user. - efWantStmt, efAllowStmt, efDetermineType, exExplain, + efWantStmt, efAllowStmt, efDetermineType, efExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo, diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 5a71896ef..316cf55c8 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2097,283 +2097,7 @@ proc isTupleType(n: PNode): bool = return false return true -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 quoeStr(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.quoeStr - -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 semContructType(c: PContext, t: PType, - initExpr: PNode, flags: TExprFlags): InitStatus = - var t = t - result = initUnknown - - while true: - let status = semConstrFields(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) - result.add n.sons[0] - 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 = semContructType(c, t, n, 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.. <n.len: - let field = n[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(n[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 +include semobjconstr proc semBlock(c: PContext, n: PNode): PNode = result = n 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 + |