diff --git a/compiler/parser.nim b/compiler/parser.nim index 15f2187eda3c3..35beb982181f5 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1898,11 +1898,7 @@ proc parseObjectCase(p: var TParser): PNode = #| | IND{=} objectBranches) result = newNodeP(nkRecCase, p) getTokNoInd(p) - var a = newNodeP(nkIdentDefs, p) - a.add(identWithPragma(p)) - eat(p, tkColon) - a.add(parseTypeDesc(p)) - a.add(p.emptyNode) + var a = parseIdentColonEquals(p, {withPragma}) result.add(a) if p.tok.tokType == tkColon: getTok(p) flexComment(p, result) diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 0ddec1706b050..d07300f501c34 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -75,17 +75,7 @@ proc semConstrField(c: PContext, flags: TExprFlags, assignment.flags.incl nfSem return initValue -proc caseBranchMatchesExpr(branch, matched: PNode): bool = - for i in 0.. 0: localError(c.config, initExpr.info, "fields not initialized: $1.", [missing]) -proc semConstructFields(c: PContext, recNode: PNode, - initExpr: PNode, flags: TExprFlags): InitStatus = - result = initUnknown - +proc checkForNoExplicitlyDefinedFields(c: PContext, recList, initExpr: PNode) = + var existing: string + for r in directFieldsInRecList(recList): + let assignment = locateFieldInInitExpr(c, r.sym, initExpr) + if assignment != nil: + if existing.len == 0: + existing = r.sym.name.s + else: + existing.add ", " + existing.add r.sym.name.s + if existing.len > 0: + localError(c.config, initExpr.info, "runtime discriminator could select " & + "multiple branches, so you can't initialize these fields: $1", [existing]) + +proc semConstructFields(c: PContext, recNode, initExpr: PNode, + flags: TExprFlags): tuple[status: InitStatus, defaults: seq[PNode]] = case recNode.kind of nkRecList: for field in recNode: - let status = semConstructFields(c, field, initExpr, flags) - mergeInitStatus(result, status) - + let (subSt, subDf) = semConstructFields(c, field, initExpr, flags) + result.status.mergeInitStatus subSt + result.defaults.add subDf of nkRecCase: - template fieldsPresentInBranch(branchIdx: int): string = - let branch = recNode[branchIdx] - let fields = branch[^1] - fieldsPresentInInitExpr(c, fields, initExpr) - - template checkMissingFields(branchNode: PNode) = - if branchNode != nil: - let fields = branchNode[^1] - checkForMissingFields(c, fields, initExpr) - + internalAssert c.config, recNode[0].kind == nkSym let discriminator = recNode[0] - internalAssert c.config, discriminator.kind == nkSym - var selectedBranch = -1 - - for i in 1.. MaxSetElements or - lengthOrd(c.config, recNode[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, recNode, 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 == tyVar: - 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, recNode, selectedBranch, false) - branchValsDiff = ctorBranchVals - recBranchVals - if branchValsDiff.len != 0: - valuesInConflictError(branchValsDiff) + var discriminatorVal = semConstrField(c, flags + {efPreferStatic}, discriminator.sym, initExpr) + let defaultValue = discriminator.sym.ast + var selectedBranches: seq[int] + + if discriminatorVal == nil: + if defaultValue == 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: + selectedBranches = @[recNode.pickCaseBranch newIntLit(c.graph, initExpr.info, 0)] 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.. BiggestInt(MaxSetElements): + localError(c.config, discriminatorVal.info, "branch initialization with a runtime " & + "discriminator only supports ordinal types with 2^16 elements or less.") + var possibleValues = c.getIntSetOfType(discriminatorVal.typ) + let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal) + if ctorCase != nil and discriminatorVal.sym.kind in {skLet, skParam} and discriminatorVal.sym.typ.kind != tyVar: + possibleValues = possibleValues * branchVals(c, ctorCase, ctorIdx) + selectedBranches = recNode.pickCaseBranches(c, possibleValues) + + assert selectedBranches.len != 0 #XXX: Proper error, does this even occur? + if selectedBranches.len == 1: + let branch = recNode[selectedBranches[0]] + #error for fields which require initialization and don't have a default + checkForMissingFields(c, branch[^1], initExpr, considerDefaults = true) + let (subSt, subDf) = semConstructFields(c, branch[^1], initExpr, flags) + result.status.mergeInitStatus subSt + result.defaults.add subDf + else: + for i in selectedBranches: + assert i != 0 + let branch = recNode[i] + if branch != nil: + checkForNoExplicitlyDefinedFields(c, branch[^1], initExpr) + #error for fields which require initialization no matter wether they have a default or not + checkForMissingFields(c, branch[^1], initExpr, considerDefaults = false) + discard semConstructFields(c, branch[^1], initExpr, flags) + #TODO: error/warn for fields which don't require initialization but have a default + #if subDf.len > 0: warn "Will not initalize branch fields to their default value" + result.status = initNone of nkSym: let field = recNode.sym let e = semConstrField(c, flags, field, initExpr) - result = if e != nil: initFull else: initNone - + if e != nil: + result.status = initFull + elif field.ast != nil: + result.status = initUnknown + result.defaults.add newTree(nkExprColonExpr, recNode, field.ast) + else: + result.status = initNone else: internalAssert c.config, false proc semConstructType(c: PContext, initExpr: PNode, - t: PType, flags: TExprFlags): InitStatus = + t: PType, flags: TExprFlags): tuple[status: InitStatus, defaults: seq[PNode]] = var t = t - result = initUnknown while true: - let status = semConstructFields(c, t.n, initExpr, flags) - mergeInitStatus(result, status) + let (status, defaults) = semConstructFields(c, t.n, initExpr, flags) + result.status.mergeInitStatus status + result.defaults.add defaults if status in {initPartial, initNone, initUnknown}: - checkForMissingFields c, t.n, initExpr + checkForMissingFields c, t.n, initExpr, true let base = t[0] if base == nil: break t = skipTypes(base, skipPtrs) @@ -333,7 +261,9 @@ proc semConstructType(c: PContext, initExpr: PNode, proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = var t = semTypeNode(c, n[0], nil) result = newNodeIT(nkObjConstr, n.info, t) - for child in n: result.add child + result.add newNodeIT(nkType, n.info, t) #This will contain the default values to be added in transf + for i in 1..= 4: a = newNodeI(nkRecList, n.info) - else: a = newNodeI(nkEmpty, n.info) - if n[^1].kind != nkEmpty: - localError(c.config, n[^1].info, errInitHereNotAllowed) var typ: PType if n[^2].kind == nkEmpty: localError(c.config, n.info, errTypeExpected) @@ -754,6 +749,8 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, else: typ = semTypeNode(c, n[^2], nil) propagateToOwner(rectype, typ) + var a: PNode = if father.kind != nkRecList and n.len > 3: newNodeI(nkRecList, n.info) + else: newNodeI(nkEmpty, n.info) var fieldOwner = if c.inGenericContext > 0: c.getCurrOwner else: rectype.sym for i in 0.. 0 if we are in inlining context (copy vars) - nestedProcs: int # > 0 if we are in a nested proc + genResult: bool # XXX contSyms, breakSyms: seq[PSym] # to transform 'continue' and 'break' deferDetected, tooEarly: bool graph: ModuleGraph @@ -383,7 +384,7 @@ proc transformYield(c: PTransf, n: PNode): PNode = let rhs = transform(c, e) result.add(asgnTo(lhs, rhs)) - inc(c.transCon.yieldStmts) + inc c.transCon.yieldStmts if c.transCon.yieldStmts <= 1: # common case result.add(c.transCon.forLoopBody) @@ -658,7 +659,7 @@ proc transformFor(c: PTransf, n: PNode): PNode = let body = transformBody(c.graph, iter, true) pushInfoContext(c.graph.config, n.info) - inc(c.inlining) + inc c.inlining stmtList.add(transform(c, body)) #findWrongOwners(c, stmtList.pnode) dec(c.inlining) @@ -743,13 +744,13 @@ proc transformCall(c: PTransf, n: PNode): PNode = var j = 1 while j < n.len: var a = transform(c, n[j]) - inc(j) + inc j if isConstExpr(a): while (j < n.len): let b = transform(c, n[j]) if not isConstExpr(b): break a = evalOp(op.magic, n, a, b, nil, c.graph) - inc(j) + inc j result.add(a) if result.len == 2: result = result[1] elif magic == mAddr: @@ -828,13 +829,13 @@ proc commonOptimizations*(g: ModuleGraph; c: PSym, n: PNode): PNode = var j = 0 while j < args.len: var a = args[j] - inc(j) + inc j if isConstExpr(a): while j < args.len: let b = args[j] if not isConstExpr(b): break a = evalOp(op.magic, result, a, b, nil, g) - inc(j) + inc j result.add(a) if result.len == 2: result = result[1] else: @@ -882,6 +883,47 @@ proc hoistParamsUsedInDefault(c: PTransf, call, letSection, defExpr: PNode): PNo let hoisted = hoistParamsUsedInDefault(c, call, letSection, defExpr[i]) if hoisted != nil: defExpr[i] = hoisted +import nimsets +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 + +proc pickCaseBranch(caseExpr, matched: PNode): int = + let endsWithElse = caseExpr[^1].kind == nkElse + for i in 1.. resultPos and c.transCon.owner.ast[resultPos] != nil result = transform(c, n) popTransCon(c) incl(result.flags, nfTransf) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 987fc7864aae6..08cbee714144c 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1639,7 +1639,7 @@ proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = else: if s.kind == skForVar and c.mode == emRepl: c.setSlot(s) if s.position > 0 or (s.position == 0 and - s.kind in {skParam, skResult}): + s.kind in {skParam, skResult, skTemp}): if dest < 0: dest = s.position + ord(s.kind == skParam) internalAssert(c.config, c.prc.slots[dest].kind < slotSomeTemp) diff --git a/tests/constructors/tinvalid_construction.nim b/tests/constructors/tinvalid_construction.nim index b3e56eec6087d..7050298b80a92 100644 --- a/tests/constructors/tinvalid_construction.nim +++ b/tests/constructors/tinvalid_construction.nim @@ -65,7 +65,7 @@ accept TObj() accept TObj(choice: A) reject TObj(choice: A, bc: 10) # bc is in the wrong branch accept TObj(choice: B, bc: 20) -reject TObj(a: 10) # branch selected without providing discriminator +accept TObj(a: 10) # branch selected with the default value "low(T)" of the discriminator reject TObj(choice: x, a: 10) # the discrimantor must be a compile-time value when a branch is selected accept TObj(choice: x) # it's OK to use run-time value when a branch is not selected accept TObj(choice: F, f: "") # match an else clause diff --git a/tests/objvariant/trt_discrim.nim b/tests/objvariant/trt_discrim.nim index 95b2a9f768858..8d4c1defb2f6b 100644 --- a/tests/objvariant/trt_discrim.nim +++ b/tests/objvariant/trt_discrim.nim @@ -133,6 +133,12 @@ reject: var varkind = k4 reject: # not immutable. + case varkind + of k1, k2, k3: discard KindObj(kind: varkind, i32: 1) + of k4: discard KindObj(kind: varkind, f32: 2.0) + else: discard KindObj(kind: varkind, str: "3") + +reject: # complete bogus case varkind of k1, k2, k3: discard KindObj(varkind: kind, i32: 1) of k4: discard KindObj(varkind: kind, f32: 2.0) diff --git a/tests/objvariant/trt_discrim_err0.nim b/tests/objvariant/trt_discrim_err0.nim index 02b551cbc4218..54e98f5b664b9 100644 --- a/tests/objvariant/trt_discrim_err0.nim +++ b/tests/objvariant/trt_discrim_err0.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "possible values {k1, k3, k4} are in conflict with discriminator values for selected object branch 3" + errormsg: "runtime discriminator could select multiple branches, so you can't initialize these fields: str" line: 17 """ diff --git a/tests/objvariant/trt_discrim_err1.nim b/tests/objvariant/trt_discrim_err1.nim index de29420a2f9ec..82c8b5f9ac493 100644 --- a/tests/objvariant/trt_discrim_err1.nim +++ b/tests/objvariant/trt_discrim_err1.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "branch initialization with a runtime discriminator is not supported inside of an `elif` branch." + errormsg: "runtime discriminator could select multiple branches, so you can't initialize these fields: green" line: 16 """ type diff --git a/tests/objvariant/trt_discrim_err3.nim b/tests/objvariant/trt_discrim_err3.nim index e739c3d508170..055740c46552b 100644 --- a/tests/objvariant/trt_discrim_err3.nim +++ b/tests/objvariant/trt_discrim_err3.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "runtime discriminator must be immutable if branch fields are initialized, a 'let' binding is required." + errormsg: "runtime discriminator could select multiple branches, so you can't initialize these fields: i32" line: 16 """