diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index ce47cf2197e01..4a41c12525d33 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -999,9 +999,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     gsub(g, n, 0)
     gcomma(g, n, 1)
   of nkCommand:
-    accentedName(g, n[0])
-    put(g, tkSpaces, Space)
-    gcomma(g, n, 1)
+    if n.len > 0:
+      accentedName(g, n[0])
+      put(g, tkSpaces, Space)
+      gcomma(g, n, 1)
   of nkExprEqExpr, nkAsgn, nkFastAsgn:
     gsub(g, n, 0)
     put(g, tkSpaces, Space)
diff --git a/compiler/vm.nim b/compiler/vm.nim
index 131501380f1a5..3951f76f674a1 100644
--- a/compiler/vm.nim
+++ b/compiler/vm.nim
@@ -19,6 +19,7 @@ import
 
 from semfold import leValueConv, ordinalValToString
 from evaltempl import evalTemplate
+from os import isAbsolute, parentDir, `/`
 
 from modulegraphs import ModuleGraph, PPassContext
 
@@ -1498,7 +1499,21 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
         internalAssert c.config, false
       regs[ra].node.info = n.info
       regs[ra].node.typ = n.typ
-    of opcNSetLineInfo:
+    of opcNSetLineInfoObj:
+      decodeB(rkNode)
+      let n = regs[rb].node
+      assert(n.kind == nkObjConstr)
+      assert(n.len == 4)
+      assert(n[1][0].sym.name.s == "filename")
+      var filename = n[1][1].strVal
+      if not isAbsolute(filename):
+         filename = parentDir(toFullPath(c.config, c.debug[pc])) / filename
+      assert(n[2][0].sym.name.s == "line")
+      let line = n[2][1].intVal.int
+      assert(n[3][0].sym.name.s == "column")
+      let column = n[3][1].intVal.int
+      regs[ra].node.info = newLineInfo(c.config, AbsoluteFile(filename), line, column)
+    of opcNCopyLineInfo:
       decodeB(rkNode)
       regs[ra].node.info = regs[rb].node.info
     of opcEqIdent:
diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim
index a43f8dbba52a8..1a2e5c0d9ea15 100644
--- a/compiler/vmdef.nim
+++ b/compiler/vmdef.nim
@@ -107,7 +107,7 @@ type
     opcNError,
     opcNWarning,
     opcNHint,
-    opcNGetLineInfo, opcNSetLineInfo,
+    opcNGetLineInfo, opcNSetLineInfoObj, opcNCopyLineInfo,
     opcEqIdent,
     opcStrToIdent,
     opcGetImpl,
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index e7993dfb21270..353df363b0336 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -1231,11 +1231,13 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     of "getFile": genUnaryABI(c, n, dest, opcNGetLineInfo, 0)
     of "getLine": genUnaryABI(c, n, dest, opcNGetLineInfo, 1)
     of "getColumn": genUnaryABI(c, n, dest, opcNGetLineInfo, 2)
+    of "lineInfoObj=": genBinaryStmt(c, n, opcNSetLineInfoObj)
     of "copyLineInfo":
       internalAssert c.config, n.len == 3
       unused(c, n, dest)
-      genBinaryStmt(c, n, opcNSetLineInfo)
-    else: internalAssert c.config, false
+      genBinaryStmt(c, n, opcNCopyLineInfo)
+    else:
+      internalAssert c.config, false
   of mNHint:
     unused(c, n, dest)
     genBinaryStmt(c, n, opcNHint)
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index 3a85324bac03a..5300766b9e301 100644
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -489,6 +489,8 @@ proc lineInfoObj*(n: NimNode): LineInfo {.compileTime.} =
 proc lineInfo*(arg: NimNode): string {.compileTime.} =
   $arg.lineInfoObj
 
+proc `lineInfoObj=`*(n: NimNode; lineinfo: LineInfo) {.magic: "NLineInfo".}
+
 proc internalParseExpr(s: string): NimNode {.
   magic: "ParseExprToAst", noSideEffect.}
 
