mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-19 01:18:32 +00:00
move the object construction logic to a separate file
This commit is contained in:
@@ -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,
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
292
compiler/semobjconstr.nim
Normal file
292
compiler/semobjconstr.nim
Normal file
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user