mirror of
https://github.com/nim-lang/Nim.git
synced 2026-05-24 21:59:52 +00:00
fixes #25784 This pull request addresses the handling of forward object types during type determination in the Nim compiler and adds new test cases to ensure correct default value initialization for objects with forward references. The main focus is to allow forward object types to remain unresolved during the initial type analysis, deferring their resolution to a later compilation phase. This helps support object constructors with default values involving forward types. **Compiler improvements:** * Updated `semObjConstr` in `compiler/semobjconstr.nim` to allow forward object types (`tyForward`) to remain unresolved during determine-type analysis. This avoids premature errors and ensures that such types are resolved later, supporting delayed field-default resolution. **Testing enhancements:** * Added new test cases in `tests/objects/mobject_default_value.nim` to verify that objects with default fields referencing forward types are correctly initialized, and that their default values are properly set. --------- Co-authored-by: Copilot <copilot@github.com>
553 lines
23 KiB
Nim
553 lines
23 KiB
Nim
#
|
|
#
|
|
# 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 nfPreventCg in assignment.flags:
|
|
# this is an object constructor node generated by the VM and
|
|
# this field is in an inactive case branch, just ignore it
|
|
discard
|
|
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, assignment[0].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.incl tfHasOwned
|
|
if t.kind == tyForward and efDetermineType in flags:
|
|
# a forward object type does not error during determine-type analysis;
|
|
# it now stays unresolved long enough for the existing delayed field-default pass to resolve it after the type section finishes.
|
|
result.typ = t
|
|
return result
|
|
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
|
|
elif nfPreventCg in field.flags:
|
|
# this is an object constructor node generated by the VM and
|
|
# this field is in an inactive case branch, just ignore it
|
|
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[0].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)
|