summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md1
-rw-r--r--compiler/ccgexprs.nim2
-rw-r--r--compiler/lowerings.nim20
-rw-r--r--compiler/parser.nim39
-rw-r--r--compiler/semstmts.nim70
-rw-r--r--compiler/transf.nim30
-rw-r--r--tests/tuples/tfortupleunpack.nim39
7 files changed, 156 insertions, 45 deletions
diff --git a/changelog.md b/changelog.md
index 211855bfc..7f6ad1ecb 100644
--- a/changelog.md
+++ b/changelog.md
@@ -165,6 +165,7 @@ proc enumToString*(enums: openArray[enum]): string =
 - Pragma blocks are no longer eliminated from the typed AST tree to preserve
   pragmas for further analysis by macros
 - Custom pragmas are now supported for `var` and `let` symbols.
+- Tuple unpacking is now supported for constants and for loop variables.
 
 
 ### Language changes
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 7e0437c39..f92f2e4de 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -752,7 +752,7 @@ proc genTupleElem(p: BProc, e: PNode, d: var TLoc) =
     a: TLoc
     i: int
   initLocExpr(p, e.sons[0], a)
-  let tupType = a.t.skipTypes(abstractInst)
+  let tupType = a.t.skipTypes(abstractInst+{tyVar})
   assert tupType.kind == tyTuple
   d.inheritLocation(a)
   discard getTypeDesc(p.module, a.t) # fill the record's fields.loc
diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim
index d199abcc7..2c1c3c48d 100644
--- a/compiler/lowerings.nim
+++ b/compiler/lowerings.nim
@@ -21,12 +21,20 @@ proc newDeref*(n: PNode): PNode {.inline.} =
   addSon(result, n)
 
 proc newTupleAccess*(g: ModuleGraph; tup: PNode, i: int): PNode =
-  result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes(
-                     abstractInst).sons[i])
-  addSon(result, copyTree(tup))
-  var lit = newNodeIT(nkIntLit, tup.info, getSysType(g, tup.info, tyInt))
-  lit.intVal = i
-  addSon(result, lit)
+  if tup.kind == nkHiddenAddr:
+    result = newNodeIT(nkHiddenAddr, tup.info, tup.typ.skipTypes(abstractInst+{tyPtr, tyVar}))
+    result.addSon(newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes(abstractInst+{tyPtr, tyVar}).sons[i]))
+    addSon(result[0], tup[0])
+    var lit = newNodeIT(nkIntLit, tup.info, getSysType(g, tup.info, tyInt))
+    lit.intVal = i
+    addSon(result[0], lit)
+  else:
+    result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes(
+                       abstractInst).sons[i])
+    addSon(result, copyTree(tup))
+    var lit = newNodeIT(nkIntLit, tup.info, getSysType(g, tup.info, tyInt))
+    lit.intVal = i
+    addSon(result, lit)
 
 proc addVar*(father, v: PNode) =
   var vpart = newNodeI(nkIdentDefs, v.info, 3)
diff --git a/compiler/parser.nim b/compiler/parser.nim
index 01a3ce4d0..260e57cdb 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -1153,18 +1153,26 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind,
     result.addSon list
     parseSymbolList(p, list)
 
+proc parseVarTuple(p: var TParser): PNode
+
 proc parseFor(p: var TParser): PNode =
   #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt
   #| forExpr = forStmt
-  result = newNodeP(nkForStmt, p)
   getTokNoInd(p)
-  var a = identWithPragma(p)
-  addSon(result, a)
-  while p.tok.tokType == tkComma:
-    getTok(p)
-    optInd(p, a)
-    a = identWithPragma(p)
+  result = newNodeP(nkForStmt, p)
+  if p.tok.tokType == tkParLe:
+    addSon(result, parseVarTuple(p))
+  else:
+    var a = identWithPragma(p)
     addSon(result, a)
+    while p.tok.tokType == tkComma:
+      getTok(p)
+      optInd(p, a)
+      if p.tok.tokType == tkParLe:
+        addSon(result, parseVarTuple(p))
+        break
+      a = identWithPragma(p)
+      addSon(result, a)
   eat(p, tkIn)
   addSon(result, parseExpr(p))
   colcom(p, result)
@@ -2048,14 +2056,15 @@ proc parseVarTuple(p: var TParser): PNode =
   addSon(result, p.emptyNode)         # no type desc
   optPar(p)
   eat(p, tkParRi)
