summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2022-10-17 23:48:51 +0200
committerGitHub <noreply@github.com>2022-10-17 23:48:51 +0200
commit81087c949f620dc80697364da414872791ffe23c (patch)
tree870c578d221484861f485371ebde2fca18d6ca08
parentc0824b9b80f50807fc41b621760c4b685d1d7497 (diff)
downloadNim-81087c949f620dc80697364da414872791ffe23c.tar.gz
fixes #20572 (#20585)
* fixes #20572

* added a test case
-rw-r--r--compiler/ast.nim3
-rw-r--r--compiler/ccgcalls.nim2
-rw-r--r--compiler/ccgexprs.nim2
-rw-r--r--compiler/cgen.nim4
-rw-r--r--compiler/closureiters.nim4
-rw-r--r--compiler/dfa.nim2
-rw-r--r--compiler/docgen.nim4
-rw-r--r--compiler/hlo.nim2
-rw-r--r--compiler/injectdestructors.nim84
-rw-r--r--compiler/jsgen.nim2
-rw-r--r--compiler/lambdalifting.nim2
-rw-r--r--compiler/nilcheck.nim2
-rw-r--r--compiler/optimizer.nim2
-rw-r--r--compiler/patterns.nim2
-rw-r--r--compiler/renderer.nim7
-rw-r--r--compiler/semgnrc.nim2
-rw-r--r--compiler/semmagic.nim8
-rw-r--r--compiler/semparallel.nim4
-rw-r--r--compiler/sempass2.nim2
-rw-r--r--compiler/semtempl.nim2
-rw-r--r--compiler/suggest.nim2
-rw-r--r--compiler/varpartitions.nim8
-rw-r--r--compiler/vmgen.nim2
-rw-r--r--lib/core/macros.nim3
-rw-r--r--tests/arc/texplicit_sink.nim29
25 files changed, 116 insertions, 70 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index c4c183932..228f7381c 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -211,8 +211,7 @@ type
     nkDistinctTy,         # distinct type
     nkProcTy,             # proc type
     nkIteratorTy,         # iterator type
-    nkSharedTy,           # 'shared T'
-                          # we use 'nkPostFix' for the 'not nil' addition
+    nkSinkAsgn,           # '=sink(x, y)'
     nkEnumTy,             # enum body
     nkEnumFieldDef,       # `ident = expr` in an enumeration
     nkArgList,            # argument list
diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim
index 81e74d089..c9ab1ddcb 100644
--- a/compiler/ccgcalls.nim
+++ b/compiler/ccgcalls.nim
@@ -335,7 +335,7 @@ proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
   of nkLiterals, nkIdent, nkFormalParams: discard
   of nkSym:
     if mutate: result.add n
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     getPotentialWrites(n[0], true, result)
     getPotentialWrites(n[1], mutate, result)
   of nkAddr, nkHiddenAddr:
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index f71a4ddbd..a34e0535c 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -3086,7 +3086,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
     cow(p, n[1])
     if nfPreventCg notin n.flags:
       genAsgn(p, n, fastAsgn=false)
-  of nkFastAsgn:
+  of nkFastAsgn, nkSinkAsgn:
     cow(p, n[1])
     if nfPreventCg notin n.flags:
       # transf is overly aggressive with 'nkFastAsgn', so we work around here.
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index e2b7c67ad..5fc42bfec 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -926,7 +926,7 @@ proc easyResultAsgn(n: PNode): PNode =
     var i = 0
     while i < n.len and n[i].kind in harmless: inc i
     if i < n.len: result = easyResultAsgn(n[i])
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     if n[0].kind == nkSym and n[0].sym.kind == skResult and not containsResult(n[1]):
       incl n.flags, nfPreventCg
       return n[1]
