From e22ac3f5db85f31b24f2bf2fbc3fac16474eee97 Mon Sep 17 00:00:00 2001 From: metagn <10591326+metagn@users.noreply.github.com> Date: Fri, 1 Sep 2023 01:27:04 +0300 Subject: [PATCH] type annotations for variable tuple unpacking, better error messages closes #17989, closes https://github.com/nim-lang/RFCs/issues/339 --- compiler/parser.nim | 9 +++++++-- compiler/semexprs.nim | 6 ++++-- compiler/semstmts.nim | 10 ++++++++-- tests/errmsgs/tassignunpack.nim | 2 +- tests/parser/ttupleunpack.nim | 17 +++++++++++++++++ 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/compiler/parser.nim b/compiler/parser.nim index c386df57bf27..7caeca95e15c 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 ff9727967fa3..65ed25015ee8 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 bb9c474a8f85..f9168eef631d 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/tests/errmsgs/tassignunpack.nim b/tests/errmsgs/tassignunpack.nim index d74e16dd5e9f..09d928a54e6c 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 860ef66cfff0..88fce562dae3 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")