Skip to content

Commit

Permalink
round out tuple unpacking assignment, support underscores (#22537)
Browse files Browse the repository at this point in the history
* round out tuple unpacking assignment, support underscores

fixes #18710

* fix test messages

* use discard instead of continue

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>

---------

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
(cherry picked from commit 53d43e9)
  • Loading branch information
metagn authored and narimiran committed Apr 17, 2024
1 parent c39a013 commit 0d02bee
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 33 deletions.
19 changes: 0 additions & 19 deletions compiler/lowerings.nim
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,6 @@ proc newTupleAccessRaw*(tup: PNode, i: int): PNode =
proc newTryFinally*(body, final: PNode): PNode =
result = newTree(nkHiddenTryStmt, body, newTree(nkFinally, final))

proc lowerTupleUnpackingForAsgn*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PNode =
let value = n.lastSon
result = newNodeI(nkStmtList, n.info)

var temp = newSym(skTemp, getIdent(g.cache, "_"), idgen, owner, value.info, owner.options)
var v = newNodeI(nkLetSection, value.info)
let tempAsNode = newSymNode(temp) #newIdentNode(getIdent(genPrefix & $temp.id), value.info)

var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
vpart[0] = tempAsNode
vpart[1] = newNodeI(nkTupleClassTy, value.info)
vpart[2] = value
v.add vpart
result.add(v)

let lhs = n[0]
for i in 0..<lhs.len:
result.add newAsgnStmt(lhs[i], newTupleAccessRaw(tempAsNode, i))

proc lowerSwap*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PNode =
result = newNodeI(nkStmtList, n.info)
# note: cannot use 'skTemp' here cause we really need the copy for the VM :-(
Expand Down
33 changes: 32 additions & 1 deletion compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,37 @@ proc goodLineInfo(arg: PNode): TLineInfo =
else:
arg.info

proc makeTupleAssignments(c: PContext; n: PNode): PNode =
## expand tuple unpacking assignment into series of assignments
##
## mirrored with semstmts.makeVarTupleSection
let lhs = n[0]
let value = semExprWithType(c, n[1], {efTypeAllowed})
if value.typ.kind != tyTuple:
localError(c.config, n[1].info, errXExpected, "tuple")
elif lhs.len != value.typ.len:
localError(c.config, n.info, errWrongNumberOfVariables)
result = newNodeI(nkStmtList, n.info)

let temp = newSym(skTemp, getIdent(c.cache, "tmpTupleAsgn"), c.idgen, getCurrOwner(c), n.info)
temp.typ = value.typ
temp.flags.incl(sfGenSym)
var v = newNodeI(nkLetSection, value.info)
let tempNode = newSymNode(temp) #newIdentNode(getIdent(genPrefix & $temp.id), value.info)
var vpart = newNodeI(nkIdentDefs, v.info, 3)
vpart[0] = tempNode
vpart[1] = c.graph.emptyNode
vpart[2] = value
v.add vpart
result.add(v)

for i in 0..<lhs.len:
if lhs[i].kind == nkIdent and lhs[i].ident.id == ord(wUnderscore):
# skip _ assignments if we are using a temp as they are already evaluated
discard
else:
result.add newAsgnStmt(lhs[i], newTupleAccessRaw(tempNode, i))

proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
checkSonsLen(n, 2, c.config)
var a = n[0]
Expand Down Expand Up @@ -1859,7 +1890,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
# unfortunately we need to rewrite ``(x, y) = foo()`` already here so
# that overloading of the assignment operator still works. Usually we
# prefer to do these rewritings in transf.nim:
return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.idgen, c.p.owner), {})
return semStmt(c, makeTupleAssignments(c, n), {})
else:
a = semExprWithType(c, a, {efLValue})
else:
Expand Down
17 changes: 10 additions & 7 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -592,30 +592,33 @@ proc globalVarInitCheck(c: PContext, n: PNode) =