@@ -968,7 +968,7 @@ proc allPathsAsgnResult(n: PNode): InitResultEnum =
     for it in n:
       result = allPathsAsgnResult(it)
       if result != Unknown: return result
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     if n[0].kind == nkSym and n[0].sym.kind == skResult:
       if not containsResult(n[1]): result = InitSkippable
       else: result = InitRequired
diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim
index 613fbe582..79038a4f5 100644
--- a/compiler/closureiters.nim
+++ b/compiler/closureiters.nim
@@ -718,7 +718,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
       n[^1] = ex
       result.add(n)
 
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     var ns = false
     for i in 0..<n.len:
       n[i] = ctx.lowerStmtListExprs(n[i], ns)
@@ -868,7 +868,7 @@ proc transformReturnsInTry(ctx: var Ctx, n: PNode): PNode =
     if n[0].kind != nkEmpty:
       let asgnTmpResult = newNodeI(nkAsgn, n.info)
       asgnTmpResult.add(ctx.newTmpResultAccess())
-      let x = if n[0].kind in {nkAsgn, nkFastAsgn}: n[0][1] else: n[0]
+      let x = if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}: n[0][1] else: n[0]
       asgnTmpResult.add(x)
       result.add(asgnTmpResult)
 
diff --git a/compiler/dfa.nim b/compiler/dfa.nim
index 001fcdbe7..61a921ba0 100644
--- a/compiler/dfa.nim
+++ b/compiler/dfa.nim
@@ -433,7 +433,7 @@ proc gen(c: var Con; n: PNode) =
     else:
       genCall(c, n)
   of nkCharLit..nkNilLit: discard
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     gen(c, n[1])
 
     if n[0].kind in PathKinds0:
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index 9271d4975..e2c04430c 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -400,7 +400,7 @@ proc genRecCommentAux(d: PDoc, n: PNode): PRstNode =
   result = genComment(d, n)
   if result == nil:
     if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef,
-                  nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn, nkHiddenStdConv}:
+                  nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn, nkSinkAsgn, nkHiddenStdConv}:
       # notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
       for i in 0..<n.len:
         result = genRecCommentAux(d, n[i])
@@ -1551,7 +1551,7 @@ proc genSection(d: PDoc, kind: TSymKind, groupedToc = false) =
   else:
     # Just have the link
     d.toc[kind] =  getConfigVar(d.conf, "doc.section.toc_item") % sectionValues
-    
+
 proc relLink(outDir: AbsoluteDir, destFile: AbsoluteFile, linkto: RelativeFile): string =
   $relativeTo(outDir / linkto, destFile.splitFile().dir, '/')
 
diff --git a/compiler/hlo.nim b/compiler/hlo.nim
index e3670312e..1aab9d5fe 100644
--- a/compiler/hlo.nim
+++ b/compiler/hlo.nim
@@ -71,7 +71,7 @@ proc hlo(c: PContext, n: PNode): PNode =
     # already processed (special cases in semstmts.nim)
     result = n
   else:
-    if n.kind in {nkFastAsgn, nkAsgn, nkIdentDefs, nkVarTuple} and
+    if n.kind in {nkFastAsgn, nkAsgn, nkSinkAsgn, nkIdentDefs, nkVarTuple} and
         n[0].kind == nkSym and
         {sfGlobal, sfPure} * n[0].sym.flags == {sfGlobal, sfPure}:
       # do not optimize 'var g {.global} = re(...)' again!
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index b41fc4ab5..ad43e23fc 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -78,7 +78,12 @@ proc nestedScope(parent: var Scope; body: PNode): Scope =
   Scope(vars: @[], locals: @[], wasMoved: @[], final: @[], body: body, needsTry: false, parent: addr(parent))
 
 proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSingleUsedTemp}): PNode
-proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope; isDecl = false): PNode
+
+type
+  MoveOrCopyFlag = enum
+    IsDecl, IsExplicitSink
+
+proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope; flags: set[MoveOrCopyFlag] = {}): PNode
 
 when false:
   var