-  eat(p, tkEquals)
-  optInd(p, result)
-  addSon(result, parseExpr(p))
 
 proc parseVariable(p: var TParser): PNode =
   #| colonBody = colcom stmt doBlocks?
   #| variable = (varTuple / identColonEquals) colonBody? indAndComment
-  if p.tok.tokType == tkParLe: result = parseVarTuple(p)
+  if p.tok.tokType == tkParLe:
+    result = parseVarTuple(p)
+    eat(p, tkEquals)
+    optInd(p, result)
+    addSon(result, parseExpr(p))
   else: result = parseIdentColonEquals(p, {withPragma, withDot})
   result[^1] = postExprBlocks(p, result[^1])
   indAndComment(p, result)
@@ -2072,10 +2081,10 @@ proc parseConstant(p: var TParser): PNode =
       addSon(result, parseTypeDesc(p))
     else:
       addSon(result, p.emptyNode)
-    eat(p, tkEquals)
-    optInd(p, result)
-    addSon(result, parseExpr(p))
-    indAndComment(p, result)
+  eat(p, tkEquals)
+  optInd(p, result)
+  addSon(result, parseExpr(p))
+  indAndComment(p, result)
 
 proc parseBind(p: var TParser, k: TNodeKind): PNode =
   #| bindStmt = 'bind' optInd qualifiedIdent ^+ comma
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 363049672..aa0230f2f 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -672,28 +672,66 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
   # and thus no tuple unpacking:
   if iter.kind != tyTuple or length == 3:
     if length == 3:
-      var v = symForVar(c, n.sons[0])
-      if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
-      # BUGFIX: don't use `iter` here as that would strip away
-      # the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
-      # for an example:
-      v.typ = iterBase
-      n.sons[0] = newSymNode(v)
-      if sfGenSym notin v.flags: addForVarDecl(c, v)
-      elif v.owner == nil: v.owner = getCurrOwner(c)
+      if n.sons[0].kind == nkVarTuple:
+        var mutable = false
+        if iter.kind == tyVar:
+          iter = iter.skipTypes({tyVar})
+          mutable = true
+        if sonsLen(n[0])-1 != sonsLen(iter):
+          localError(c.config, n[0].info, errWrongNumberOfVariables)
+        for i in 0 ..< sonsLen(n[0])-1:
+          var v = symForVar(c, n[0][i])
+          if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+          if mutable:
+            v.typ = newTypeS(tyVar, c)
+            v.typ.sons.add iter[i]
+          else:
+            v.typ = iter.sons[i]
+          n.sons[0][i] = newSymNode(v)
+          if sfGenSym notin v.flags: addForVarDecl(c, v)
+          elif v.owner == nil: v.owner = getCurrOwner(c)
+      else:
+        var v = symForVar(c, n.sons[0])
+        if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+        # BUGFIX: don't use `iter` here as that would strip away
+        # the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
+        # for an example:
+        v.typ = iterBase
+        n.sons[0] = newSymNode(v)
+        if sfGenSym notin v.flags: addForVarDecl(c, v)
+        elif v.owner == nil: v.owner = getCurrOwner(c)
     else:
       localError(c.config, n.info, errWrongNumberOfVariables)
   elif length-2 != sonsLen(iter):
     localError(c.config, n.info, errWrongNumberOfVariables)
   else:
     for i in countup(0, length - 3):
-      var v = symForVar(c, n.sons[i])
-      if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
-      v.typ = iter.sons[i]
-      n.sons[i] = newSymNode(v)
-      if sfGenSym notin v.flags:
-        if not isDiscardUnderscore(v): addForVarDecl(c, v)
-      elif v.owner == nil: v.owner = getCurrOwner(c)
+      if n.sons[i].kind == nkVarTuple:
+        var mutable = false
+        if iter[i].kind == tyVar:
+          iter[i] = iter[i].skipTypes({tyVar})
+          mutable = true
+        if sonsLen(n[i])-1 != sonsLen(iter[i]):
+          localError(c.config, n[i].info, errWrongNumberOfVariables)
+        for j in 0 ..< sonsLen(n[i])-1:
+          var v = symForVar(c, n[i][j])
+          if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+          if mutable:
+            v.typ = newTypeS(tyVar, c)
+            v.typ.sons.add iter[i][j]
+          else:
+            v.typ = iter[i][j]
+          n.sons[i][j] = newSymNode(v)
+          if not isDiscardUnderscore(v): addForVarDecl(c, v)
+          elif v.owner == nil: v.owner = getCurrOwner(c)
+      else:
+        var v = symForVar(c, n.sons[i])
+        if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
+        v.typ = iter.sons[i]
+        n.sons[i] = newSymNode(v)
+        if sfGenSym notin v.flags:
+          if not isDiscardUnderscore(v): addForVarDecl(c, v)
+        elif v.owner == nil: v.owner = getCurrOwner(c)
   inc(c.p.nestedLoopCounter)
   openScope(c)
   n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags)
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 071cb00ee..a26598094 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -378,9 +378,15 @@ proc transformYield(c: PTransf, n: PNode): PTransNode =
       for i in countup(0, sonsLen(e) - 1):
         var v = e.sons[i]
         if v.kind == nkExprColonExpr: v = v.sons[1]
