Tuple unpacking now works for for vars (#10152)

* Tuple unpacking now works for `for` vars
* Give error if length of tuple vars != length of tuple
* Fix error message showing wrong tuple length
* unpacking now works now for mutable items
* Update changelog
This commit is contained in:
Neelesh Chandola 2019-02-23 18:31:34 +05:30 • committed by Andreas Rumpf
commit 9884129058
7 changed files with 156 additions and 45 deletions

View file

@ -165,6 +165,7 @@ proc enumToString*(enums: openArray[enum]): string =
- Pragma blocks are no longer eliminated from the typed AST tree to preserve - Pragma blocks are no longer eliminated from the typed AST tree to preserve
pragmas for further analysis by macros pragmas for further analysis by macros
- Custom pragmas are now supported for `var` and `let` symbols. - Custom pragmas are now supported for `var` and `let` symbols.
- Tuple unpacking is now supported for constants and for loop variables.
### Language changes ### Language changes

View file

@ -752,7 +752,7 @@ proc genTupleElem(p: BProc, e: PNode, d: var TLoc) =
a: TLoc a: TLoc
i: int i: int
initLocExpr(p, e.sons[0], a) initLocExpr(p, e.sons[0], a)
let tupType = a.t.skipTypes(abstractInst) let tupType = a.t.skipTypes(abstractInst+{tyVar})
assert tupType.kind == tyTuple assert tupType.kind == tyTuple
d.inheritLocation(a) d.inheritLocation(a)
discard getTypeDesc(p.module, a.t) # fill the record's fields.loc discard getTypeDesc(p.module, a.t) # fill the record's fields.loc

View file

@ -21,12 +21,20 @@ proc newDeref*(n: PNode): PNode {.inline.} =
addSon(result, n) addSon(result, n)
proc newTupleAccess*(g: ModuleGraph; tup: PNode, i: int): PNode = proc newTupleAccess*(g: ModuleGraph; tup: PNode, i: int): PNode =
result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes( if tup.kind == nkHiddenAddr:
abstractInst).sons[i]) result = newNodeIT(nkHiddenAddr, tup.info, tup.typ.skipTypes(abstractInst+{tyPtr, tyVar}))
addSon(result, copyTree(tup)) result.addSon(newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes(abstractInst+{tyPtr, tyVar}).sons[i]))
var lit = newNodeIT(nkIntLit, tup.info, getSysType(g, tup.info, tyInt)) addSon(result[0], tup[0])
lit.intVal = i var lit = newNodeIT(nkIntLit, tup.info, getSysType(g, tup.info, tyInt))
addSon(result, lit) 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) = proc addVar*(father, v: PNode) =
var vpart = newNodeI(nkIdentDefs, v.info, 3) var vpart = newNodeI(nkIdentDefs, v.info, 3)

View file

@ -1153,18 +1153,26 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind,
result.addSon list result.addSon list
parseSymbolList(p, list) parseSymbolList(p, list)
proc parseVarTuple(p: var TParser): PNode
proc parseFor(p: var TParser): PNode = proc parseFor(p: var TParser): PNode =
#| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt
#| forExpr = forStmt #| forExpr = forStmt
result = newNodeP(nkForStmt, p)
getTokNoInd(p) getTokNoInd(p)
var a = identWithPragma(p) result = newNodeP(nkForStmt, p)
addSon(result, a) if p.tok.tokType == tkParLe:
while p.tok.tokType == tkComma: addSon(result, parseVarTuple(p))
getTok(p) else:
optInd(p, a) var a = identWithPragma(p)
a = identWithPragma(p)
addSon(result, a) 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) eat(p, tkIn)
addSon(result, parseExpr(p)) addSon(result, parseExpr(p))
colcom(p, result) colcom(p, result)
@ -2048,14 +2056,15 @@ proc parseVarTuple(p: var TParser): PNode =
addSon(result, p.emptyNode) # no type desc addSon(result, p.emptyNode) # no type desc
optPar(p) optPar(p)
eat(p, tkParRi) eat(p, tkParRi)
eat(p, tkEquals)
optInd(p, result)
addSon(result, parseExpr(p))
proc parseVariable(p: var TParser): PNode = proc parseVariable(p: var TParser): PNode =
#| colonBody = colcom stmt doBlocks? #| colonBody = colcom stmt doBlocks?
#| variable = (varTuple / identColonEquals) colonBody? indAndComment #| 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}) else: result = parseIdentColonEquals(p, {withPragma, withDot})
result[^1] = postExprBlocks(p, result[^1]) result[^1] = postExprBlocks(p, result[^1])
indAndComment(p, result) indAndComment(p, result)
@ -2072,10 +2081,10 @@ proc parseConstant(p: var TParser): PNode =
addSon(result, parseTypeDesc(p)) addSon(result, parseTypeDesc(p))
else: else:
addSon(result, p.emptyNode) addSon(result, p.emptyNode)
eat(p, tkEquals) eat(p, tkEquals)
optInd(p, result) optInd(p, result)
addSon(result, parseExpr(p)) addSon(result, parseExpr(p))
indAndComment(p, result) indAndComment(p, result)
proc parseBind(p: var TParser, k: TNodeKind): PNode = proc parseBind(p: var TParser, k: TNodeKind): PNode =
#| bindStmt = 'bind' optInd qualifiedIdent ^+ comma #| bindStmt = 'bind' optInd qualifiedIdent ^+ comma