@@ -243,8 +248,8 @@ proc canBeMoved(c: Con; t: PType): bool {.inline.} =
 proc isNoInit(dest: PNode): bool {.inline.} =
   result = dest.kind == nkSym and sfNoInit in dest.sym.flags
 
-proc genSink(c: var Con; dest, ri: PNode, isDecl = false): PNode =
-  if (c.inLoopCond == 0 and (isUnpackedTuple(dest) or isDecl or
+proc genSink(c: var Con; dest, ri: PNode; flags: set[MoveOrCopyFlag] = {}): PNode =
+  if (c.inLoopCond == 0 and (isUnpackedTuple(dest) or IsDecl in flags or
       (isAnalysableFieldAccess(dest, c.owner) and isFirstWrite(dest, c)))) or
       isNoInit(dest):
     # optimize sink call into a bitwise memcopy
@@ -296,17 +301,21 @@ proc genMarkCyclic(c: var Con; result, dest: PNode) =
         xenv.typ = getSysType(c.graph, dest.info, tyPointer)
         result.add callCodegenProc(c.graph, "nimMarkCyclic", dest.info, xenv)
 
-proc genCopyNoCheck(c: var Con; dest, ri: PNode): PNode =
+proc genCopyNoCheck(c: var Con; dest, ri: PNode; a: TTypeAttachedOp): PNode =
   let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
-  result = c.genOp(t, attachedAsgn, dest, ri)
+  result = c.genOp(t, a, dest, ri)
   assert ri.typ != nil
 
-proc genCopy(c: var Con; dest, ri: PNode): PNode =
+proc genCopy(c: var Con; dest, ri: PNode; flags: set[MoveOrCopyFlag]): PNode =
   let t = dest.typ
   if tfHasOwned in t.flags and ri.kind != nkNilLit:
     # try to improve the error message here:
-    c.checkForErrorPragma(t, ri, "=copy")
-  result = c.genCopyNoCheck(dest, ri)
+    if IsExplicitSink in flags:
+      c.checkForErrorPragma(t, ri, "=sink")
+    else:
+      c.checkForErrorPragma(t, ri, "=copy")
+  let a = if IsExplicitSink in flags: attachedSink else: attachedAsgn
+  result = c.genCopyNoCheck(dest, ri, a)
   assert ri.typ != nil
 
 proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode =
@@ -391,7 +400,7 @@ proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
   let tmp = c.getTemp(s, n.typ, n.info)
   if hasDestructor(c, n.typ):
     result.add c.genWasMoved(tmp)
-    var m = c.genCopy(tmp, n)
+    var m = c.genCopy(tmp, n, {})
     m.add p(n, c, s, normal)
     c.finishCopy(m, n, isFromSink = true)
     result.add m
@@ -436,7 +445,7 @@ proc ensureDestruction(arg, orig: PNode; c: var Con; s: var Scope): PNode =
     # This was already done in the sink parameter handling logic.
     result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
     let tmp = c.getTemp(s, arg.typ, arg.info)
-    result.add c.genSink(tmp, arg, isDecl = true)
+    result.add c.genSink(tmp, arg, {IsDecl})
     result.add tmp
     s.final.add c.genDestroy(tmp)
   else:
@@ -518,7 +527,7 @@ template processScopeExpr(c: var Con; s: var Scope; ret: PNode, processCall: unt
   tmp.sym.flags = tmpFlags
   let cpy = if hasDestructor(c, ret.typ):
               s.parent[].final.add c.genDestroy(tmp)
-              moveOrCopy(tmp, ret, c, s, isDecl = true)
+              moveOrCopy(tmp, ret, c, s, {IsDecl})
             else:
               newTree(nkFastAsgn, tmp, p(ret, c, s, normal))
 
@@ -654,7 +663,7 @@ proc pRaiseStmt(n: PNode, c: var Con; s: var Scope): PNode =
       result.add call
     else:
       let tmp = c.getTemp(s, n[0].typ, n.info)
-      var m = c.genCopyNoCheck(tmp, n[0])
+      var m = c.genCopyNoCheck(tmp, n[0], attachedAsgn)
       m.add p(n[0], c, s, normal)
       c.finishCopy(m, n[0], isFromSink = false)
       result = newTree(nkStmtList, c.genWasMoved(tmp), m)
@@ -818,9 +827,9 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
               s.locals.add v.sym
               pVarTopLevel(v, c, s, result)
             if ri.kind != nkEmpty:
-              result.add moveOrCopy(v, ri, c, s, isDecl = v.kind == nkSym)
+              result.add moveOrCopy(v, ri, c, s, if v.kind == nkSym: {IsDecl} else: {})
             elif ri.kind == nkEmpty and c.inLoop > 0:
-              result.add moveOrCopy(v, genDefaultCall(v.typ, c, v.info), c, s, isDecl = v.kind == nkSym)
+              result.add moveOrCopy(v, genDefaultCall(v.typ, c, v.info), c, s, if v.kind == nkSym: {IsDecl} else: {})
         else: # keep the var but transform 'ri':
           var v = copyNode(n)
           var itCopy = copyNode(it)
@@ -833,12 +842,13 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
           itCopy.add p(it[^1], c, s, normal, tmpFlags = flags)
           v.add itCopy
           result.add v
-    of nkAsgn, nkFastAsgn:
+    of nkAsgn, nkFastAsgn, nkSinkAsgn:
       if hasDestructor(c, n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda}:
         if n[0].kind in {nkDotExpr, nkCheckedFieldExpr}:
           cycleCheck(n, c)
-        assert n[1].kind notin {nkAsgn, nkFastAsgn}
-        result = moveOrCopy(p(n[0], c, s, mode), n[1], c, s)
+        assert n[1].kind notin {nkAsgn, nkFastAsgn, nkSinkAsgn}
+        let flags = if n.kind == nkSinkAsgn: {IsExplicitSink} else: {}
+        result = moveOrCopy(p(n[0], c, s, mode), n[1], c, s, flags)
       elif isDiscriminantField(n[0]):
         result = c.genDiscriminantAsgn(s, n)
       else:
@@ -963,7 +973,7 @@ proc sameLocation*(a, b: PNode): bool =
     of nkHiddenStdConv, nkHiddenSubConv: sameLocation(a[1], b)
     else: false
 
-proc genFieldAccessSideEffects(c: var Con; dest, ri: PNode, isDecl: bool): PNode =
+proc genFieldAccessSideEffects(c: var Con; dest, ri: PNode; flags: set[MoveOrCopyFlag] = {}): PNode =
   # with side effects
   var temp = newSym(skLet, getIdent(c.graph.cache, "bracketTmp"), nextSymId c.idgen, c.owner, ri[1].info)
   temp.typ = ri[1].typ
@@ -980,17 +990,17 @@ proc genFieldAccessSideEffects(c: var Con; dest, ri: PNode, isDecl: bool): PNode
   newAccess.add ri[0]
   newAccess.add tempAsNode
 
-  var snk = c.genSink(dest, newAccess, isDecl)
+  var snk = c.genSink(dest, newAccess, flags)
   result = newTree(nkStmtList, v, snk, c.genWasMoved(newAccess))
 
-proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNode =
+proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, flags: set[MoveOrCopyFlag] = {}): PNode =
   if sameLocation(dest, ri):
     # rule (self-assignment-removal):
     result = newNodeI(nkEmpty, dest.info)
   elif isCursor(dest):
     case ri.kind:
     of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt:
-      template process(child, s): untyped = moveOrCopy(dest, child, c, s, isDecl)
+      template process(child, s): untyped = moveOrCopy(dest, child, c, s, flags)
       # We know the result will be a stmt so we use that fact to optimize
       handleNestedTempl(ri, process, willProduceStmt = true)
     else:
@@ -998,53 +1008,53 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNod
   else:
     case ri.kind
     of nkCallKinds:
-      result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
+      result = c.genSink(dest, p(ri, c, s, consumed), flags)
     of nkBracketExpr:
       if isUnpackedTuple(ri[0]):
         # unpacking of tuple: take over the elements
-        result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
+        result = c.genSink(dest, p(ri, c, s, consumed), flags)
       elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c, s):
         if aliases(dest, ri) == no:
           # Rule 3: `=sink`(x, z); wasMoved(z)
           if isAtom(ri[1]):
-            var snk = c.genSink(dest, ri, isDecl)
+            var snk = c.genSink(dest, ri, flags)
             result = newTree(nkStmtList, snk, c.genWasMoved(ri))
           else:
-            result = genFieldAccessSideEffects(c, dest, ri, isDecl)
+            result = genFieldAccessSideEffects(c, dest, ri, flags)
         else:
-          result = c.genSink(dest, destructiveMoveVar(ri, c, s), isDecl)
+          result = c.genSink(dest, destructiveMoveVar(ri, c, s), flags)
       else:
-        result = c.genCopy(dest, ri)
+        result = c.genCopy(dest, ri, flags)
         result.add p(ri, c, s, consumed)
         c.finishCopy(result, dest, isFromSink = false)
     of nkBracket:
       # array constructor
       if ri.len > 0 and isDangerousSeq(ri.typ):
-        result = c.genCopy(dest, ri)
+        result = c.genCopy(dest, ri, flags)
         result.add p(ri, c, s, consumed)
         c.finishCopy(result, dest, isFromSink = false)
       else:
-        result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
+        result = c.genSink(dest, p(ri, c, s, consumed), flags)
     of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
-      result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
+      result = c.genSink(dest, p(ri, c, s, consumed), flags)
     of nkSym:
       if isSinkParam(ri.sym) and isLastRead(ri, c, s):
         # Rule 3: `=sink`(x, z); wasMoved(z)
-        let snk = c.genSink(dest, ri, isDecl)
+        let snk = c.genSink(dest, ri, flags)
         result = newTree(nkStmtList, snk, c.genWasMoved(ri))
       elif ri.sym.kind != skParam and ri.sym.owner == c.owner and
           isLastRead(ri, c, s) and canBeMoved(c, dest.typ) and not isCursor(ri):
         # Rule 3: `=sink`(x, z); wasMoved(z)
-        let snk = c.genSink(dest, ri, isDecl)
+        let snk = c.genSink(dest, ri, flags)
         result = newTree(nkStmtList, snk, c.genWasMoved(ri))
       else:
-        result = c.genCopy(dest, ri)
+        result = c.genCopy(dest, ri, flags)
         result.add p(ri, c, s, consumed)
         c.finishCopy(result, dest, isFromSink = false)
     of nkHiddenSubConv, nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv, nkCast:
-      result = c.genSink(dest, p(ri, c, s, sinkArg), isDecl)
+      result = c.genSink(dest, p(ri, c, s, sinkArg), flags)
     of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt:
-      template process(child, s): untyped = moveOrCopy(dest, child, c, s, isDecl)
+      template process(child, s): untyped = moveOrCopy(dest, child, c, s, flags)
       # We know the result will be a stmt so we use that fact to optimize
       handleNestedTempl(ri, process, willProduceStmt = true)
     of nkRaiseStmt:
@@ -1053,10 +1063,10 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNod
       if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c, s) and
           canBeMoved(c, dest.typ):
         # Rule 3: `=sink`(x, z); wasMoved(z)
