diff options
Diffstat (limited to 'compiler/semobjconstr.nim')
-rw-r--r-- | compiler/semobjconstr.nim | 314 |
1 files changed, 209 insertions, 105 deletions
diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 792488c9f..048053115 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -11,6 +11,8 @@ # included from sem.nim +from std/sugar import dup + type ObjConstrContext = object typ: PType # The constructed type @@ -19,6 +21,7 @@ type # 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 @@ -27,6 +30,10 @@ type 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: @@ -55,14 +62,13 @@ proc invalidObjConstr(c: PContext, n: PNode) = 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) - continue - - if fieldId == considerQuotedIdent(c, assignment[0]).id: + elif fieldId == considerQuotedIdent(c, assignment[0]).id: return assignment proc semConstrField(c: PContext, flags: TExprFlags, @@ -70,27 +76,23 @@ proc semConstrField(c: PContext, flags: TExprFlags, let assignment = locateFieldInInitExpr(c, field, initExpr) if assignment != nil: if nfSem in assignment.flags: return assignment[1] - if not fieldVisible(c, field): + 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) + var initValue = semExprFlagDispatched(c, assignment[1], flags, field.typ) if initValue != nil: - initValue = fitNode(c, field.typ, initValue, assignment.info) + initValue = fitNodeConsiderViewType(c, field.typ, initValue, assignment.info) + initValue.flags.incl nfSkipFieldChecking assignment[0] = newSymNode(field) assignment[1] = initValue assignment.flags.incl nfSem - return initValue - -proc caseBranchMatchesExpr(branch, matched: PNode): bool = - for i in 0..<branch.len-1: - if branch[i].kind == nkRange: - if overlap(branch[i], matched): return true - elif exprStructuralEquivalent(branch[i], matched): - return true - - return false + result = initValue + else: + result = nil proc branchVals(c: PContext, caseNode: PNode, caseIdx: int, isStmtBranch: bool): IntSet = @@ -105,6 +107,7 @@ proc branchVals(c: PContext, caseNode: PNode, caseIdx: int, 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] @@ -120,7 +123,9 @@ proc pickCaseBranch(caseExpr, matched: PNode): PNode = return caseExpr[i] if endsWithElse: - return caseExpr[^1] + result = caseExpr[^1] + else: + result = nil iterator directFieldsInRecList(recList: PNode): PNode = # XXX: We can remove this case by making all nkOfBranch nodes @@ -131,8 +136,8 @@ iterator directFieldsInRecList(recList: PNode): PNode = else: doAssert recList.kind == nkRecList for field in recList: - if field.kind != nkSym: continue - yield field + if field.kind == nkSym: + yield field template quoteStr(s: string): string = "'" & s & "'" @@ -143,48 +148,90 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin 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) = + constrCtx: var ObjConstrContext, defaults: seq[PNode] + ): seq[PSym] = + result = @[] for r in directFieldsInRecList(fieldsRecList): - if constrCtx.needsFullInit or - sfRequiresInit in r.sym.flags or - r.sym.typ.requiresInit: - let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr) - if assignment == nil: + 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 semConstructFields(c: PContext, n: PNode, - constrCtx: var ObjConstrContext, - flags: TExprFlags): InitStatus = - result = initUnknown - +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 status = semConstructFields(c, field, constrCtx, flags) - mergeInitStatus(result, status) - + 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) - template collectMissingFields(branchNode: PNode) = - if branchNode != nil: - let fields = branchNode[^1] - collectMissingFields(c, fields, constrCtx) - 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) + let (status, _) = semConstructFields(c, innerRecords, constrCtx, flags) # todo if status notin {initNone, initUnknown}: - mergeInitStatus(result, status) + result.status.mergeInitStatus status if selectedBranch != -1: let prevFields = fieldsPresentInBranch(selectedBranch) let currentFields = fieldsPresentInBranch(i) @@ -192,26 +239,28 @@ proc semConstructFields(c: PContext, n: PNode, ("The fields '$1' and '$2' cannot be initialized together, " & "because they are from conflicting branches in the case object.") % [prevFields, currentFields]) - result = initConflict + result.status = initConflict else: selectedBranch = i if selectedBranch != -1: template badDiscriminatorError = - 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, initNone) + 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) = - 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]) + 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 " & @@ -220,8 +269,7 @@ proc semConstructFields(c: PContext, n: PNode, valsDiff.renderAsType(n[0].typ)]) let branchNode = n[selectedBranch] - let flags = flags*{efAllowDestructor} + {efPreferStatic, - efPreferNilResult} + let flags = {efPreferStatic, efPreferNilResult} var discriminatorVal = semConstrField(c, flags, discriminator.sym, constrCtx.initExpr) @@ -250,9 +298,10 @@ proc semConstructFields(c: PContext, n: PNode, badDiscriminatorError() elif discriminatorVal.sym.kind notin {skLet, skParam} or discriminatorVal.sym.typ.kind in {tyVar}: - localError(c.config, discriminatorVal.info, - "runtime discriminator must be immutable if branch fields are " & - "initialized, a 'let' binding is required.") + 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 " & @@ -285,56 +334,92 @@ proc semConstructFields(c: PContext, n: PNode, 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 == initPartial: - collectMissingFields branchNode - + if result.status == initPartial: + collectOrAddMissingCaseFields(c, branchNode, constrCtx, result.defaults) else: - result = initNone + result.status = initNone let discriminatorVal = semConstrField(c, flags + {efPreferStatic}, discriminator.sym, constrCtx.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 defaultValue = newIntLit(c.graph, constrCtx.initExpr.info, 0) - let matchedBranch = n.pickCaseBranch defaultValue - collectMissingFields matchedBranch + 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 = initPartial + 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: collectMissingFields matchedBranch + if matchedBranch != nil: + let (_, defaults) = semConstructFields(c, matchedBranch[^1], constrCtx, flags) + result.defaults.add defaults + collectOrAddMissingCaseFields(c, matchedBranch, constrCtx, result.defaults) else: - # All bets are off. If any of the branches has a mandatory - # fields we must produce an error: - for i in 1..<n.len: collectMissingFields n[i] + collectBranchFields(c, n, discriminatorVal, constrCtx, flags) of nkSym: let field = n.sym let e = semConstrField(c, flags, field, constrCtx.initExpr) - result = if e != nil: initFull else: initNone - + 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): InitStatus = - result = initUnknown + flags: TExprFlags): tuple[status: InitStatus, defaults: seq[PNode]] = + result = (initUnknown, @[]) var t = constrCtx.typ while true: - let status = semConstructFields(c, t.n, constrCtx, flags) - mergeInitStatus(result, status) + let (status, defaults) = semConstructFields(c, t.n, constrCtx, flags) + result.status.mergeInitStatus status + result.defaults.add defaults if status in {initPartial, initNone, initUnknown}: - collectMissingFields c, t.n, constrCtx - let base = t[0] - if base == nil: break + 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 @@ -351,60 +436,70 @@ proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext = proc computeRequiresInit(c: PContext, t: PType): bool = assert t.kind == tyObject var constrCtx = initConstrContext(t, newNode(nkObjConstr)) - let initResult = semConstructTypeAux(c, constrCtx, {}) + 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.lastSon + objType = objType.last assert objType != nil if objType.kind == tyObject: var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info)) - let initResult = semConstructTypeAux(c, constrCtx, {}) - assert 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)]) + 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)]) + "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): PNode = +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 child in n: result.add child + for i in 0..<n.len: + result.add n[i] if t == nil: - localError(c.config, n.info, errGenerated, "object constructor needs an object type") - return + return localErrorNode(c, result, "object constructor needs an object type") + + 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[0], {tyGenericInst, tyAlias, tySink, tyOwned}) + 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: - localError(c.config, n.info, errGenerated, "object constructor needs an object type") - return + 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 = semConstructTypeAux(c, constrCtx, flags) + 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.", + "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 @@ -415,6 +510,7 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = 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: @@ -423,10 +519,18 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = let prevId = considerQuotedIdent(c, result[j][0]) if prevId.id == id.id: localError(c.config, field.info, errFieldInitTwice % id.s) - return + hasError = true + break # 2) No such field exists in the constructed type - localError(c.config, field.info, errUndeclaredFieldX % id.s) - return + 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) |