View file

@ -672,28 +672,66 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
# and thus no tuple unpacking: # and thus no tuple unpacking:
if iter.kind != tyTuple or length == 3: if iter.kind != tyTuple or length == 3:
if length == 3: if length == 3:
var v = symForVar(c, n.sons[0]) if n.sons[0].kind == nkVarTuple:
if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) var mutable = false
# BUGFIX: don't use `iter` here as that would strip away if iter.kind == tyVar:
# the ``tyGenericInst``! See ``tests/compile/tgeneric.nim`` iter = iter.skipTypes({tyVar})
# for an example: mutable = true
v.typ = iterBase if sonsLen(n[0])-1 != sonsLen(iter):
n.sons[0] = newSymNode(v) localError(c.config, n[0].info, errWrongNumberOfVariables)
if sfGenSym notin v.flags: addForVarDecl(c, v) for i in 0 ..< sonsLen(n[0])-1:
elif v.owner == nil: v.owner = getCurrOwner(c) 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: else:
localError(c.config, n.info, errWrongNumberOfVariables) localError(c.config, n.info, errWrongNumberOfVariables)
elif length-2 != sonsLen(iter): elif length-2 != sonsLen(iter):
localError(c.config, n.info, errWrongNumberOfVariables) localError(c.config, n.info, errWrongNumberOfVariables)
else: else:
for i in countup(0, length - 3): for i in countup(0, length - 3):
var v = symForVar(c, n.sons[i]) if n.sons[i].kind == nkVarTuple:
if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) var mutable = false
v.typ = iter.sons[i] if iter[i].kind == tyVar:
n.sons[i] = newSymNode(v) iter[i] = iter[i].skipTypes({tyVar})
if sfGenSym notin v.flags: mutable = true
if not isDiscardUnderscore(v): addForVarDecl(c, v) if sonsLen(n[i])-1 != sonsLen(iter[i]):
elif v.owner == nil: v.owner = getCurrOwner(c) 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) inc(c.p.nestedLoopCounter)
openScope(c) openScope(c)
n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags) n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags)

View file

@ -378,9 +378,15 @@ proc transformYield(c: PTransf, n: PNode): PTransNode =
for i in countup(0, sonsLen(e) - 1): for i in countup(0, sonsLen(e) - 1):
var v = e.sons[i] var v = e.sons[i]
if v.kind == nkExprColonExpr: v = v.sons[1] if v.kind == nkExprColonExpr: v = v.sons[1]
let lhs = c.transCon.forStmt.sons[i] if c.transCon.forStmt[i].kind == nkVarTuple:
let rhs = transform(c, v) for j in 0 ..< sonsLen(c.transCon.forStmt[i])-1:
add(result, asgnTo(lhs, rhs)) 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: else:
# Unpack the tuple into the loop variables # Unpack the tuple into the loop variables
# XXX: BUG: what if `n` is an expression with side-effects? # 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)) let rhs = transform(c, newTupleAccess(c.graph, e, i))
add(result, asgnTo(lhs, rhs)) add(result, asgnTo(lhs, rhs))
else: else:
let lhs = c.transCon.forStmt.sons[0] if c.transCon.forStmt.sons[0].kind == nkVarTuple:
let rhs = transform(c, e) for i in 0 ..< sonsLen(c.transCon.forStmt[0])-1:
add(result, asgnTo(lhs, rhs)) 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) inc(c.transCon.yieldStmts)
if c.transCon.yieldStmts <= 1: if c.transCon.yieldStmts <= 1:
@ -609,7 +621,11 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
var v = newNodeI(nkVarSection, n.info) var v = newNodeI(nkVarSection, n.info)
for i in countup(0, length - 3): 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) add(stmtList, v.PTransNode)
# Bugfix: inlined locals belong to the invoking routine, not to the invoked # Bugfix: inlined locals belong to the invoking routine, not to the invoked

View file

@ -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