-        let snk = c.genSink(dest, ri, isDecl)
+        let snk = c.genSink(dest, ri, flags)
         result = newTree(nkStmtList, snk, c.genWasMoved(ri))
       else:
-        result = c.genCopy(dest, ri)
+        result = c.genCopy(dest, ri, flags)
         result.add p(ri, c, s, consumed)
         c.finishCopy(result, dest, isFromSink = false)
 
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 267b9ba34..29bfb7df5 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -2711,7 +2711,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkReturnStmt: genReturnStmt(p, n)
   of nkBreakStmt: genBreakStmt(p, n)
   of nkAsgn: genAsgn(p, n)
-  of nkFastAsgn: genFastAsgn(p, n)
+  of nkFastAsgn, nkSinkAsgn: genFastAsgn(p, n)
   of nkDiscardStmt:
     if n[0].kind != nkEmpty:
       genLineDir(p, n)
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index ca9b29a77..a34f4ba94 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -782,7 +782,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: var DetectionPass;
       n[1] = liftCapturedVars(n[1], owner, d, c)
       if n[1].kind == nkClosure: result = n[1]
   of nkReturnStmt:
-    if n[0].kind in {nkAsgn, nkFastAsgn}:
+    if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}:
       # we have a `result = result` expression produced by the closure
       # transform, let's not touch the LHS in order to make the lifting pass
       # correct when `result` is lifted