proc makeVarTupleSection(c: PContext, n, a, def: PNode, typ: PType, symkind: TSymKind, origResult: var PNode): PNode =
## expand tuple unpacking assignments into new var/let/const section
##
## mirrored with semexprs.makeTupleAssignments
if typ.kind != tyTuple:
localError(c.config, a.info, errXExpected, "tuple")
elif a.len-2 != typ.len:
localError(c.config, a.info, errWrongNumberOfVariables)
var
tmpTuple: PSym
tempNode: PNode = nil
lastDef: PNode
let defkind = if symkind == skConst: nkConstDef else: nkIdentDefs
# temporary not needed if not const and RHS is tuple literal
# const breaks with seqs without temporary
let useTemp = def.kind notin {nkPar, nkTupleConstr} or symkind == skConst
if useTemp:
# use same symkind for compatibility with original section
tmpTuple = newSym(symkind, getIdent(c.cache, "tmpTuple"), c.idgen, getCurrOwner(c), n.info)
tmpTuple.typ = typ
tmpTuple.flags.incl(sfGenSym)
let temp = newSym(symkind, getIdent(c.cache, "tmpTuple"), c.idgen, getCurrOwner(c), n.info)
temp.typ = typ
temp.flags.incl(sfGenSym)
lastDef = newNodeI(defkind, a.info)
newSons(lastDef, 3)
lastDef[0] = newSymNode(tmpTuple)
lastDef[0] = newSymNode(temp)
# NOTE: at the moment this is always ast.emptyNode, see parser.nim
lastDef[1] = a[^2]
lastDef[2] = def
tmpTuple.ast = lastDef
temp.ast = lastDef
addToVarSection(c, origResult, n, lastDef)
tempNode = newSymNode(temp)
result = newNodeI(n.kind, a.info)
for j in 0..<a.len-2:
let name = a[j]
Expand All @@ -634,7 +637,7 @@ proc makeVarTupleSection(c: PContext, n, a, def: PNode, typ: PType, symkind: TSy
lastDef[0] = name
lastDef[^2] = c.graph.emptyNode
if useTemp:
lastDef[^1] = newTreeIT(nkBracketExpr, name.info, typ[j], newSymNode(tmpTuple), newIntNode(nkIntLit, j))
lastDef[^1] = newTupleAccessRaw(tempNode, j)
else:
var val = def[j]
if val.kind == nkExprColonExpr: val = val[1]
Expand Down
8 changes: 4 additions & 4 deletions tests/arc/topt_no_cursor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ var
lresult
lvalue
lnext
_
tmpTupleAsgn
lresult = @[123]
_ = (
tmpTupleAsgn = (
let blitTmp = lresult
blitTmp, ";")
lvalue = _[0]
lnext = _[1]
lvalue = tmpTupleAsgn[0]
lnext = tmpTupleAsgn[1]
`=sink`(result.value, move lvalue)
`=destroy`(lnext)
`=destroy_1`(lvalue)
Expand Down
2 changes: 1 addition & 1 deletion tests/errmsgs/tassignunpack.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
var a, b = 0
(a, b) = 1 #[tt.Error
^ type mismatch: got <int literal(1)> but expected 'tuple']#
^ 'tuple' expected]#
12 changes: 12 additions & 0 deletions tests/tuples/ttuples_various.nim
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,15 @@ block: # bug #22054

var v = A(field: (a: 1314))
doAssert get(v)[0] == 1314

block: # tuple unpacking assignment with underscore
var
a = 1
b = 2
doAssert (a, b) == (1, 2)
(a, _) = (3, 4)
doAssert (a, b) == (3, 2)
(_, a) = (5, 6)
doAssert (a, b) == (6, 2)
(b, _) = (7, 8)
doAssert (a, b) == (6, 7)
2 changes: 1 addition & 1 deletion tests/types/tassignemptytuple.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
discard """
errormsg: "invalid type: 'empty' in this context: '(seq[empty], (seq[empty], set[empty]))' for let"
errormsg: "cannot infer the type of the tuple"
file: "tassignemptytuple.nim"
line: 11
"""
Expand Down

0 comments on commit 0d02bee

Please sign in to comment.