diff --git a/lib/experimental/quote2.nim b/lib/experimental/quote2.nim
new file mode 100644
index 0000000000000..7e91e40d3aaae
--- /dev/null
+++ b/lib/experimental/quote2.nim
@@ -0,0 +1,241 @@
+import macros
+
+# template expectNimNode(arg: NimNode): NimNode = arg
+# macro expectNimNode(arg: typed): NimNode =
+#   error("expressions needs to be of type NimNode", arg)
+
+proc expectNimNode[T](arg: T): NimNode = arg
+
+proc newTreeWithLineinfo*(kind: NimNodeKind; lineinfo: LineInfo; children: varargs[NimNode]): NimNode {.compileTime.} =
+  ## like ``macros.newTree``, just that the first argument is a node to take lineinfo from.
+  # TODO lineinfo cannot be forwarded to new node.  I am forced to drop it here.
+  result = newNimNode(kind, nil)
+  result.lineinfoObj = lineinfo
+  result.add(children)
+
+# TODO restrict unquote to nimnode expressions (error message)
+
+const forwardLineinfo = true
+
+proc newTreeExpr(stmtList, exprNode, unquoteIdent: NimNode): NimNode {.compileTime.} =
+  # stmtList is a buffer to generate statements
+  if exprNode.kind in nnkLiterals:
+    result = newCall(bindSym"newLit", exprNode)
+  elif exprNode.kind == nnkIdent:
+    result = newCall(bindSym"ident", newLit(exprNode.strVal))
+  elif exprNode.kind in nnkCallKinds and exprNode.len == 2 and exprNode[0].eqIdent unquoteIdent:
+    result = newCall(bindSym"expectNimNode", exprNode[1])
+  elif exprNode.kind == nnkSym:
+    error("for quoting the ast needs to be untyped", exprNode)
+  elif exprNode.kind == nnkCommentStmt:
+    result = newCall(bindSym"newCommentStmtNode", newLit(exprNode.strVal))
+  elif exprNode.kind == nnkEmpty:
+    # bug newTree(nnkEmpty) raises exception:
+    result = newCall(bindSym"newEmptyNode")
+  else:
+    if forwardLineInfo:
+      result = newCall(bindSym"newTreeWithLineinfo", newLit(exprNode.kind), newLit(exprNode.lineinfoObj))
+    else:
+      result = newCall(bindSym"newTree", newLit(exprNode.kind))
+    for child in exprNode:
+      result.add newTreeExpr(stmtList, child, unquoteIdent)
+
+macro quoteAst*(ast: untyped): untyped =
+  ## Substitute for ``quote do`` but with ``uq`` for unquoting instead of backticks.
+  result = newNimNode(nnkStmtListExpr)
+  result.add result.newTreeExpr(ast, ident"uq")
+
+  echo "quoteAst:"
+  echo result.repr
+
+macro quoteAst*(unquoteIdent, ast: untyped): untyped =
+  unquoteIdent.expectKind nnkIdent
+  result = newStmtList()
+  result.add newTreeExpr(result, ast, unquoteIdent)
+
+proc foo(arg1: int, arg2: string): string =
+  "xxx"
+
+macro foobar(arg: untyped): untyped =
+  # simple generation of source code:
+  result = quoteAst:
+    echo "Hello world!"
+
+  echo result.treeRepr
+
+  # inject subtrees from local scope, like `` in quote do:
+  let world = newLit("world")
+  result = quoteAst:
+    echo "Hello ", uq(world), "!"
+
+  echo result.treeRepr
+
+  # inject subtree from expression:
+  result = quoteAst:
+    echo "Hello ", uq(newLit("world")), "!"
+
+  echo result.treeRepr
+
+  # custom name for unquote in case `uq` should collide with anything.
+  let x = newLit(123)
+  result = quoteAst myUnquote:
+    echo "abc ", myUnquote(x), " ", myUnquote(newLit("xyz")), " ", myUnquote(arg)
+
+  #result = quoteAst:
+  #  echo uq(bindSym"foo")(123, "abc")
+
+  echo result.treeRepr
+
+  # result = quoteAst:
+  #   var tmp = 1
+  #   for x in 0 ..< 100:
+  #     tmp *= 3
+
+let myVal = "Hallo Welt!"
+foobar(myVal)
+
+# example from #10326
+
+template id*(val: int) {.pragma.}
+macro m1(): untyped =
+   let x = newLit(10)
+   let r1 = quote do:
+      type T1 {.id(`x`).} = object
+
+   let r2 = quoteAst:
+     type T1 {.id(uq(x)).} = object
+
+   echo "from #10326:"
+   echo r1[0][0].treeRepr
+   echo r2[0][0].treeRepr
+
+m1()
+
+macro lineinfoTest(): untyped =
+  # line info is preserved as if the content of ``quoteAst`` is written in a template
+  result = quoteAst:
+    assert(false)
+
+#lineinfoTest()
+
+# example from #7375
+
+macro fails(b: static[bool]): untyped =
+  echo b
+  result = newStmtList()
+
+macro works(b: static[int]): untyped =
+  echo b
+  result = newStmtList()
+
+macro foo(): untyped =
+
+  var b = newLit(false)
+
+  ## Fails
+  result = quote do:
+    fails(`b`)
+
+  ## Works
+  # result = quote do:
+  #   works(`b`)
+
+foo()
+
+
+# example from #9745  (does not work yet)
+# import macros
+
+# var
+#   typ {.compiletime.} = newLit("struct ABC")
+#   name {.compiletime.} = ident("ABC")
+
+# macro abc(): untyped =
+#   result = newNimNode(nnkStmtList)
+
+#   let x = quoteAst:
+#     type
+#       uq(name) {.importc: uq(typ).} = object
+
+#   echo result.repr
+
+# abc()
+
+# example from #7889
+
+from streams import newStringStream, readData, writeData
+import macros
+
+macro bindme*(): untyped =
+  quoteAst:
+    var tst = "sometext"
+    var ss = uq(bindSym"newStringStream")("anothertext")
+    uq(bindSym"writeData")(ss, tst[0].addr, 2)
+    discard uq(bindSym"readData")(ss, tst[0].addr, 2) # <= comment this out to make compilation successful
+
+# test.nim
+# from binder import bindme
+# bindme()
+
+
+# example from #8220
+
+macro fooA(): untyped =
+  result = quoteAst:
+    let bar = "Hello, World"
+    echo &"Let's interpolate {bar} in the string"
+
+foo()
+
+
+# example from #7589
+
+macro fooB(x: untyped): untyped =
+  result = quoteAst:
+    echo uq(bindSym"==")(3,4) # echo ["false"]' has no type (or is ambiguous)
+  echo result.treerepr
+
+# example from #7726
+
+import macros
+
+macro fooC(): untyped =
+  let a = @[1, 2, 3, 4, 5]
+  result = quoteAst:
+    uq(newLit(a.len))
+
+macro fooD(): untyped =
+  let a = @[1, 2, 3, 4, 5]
+  let len = a.len
+  result = quoteAst:
+    uq(newLit(len))
+
+macro fooE(): untyped =
+  let a = @[1, 2, 3, 4, 5]
+  let len = a.len
+  result = quoteAst:
+    uq(newLit(a[2]))
+
+echo fooC() # Outputs 5
+echo fooD() # Outputs 5
+echo fooE() # Outputs 3
+
+
+# example from #10430
+
+import macros
+
+macro commentTest(arg: untyped): untyped =
+  let tmp = quoteAst:
+    ## comment 1
+    echo "abc"
+    ## comment 2
+    ## still comment 2
+
+  doAssert tmp.treeRepr == arg.treeRepr
+
+commentTest:
+  ## comment 1
+  echo "abc"
+  ## comment 2
+  ## still comment 2