diff --git a/compiler/nilcheck.nim b/compiler/nilcheck.nim
index 49ceb8942..91d76b2b8 100644
--- a/compiler/nilcheck.nim
+++ b/compiler/nilcheck.nim
@@ -1242,7 +1242,7 @@ proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
     result = check(n.sons[0], ctx, map)
   of nkIfStmt, nkIfExpr:
     result = checkIf(n, ctx, map)
-  of nkAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     result = checkAsgn(n[0], n[1], ctx, map)
   of nkVarSection:
     result.map = map
diff --git a/compiler/optimizer.nim b/compiler/optimizer.nim
index 10b092e11..98939f80d 100644
--- a/compiler/optimizer.nim
+++ b/compiler/optimizer.nim
@@ -154,7 +154,7 @@ proc analyse(c: var Con; b: var BasicBlock; n: PNode) =
       nkTypeOfExpr, nkMixinStmt, nkBindStmt:
     discard "do not follow the construct"
 
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     # reverse order, see remark for `=sink`:
     analyse(c, b, n[1])
     analyse(c, b, n[0])
diff --git a/compiler/patterns.nim b/compiler/patterns.nim
index d4577981f..ff9a9efa3 100644
--- a/compiler/patterns.nim
+++ b/compiler/patterns.nim
@@ -44,7 +44,7 @@ proc canonKind(n: PNode): TNodeKind =
   case result
   of nkCallKinds: result = nkCall
   of nkStrLit..nkTripleStrLit: result = nkStrLit
