summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2021-09-03 21:52:24 +0200
committerGitHub <noreply@github.com>2021-09-03 21:52:24 +0200
commitcddf8ec6f684e5636a114e0e286bc6609e01f228 (patch)
treedda219b3560479c8efde6c6e90b425c1d56e51d4
parentc2b20516d33520b1d339b447ece32ade8625fefc (diff)
downloadNim-cddf8ec6f684e5636a114e0e286bc6609e01f228.tar.gz
implements https://github.com/nim-lang/RFCs/issues/407 (#18793)
-rw-r--r--compiler/semdata.nim1
-rw-r--r--compiler/semexprs.nim16
-rw-r--r--compiler/semobjconstr.nim31
-rw-r--r--compiler/sempass2.nim2
-rw-r--r--compiler/semstmts.nim14
-rw-r--r--compiler/sigmatch.nim14
-rw-r--r--compiler/wordrecg.nim1
-rw-r--r--doc/manual.rst35
-rw-r--r--tests/objvariant/treassign.nim9
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