diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2021-09-03 21:52:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-03 21:52:24 +0200 |
commit | cddf8ec6f684e5636a114e0e286bc6609e01f228 (patch) | |
tree | dda219b3560479c8efde6c6e90b425c1d56e51d4 | |
parent | c2b20516d33520b1d339b447ece32ade8625fefc (diff) | |
download | Nim-cddf8ec6f684e5636a114e0e286bc6609e01f228.tar.gz |
implements https://github.com/nim-lang/RFCs/issues/407 (#18793)
-rw-r--r-- | compiler/semdata.nim | 1 | ||||
-rw-r--r-- | compiler/semexprs.nim | 16 | ||||
-rw-r--r-- | compiler/semobjconstr.nim | 31 | ||||
-rw-r--r-- | compiler/sempass2.nim | 2 | ||||
-rw-r--r-- | compiler/semstmts.nim | 14 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 14 | ||||
-rw-r--r-- | compiler/wordrecg.nim | 1 | ||||
-rw-r--r-- | doc/manual.rst | 35 | ||||
-rw-r--r-- | tests/objvariant/treassign.nim | 9 |
9 files changed, 103 insertions, 20 deletions
diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 31affb24f..422d1223a 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -160,6 +160,7 @@ type importModuleMap*: Table[int, int] # (module.id, module.id) lastTLineInfo*: TLineInfo sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index + inUncheckedAssignSection*: int template config*(c: PContext): ConfigRef = c.graph.config diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index f2fe54fad..3edeae324 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -691,8 +691,12 @@ proc newHiddenAddrTaken(c: PContext, n: PNode): PNode = else: result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ)) result.add n - if isAssignable(c, n) notin {arLValue, arLocalLValue}: - localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n)) + let aa = isAssignable(c, n) + if aa notin {arLValue, arLocalLValue}: + if aa == arDiscriminant and c.inUncheckedAssignSection > 0: + discard "allow access within a cast(unsafeAssign) section" + else: + localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n)) proc analyseIfAddressTaken(c: PContext, n: PNode): PNode = result = n @@ -738,9 +742,13 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = if i < t.len and t[i] != nil and skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}: let it = n[i] - if isAssignable(c, it) notin {arLValue, arLocalLValue}: + let aa = isAssignable(c, it) + if aa notin {arLValue, arLocalLValue}: if it.kind != nkHiddenAddr: - localError(c.config, it.info, errVarForOutParamNeededX % $it) + if aa == arDiscriminant and c.inUncheckedAssignSection > 0: + discard "allow access within a cast(unsafeAssign) section" + else: + localError(c.config, it.info, errVarForOutParamNeededX % $it) # bug #5113: disallow newSeq(result) where result is a 'var T': if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}: var arg = n[1] #.skipAddr diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 1c97d32c8..1563e5c16 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -200,20 +200,22 @@ proc semConstructFields(c: PContext, n: PNode, 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]) + 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, 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 " & @@ -251,9 +253,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 " & diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index cae2f926c..2df6327b8 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -961,6 +961,8 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) = else: bc.exc = newNodeI(nkArgList, pragma.info) bc.exc.add n + of wUncheckedAssign: + discard "handled in sempass1" else: localError(tracked.config, pragma.info, "invalid pragma block: " & $pragma) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index df0e2778b..2c5f3ba54 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2223,7 +2223,21 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode = checkSonsLen(n, 2, c.config) let pragmaList = n[0] pragma(c, nil, pragmaList, exprPragmas, isStatement = true) + + var inUncheckedAssignSection = 0 + for p in pragmaList: + if whichPragma(p) == wCast: + case whichPragma(p[1]) + of wGcSafe, wNoSideEffect, wTags, wRaises: + discard "handled in sempass2" + of wUncheckedAssign: + inUncheckedAssignSection = 1 + else: + localError(c.config, p.info, "invalid pragma block: " & $p) + + inc c.inUncheckedAssignSection, inUncheckedAssignSection n[1] = semExpr(c, n[1]) + dec c.inUncheckedAssignSection, inUncheckedAssignSection result = n result.typ = n[1].typ for i in 0..<pragmaList.len: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 9cf18277f..6bc6eefe0 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1879,6 +1879,16 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate, result.add c.graph.emptyNode result.add arg +proc isLValue(c: PContext; n: PNode): bool {.inline.} = + let aa = isAssignable(nil, n) + case aa + of arLValue, arLocalLValue, arStrange: + result = true + of arDiscriminant: + result = c.inUncheckedAssignSection > 0 + else: + result = false + proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, arg: PNode): PNode = result = nil @@ -1895,7 +1905,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, let constraint = c.converters[i].typ.n[1].sym.constraint if not constraint.isNil and not matchNodeKinds(constraint, arg): continue - if src.kind in {tyVar, tyLent} and not arg.isLValue: + if src.kind in {tyVar, tyLent} and not isLValue(c, arg): continue let destIsGeneric = containsGenericType(dest) @@ -2338,7 +2348,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int if argConverter.typ.kind notin {tyVar}: m.firstMismatch.kind = kVarNeeded noMatch() - elif not n.isLValue: + elif not isLValue(c, n): m.firstMismatch.kind = kVarNeeded noMatch() diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 6ff68be75..4ba807518 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -36,6 +36,7 @@ type wMemTracker = "memtracker", wObjChecks = "objchecks", wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine", wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf", + wUncheckedAssign = "uncheckedAssign", wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor", wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp", diff --git a/doc/manual.rst b/doc/manual.rst index 997253f16..714f98bdc 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -1854,6 +1854,41 @@ A small example: let unknownKindBounded = range[nkAdd..nkSub](unknownKind) z = Node(kind: unknownKindBounded, leftOp: Node(), rightOp: Node()) + +cast uncheckedAssign +-------------------- + +Some restrictions for case objects can be disabled via a `{.cast(unsafeAssign).}` section: + +.. code-block:: nim + :test: "nim c $1" + + type + TokenKind* = enum + strLit, intLit + Token = object + case kind*: TokenKind + of strLit: + s*: string + of intLit: + i*: int64 + + proc passToVar(x: var TokenKind) = discard + + var t = Token(kind: strLit, s: "abc") + + {.cast(uncheckedAssign).}: + # inside the 'cast' section it is allowed to pass 't.kind' to a 'var T' parameter: + passToVar(t.kind) + + # inside the 'cast' section it is allowed to set field 's' even though the + # constructed 'kind' field has an unknown value: + t = Token(kind: t.kind, s: "abc") + + # inside the 'cast' section it is allowed to assign to the 't.kind' field directly: + t.kind = intLit + + Set type -------- diff --git a/tests/objvariant/treassign.nim b/tests/objvariant/treassign.nim index 2938b30a3..9549cb29c 100644 --- a/tests/objvariant/treassign.nim +++ b/tests/objvariant/treassign.nim @@ -25,3 +25,12 @@ t.curr = TokenObject(kind: Token.bar, bar: BasicNumber(value: 12.34)) t.curr = TokenObject(kind: Token.foo, foo: "foo") echo "SUCCESS" + +proc passToVar(x: var Token) = discard + +{.cast(uncheckedAssign).}: + passToVar(t.curr.kind) + + t.curr = TokenObject(kind: t.curr.kind, foo: "abc") + + t.curr.kind = Token.foo |