# # # 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 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 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 let fieldId = field.name.id for i in 1.. 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.. 0 proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) = var objType = t while objType.kind notin {tyObject, tyDistinct}: objType = objType.lastSon assert objType != nil if objType.kind == tyObject: var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info)) let initResult = semConstructTypeAux(c, constrCtx, {efWantNoDefaults}) 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." type ObjConstrError = enum none discriminatorError = "The discriminator can only be initialized with unnamed fields known at the compile time" mixingError = "When mixing named fields and unnamed fields, every field needs to be initialized in order" lackingError = "The object construction is given more fields than required" proc filterObjConstr(c: PContext; field: PNode, n: PNode, iterField: var int, flags: TExprFlags, write: bool): ObjConstrError = result = none if iterField >= n.len: return mixingError case field.kind of nkRecCase: # handle defaults if the ast of the field is known var discriminatorVal = case n[iterField].kind of nkExprColonExpr: semExprFlagDispatched(c, n[iterField][1], flags + {efPreferStatic}) else: semExprFlagDispatched(c, n[iterField], flags + {efPreferStatic}) let ret = filterObjConstr(c, field[0], n, iterField, flags, write) if ret != none: return ret if discriminatorVal == nil or discriminatorVal.kind != nkIntLit: return discriminatorError let matchedBranch = field.pickCaseBranch discriminatorVal if matchedBranch != nil: result = filterObjConstr(c, matchedBranch.lastSon, n, iterField, flags, write) else: result = none of nkSym: if n[iterField].kind == nkExprColonExpr and field.sym.name.id == considerQuotedIdent(c, n[iterField][0]).id: inc iterField elif not fieldVisible(c, field.sym): discard elif n[iterField].kind != nkExprColonExpr: if write: n[iterField] = newTree(nkExprColonExpr, field, n[iterField]) inc iterField else: result = mixingError of nkRecList: for f in field: let ret = filterObjConstr(c, f, n, iterField, flags, write) if ret != none: result = ret break else: assert false proc expandObjConstr(c: PContext, n: PNode, t: PType, flags: TExprFlags): PNode = result = n var hasValue = false for i in 1.. result.len: localError(c.config, result.info, $mixingError) elif iterField < result.len: localError(c.config, result.info, $lackingError) proc useObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): bool = var n = copyTree(n) var t = semTypeNode(c, n[0], nil) if t == nil: return false 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}) if t.kind != tyObject: return false for i in 1.. 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..