-        let lhs = c.transCon.forStmt.sons[i]
-        let rhs = transform(c, v)
-        add(result, asgnTo(lhs, rhs))
+        if c.transCon.forStmt[i].kind == nkVarTuple:
+          for j in 0 ..< sonsLen(c.transCon.forStmt[i])-1:
+            let lhs = c.transCon.forStmt[i][j]
+            let rhs = transform(c, newTupleAccess(c.graph, v, j))
+            add(result, asgnTo(lhs, rhs))
+        else:
+          let lhs = c.transCon.forStmt.sons[i]
+          let rhs = transform(c, v)
+          add(result, asgnTo(lhs, rhs))
     else:
       # Unpack the tuple into the loop variables
       # XXX: BUG: what if `n` is an expression with side-effects?
@@ -389,9 +395,15 @@ proc transformYield(c: PTransf, n: PNode): PTransNode =
         let rhs = transform(c, newTupleAccess(c.graph, e, i))
         add(result, asgnTo(lhs, rhs))
   else:
-    let lhs = c.transCon.forStmt.sons[0]
-    let rhs = transform(c, e)
-    add(result, asgnTo(lhs, rhs))
+    if c.transCon.forStmt.sons[0].kind == nkVarTuple:
+      for i in 0 ..< sonsLen(c.transCon.forStmt[0])-1:
+        let lhs = c.transCon.forStmt[0][i]
+        let rhs = transform(c, newTupleAccess(c.graph, e, i))
+        add(result, asgnTo(lhs, rhs))
+    else:
+      let lhs = c.transCon.forStmt.sons[0]
+      let rhs = transform(c, e)
+      add(result, asgnTo(lhs, rhs))
 
   inc(c.transCon.yieldStmts)
   if c.transCon.yieldStmts <= 1:
@@ -609,7 +621,11 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
 
   var v = newNodeI(nkVarSection, n.info)
   for i in countup(0, length - 3):
-    addVar(v, copyTree(n.sons[i])) # declare new vars
+    if n[i].kind == nkVarTuple:
+      for j in 0 ..< sonsLen(n[i])-1:
+        addVar(v, copyTree(n[i][j])) # declare new vars
+    else:
+      addVar(v, copyTree(n.sons[i])) # declare new vars
   add(stmtList, v.PTransNode)
 
   # Bugfix: inlined locals belong to the invoking routine, not to the invoked
diff --git a/tests/tuples/tfortupleunpack.nim b/tests/tuples/tfortupleunpack.nim
new file mode 100644
index 000000000..56cf30ebc
--- /dev/null
+++ b/tests/tuples/tfortupleunpack.nim
@@ -0,0 +1,39 @@
+discard """
+output: '''
+123
+113283
+0
+123
+1
+113283
+@[(88, 99, 11), (88, 99, 11)]
+@[(7, 6, -28), (7, 6, -28)]
+'''
+"""
+
+let t1 = (1, 2, 3)
+let t2 = (11, 32, 83)
+let s = @[t1, t2]
+
+for (a, b, c) in s:
+  echo a, b, c
+
+for i, (a, b, c) in s:
+  echo i
+  echo a, b, c
+
+var x = @[(1,2,3), (4,5,6)]
+
+for (a, b, c) in x.mitems:
+  a = 88
+  b = 99
+  c = 11
+echo x
+
+for i, (a, b, c) in x.mpairs:
+  a = 7
+  b = 6
+  c = -28
+echo x
+
+