-  of nkFastAsgn: result = nkAsgn
+  of nkFastAsgn, nkSinkAsgn: result = nkAsgn
   else: discard
 
 proc sameKinds(a, b: PNode): bool {.inline.} =
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index eb410bc5d..f975dc896 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -518,7 +518,7 @@ proc lsub(g: TSrcGen; n: PNode): int =
   of nkOfInherit: result = lsub(g, n[0]) + len("of_")
   of nkProcTy: result = lsons(g, n) + len("proc_")
   of nkIteratorTy: result = lsons(g, n) + len("iterator_")
-  of nkSharedTy: result = lsons(g, n) + len("shared_")
+  of nkSinkAsgn: result = lsons(g, n) + len("`=sink`(, )")
   of nkEnumTy:
     if n.len > 0:
       result = lsub(g, n[0]) + lcomma(g, n, 1) + len("enum_")
@@ -1170,6 +1170,11 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
     put(g, tkSpaces, Space)
     putWithSpace(g, tkEquals, "=")
     gsub(g, n, 1)
+  of nkSinkAsgn:
+    put(g, tkSymbol, "`=sink`")
+    put(g, tkParLe, "(")
+    gcomma(g, n)
+    put(g, tkParRi, ")")
   of nkChckRangeF:
     put(g, tkSymbol, "chckRangeF")
     put(g, tkParLe, "(")
diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim
index d1c4acef6..0827e6845 100644
--- a/compiler/semgnrc.nim
+++ b/compiler/semgnrc.nim
@@ -306,7 +306,7 @@ proc semGenericStmt(c: PContext, n: PNode,
     result.add newIdentNode(getIdent(c.cache, "[]"), n.info)
     for i in 0..<n.len: result.add(n[i])
     result = semGenericStmt(c, result, flags, ctx)
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     checkSonsLen(n, 2, c.config)
     let a = n[0]
     let b = n[1]
diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim
index f815c7059..9e484e214 100644
--- a/compiler/semmagic.nim
+++ b/compiler/semmagic.nim
@@ -78,8 +78,8 @@ proc semArrPut(c: PContext; n: PNode; flags: TExprFlags): PNode =
   result[1] = n.lastSon
   result = semAsgn(c, result, noOverloadedSubscript)
 
-proc semAsgnOpr(c: PContext; n: PNode): PNode =
-  result = newNodeI(nkAsgn, n.info, 2)
+proc semAsgnOpr(c: PContext; n: PNode; k: TNodeKind): PNode =
+  result = newNodeI(k, n.info, 2)
   result[0] = n[1]
   result[1] = n[2]
   result = semAsgn(c, result, noOverloadedAsgn)
@@ -553,7 +553,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
     result = semArrPut(c, n, flags)
   of mAsgn:
     if n[0].sym.name.s == "=":
-      result = semAsgnOpr(c, n)
+      result = semAsgnOpr(c, n, nkAsgn)
+    elif n[0].sym.name.s == "=sink":
+      result = semAsgnOpr(c, n, nkSinkAsgn)
     else:
       result = semShallowCopy(c, n, flags)
   of mIsPartOf: result = semIsPartOf(c, n, flags)
diff --git a/compiler/semparallel.nim b/compiler/semparallel.nim
index f696f6a0a..540a06c40 100644
--- a/compiler/semparallel.nim
+++ b/compiler/semparallel.nim
@@ -323,7 +323,7 @@ proc analyseIf(c: var AnalysisCtx; n: PNode) =
 
 proc analyse(c: var AnalysisCtx; n: PNode) =
   case n.kind
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     let y = n[1].skipConv
     if n[0].isSingleAssignable and y.isLocal:
       let slot = c.getSlot(y.sym)
@@ -442,7 +442,7 @@ proc transformSpawn(g: ModuleGraph; idgen: IdGenerator; owner: PSym; n, barrier:
         else:
           it[^1] = wrapProcForSpawn(g, idgen, owner, m, b.typ, barrier, nil)
     if result.isNil: result = n
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     let b = n[1]
     if getMagic(b) == mSpawn and (let t = b[1][0].typ[0];
         spawnResult(t, true) == srByVar):
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 42c463cd9..8916e2568 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -1053,7 +1053,7 @@ proc track(tracked: PEffects, n: PNode) =
       checkFieldAccess(tracked.guards, n, tracked.config)
   of nkTryStmt: trackTryStmt(tracked, n)
   of nkPragma: trackPragmaStmt(tracked, n)
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     track(tracked, n[1])
     initVar(tracked, n[0], volatileCheck=true)
     invalidateFacts(tracked.guards, n[0])
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index ed2475341..c20f01023 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -511,7 +511,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
     result.add newIdentNode(getIdent(c.c.cache, "{}"), n.info)
     for i in 0..<n.len: result.add(n[i])
     result = semTemplBodySons(c, result)
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     checkSonsLen(n, 2, c.c.config)
     let a = n[0]
     let b = n[1]
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index b5f787296..829335015 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -53,7 +53,7 @@ proc findDocComment(n: PNode): PNode =
     if result != nil: return
     if n.len > 1:
       result = findDocComment(n[1])
-  elif n.kind in {nkAsgn, nkFastAsgn} and n.len == 2:
+  elif n.kind in {nkAsgn, nkFastAsgn, nkSinkAsgn} and n.len == 2:
     result = findDocComment(n[1])
 
 proc extractDocComment(g: ModuleGraph; s: PSym): string =
diff --git a/compiler/varpartitions.nim b/compiler/varpartitions.nim
index 60eed5c78..c83da3277 100644
--- a/compiler/varpartitions.nim
+++ b/compiler/varpartitions.nim
@@ -686,7 +686,7 @@ proc traverse(c: var Partitions; n: PNode) =
         for i in 0..<child.len-2:
           #registerVariable(c, child[i])
           deps(c, child[i], last)
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     traverse(c, n[0])
     inc c.inAsgnSource
     traverse(c, n[1])
@@ -779,7 +779,7 @@ proc traverse(c: var Partitions; n: PNode) =
 
     if n.kind == nkWhileStmt:
       traverse(c, n[0])
-      # variables in while condition has longer alive time than local variables 
+      # variables in while condition has longer alive time than local variables
       # in the while loop body
   else:
     for child in n: traverse(c, child)
@@ -811,7 +811,7 @@ proc computeLiveRanges(c: var Partitions; n: PNode) =
           registerVariable(c, child[i])
           #deps(c, child[i], last)
 
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     computeLiveRanges(c, n[0])
     computeLiveRanges(c, n[1])
     if n[0].kind == nkSym:
@@ -871,7 +871,7 @@ proc computeLiveRanges(c: var Partitions; n: PNode) =
 
     if n.kind == nkWhileStmt:
       computeLiveRanges(c, n[0])
-      # variables in while condition has longer alive time than local variables 
+      # variables in while condition has longer alive time than local variables
       # in the while loop body
   of nkElifBranch, nkElifExpr, nkElse, nkOfBranch:
     inc c.inConditional
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index ec2d068b7..a326428f9 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -2045,7 +2045,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
   of nkNilLit:
     if not n.typ.isEmptyType: genLit(c, getNullValue(n.typ, n.info, c.config), dest)
     else: unused(c, n, dest)
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     unused(c, n, dest)
     genAsgn(c, n[0], n[1], n.kind == nkAsgn)
   of nkDotExpr: genObjAccess(c, n, dest, flags)
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index d09ea2fe2..b26264405 100644
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -79,7 +79,7 @@ type
     nnkDistinctTy,
     nnkProcTy,
     nnkIteratorTy,         # iterator type
-    nnkSharedTy,           # 'shared T'
+    nnkSinkAsgn,
     nnkEnumTy,
     nnkEnumFieldDef,
     nnkArgList, nnkPattern
@@ -127,6 +127,7 @@ type
 
 const
   nnkMutableTy* {.deprecated.} = nnkOutTy
+  nnkSharedTy* {.deprecated.} = nnkSinkAsgn
 
 type
   NimIdent* {.deprecated.} = object of RootObj
diff --git a/tests/arc/texplicit_sink.nim b/tests/arc/texplicit_sink.nim
new file mode 100644
index 000000000..4b021ed62
--- /dev/null
+++ b/tests/arc/texplicit_sink.nim
@@ -0,0 +1,29 @@
+discard """
+  output: '''de'''
+  cmd: '''nim c --mm:arc --expandArc:main $file'''
+  nimout: '''--expandArc: main
+
+var
+  a
+  b_cursor
+try:
+  a = f "abc"
+  b_cursor = "de"
+  `=sink`(a, b_cursor)
+  echo [a]
+finally:
+  `=destroy`(a)
+-- end of expandArc ------------------------'''
+"""
+
+# bug #20572
+
+proc f(s: string): string = s
+
+proc main =
+  var a = f "abc"
+  var b = "de"
+  `=sink`(a, b)
+  echo a
+
+main()