summary refs log tree commit diff stats
diff options
context:
space:
mode:
authormetagn <metagngn@gmail.com>2023-09-01 07:26:53 +0300
committerGitHub <noreply@github.com>2023-09-01 06:26:53 +0200
commitba158d73dc23fb6e61ebe88f6485f95c8dcb96c2 (patch)
treeb81216df57196946115e4219322b712ddefa5d79
parentb3912c25d3dcb78bc1c8f7d6acc3c512964d3ea8 (diff)
downloadNim-ba158d73dc23fb6e61ebe88f6485f95c8dcb96c2.tar.gz
type annotations for variable tuple unpacking, better error messages (#22611)
* type annotations for variable tuple unpacking, better error messages

closes #17989, closes https://github.com/nim-lang/RFCs/issues/339

* update grammar

* fix test
-rw-r--r--compiler/parser.nim9
-rw-r--r--compiler/semexprs.nim6
-rw-r--r--compiler/semstmts.nim10
-rw-r--r--doc/grammar.txt2
-rw-r--r--tests/errmsgs/tassignunpack.nim2
-rw-r--r--tests/parser/ttupleunpack.nim17
6 files changed, 38 insertions, 8 deletions
diff --git a/compiler/parser.nim b/compiler/parser.nim
index c386df57b..7caeca95e 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -2287,7 +2287,7 @@ proc parseTypeDef(p: var Parser): PNode =
   setEndInfo()
 
 proc parseVarTuple(p: var Parser): PNode =
-  #| varTupleLhs = '(' optInd (identWithPragma / varTupleLhs) ^+ comma optPar ')'
+  #| varTupleLhs = '(' optInd (identWithPragma / varTupleLhs) ^+ comma optPar ')' (':' optInd typeDescExpr)?
   #| varTuple = varTupleLhs '=' optInd expr
   result = newNodeP(nkVarTuple, p)
   getTok(p)                   # skip '('
@@ -2304,9 +2304,14 @@ proc parseVarTuple(p: var Parser): PNode =
     if p.tok.tokType != tkComma: break
     getTok(p)
     skipComment(p, a)
-  result.add(p.emptyNode)         # no type desc
   optPar(p)
   eat(p, tkParRi)
+  if p.tok.tokType == tkColon:
+    getTok(p)
+    optInd(p, result)
+    result.add(parseTypeDesc(p, fullExpr = true))
+  else:
+    result.add(p.emptyNode)         # no type desc
   setEndInfo()
 
 proc parseVariable(p: var Parser): PNode =
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index ff9727967..65ed25015 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -1834,9 +1834,11 @@ proc makeTupleAssignments(c: PContext; n: PNode): PNode =
   let lhs = n[0]
   let value = semExprWithType(c, n[1], {efTypeAllowed})
   if value.typ.kind != tyTuple:
-    localError(c.config, n[1].info, errXExpected, "tuple")
+    localError(c.config, n[1].info, errTupleUnpackingTupleExpected %
+      [typeToString(value.typ, preferDesc)])
   elif lhs.len != value.typ.len:
-    localError(c.config, n.info, errWrongNumberOfVariables)
+    localError(c.config, n.info, errTupleUnpackingDifferentLengths %
+      [$lhs.len, typeToString(value.typ, preferDesc), $value.typ.len])
   result = newNodeI(nkStmtList, n.info)
 
   let temp = newSym(skTemp, getIdent(c.cache, "tmpTupleAsgn"), c.idgen, getCurrOwner(c), n.info)
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index bb9c474a8..f9168eef6 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -597,14 +597,20 @@ proc globalVarInitCheck(c: PContext, n: PNode) =
   if n.isLocalVarSym or n.kind in nkCallKinds and usesLocalVar(n):
     localError(c.config, n.info, errCannotAssignToGlobal)
 
+const
+  errTupleUnpackingTupleExpected = "tuple expected for tuple unpacking, but got '$1'"
+  errTupleUnpackingDifferentLengths = "tuple with $1 elements expected, but got '$2' with $3 elements"
+
 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")
+    localError(c.config, a.info, errTupleUnpackingTupleExpected %
+      [typeToString(typ, preferDesc)])
   elif a.len-2 != typ.len:
-    localError(c.config, a.info, errWrongNumberOfVariables)
+    localError(c.config, a.info, errTupleUnpackingDifferentLengths %
+      [$(a.len-2), typeToString(typ, preferDesc), $typ.len])
   var
     tempNode: PNode = nil
     lastDef: PNode
diff --git a/doc/grammar.txt b/doc/grammar.txt
index 3096eecb5..f1484bb0b 100644
--- a/doc/grammar.txt
+++ b/doc/grammar.txt
@@ -192,7 +192,7 @@ conceptDecl = 'concept' conceptParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
               &IND{>} stmt
 typeDef = identVisDot genericParamList? pragma '=' optInd typeDefValue
             indAndComment?
-varTupleLhs = '(' optInd (identWithPragma / varTupleLhs) ^+ comma optPar ')'
+varTupleLhs = '(' optInd (identWithPragma / varTupleLhs) ^+ comma optPar ')' (':' optInd typeDescExpr)?
 varTuple = varTupleLhs '=' optInd expr
 colonBody = colcom stmt postExprBlocks?
 variable = (varTuple / identColonEquals) colonBody? indAndComment
diff --git a/tests/errmsgs/tassignunpack.nim b/tests/errmsgs/tassignunpack.nim
index d74e16dd5..09d928a54 100644
--- a/tests/errmsgs/tassignunpack.nim
+++ b/tests/errmsgs/tassignunpack.nim
@@ -1,3 +1,3 @@
 var a, b = 0
 (a, b) = 1 #[tt.Error
-         ^ 'tuple' expected]#
+         ^ tuple expected for tuple unpacking, but got 'int literal(1)']#
diff --git a/tests/parser/ttupleunpack.nim b/tests/parser/ttupleunpack.nim
index 860ef66cf..993501fbb 100644
--- a/tests/parser/ttupleunpack.nim
+++ b/tests/parser/ttupleunpack.nim
@@ -75,3 +75,20 @@ block: # unary assignment unpacking
   var a: int
   (a,) = (1,)
   doAssert a == 1
+
+block: # type annotations
+  block: # basic
+    let (a, b): (int, int) = (1, 2)
+    doAssert (a, b) == (1, 2)
+  block: # type inference
+    let (a, b): (byte, float) = (1, 2)
+    doAssert (a, b) == (1.byte, 2.0)
+  block: # type mismatch
+    doAssert not (compiles do:
+      let (a, b): (int, string) = (1, 2))
+  block: # nested
+    let (a, (b, c)): (int, (int, int)) = (1, (2, 3))
+    doAssert (a, b, c) == (1, 2, 3)
+  block: # nested type inference
+    let (a, (b, c)): (byte, (float, cstring)) = (1, (2, "abc"))
+    doAssert (a, b, c) == (1.byte, 2.0, cstring"abc")