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

(cherry picked from commit ba158d73dc)
This commit is contained in:
metagn
2023-09-01 07:26:53 +03:00
committed by narimiran
parent 7c44af4e22
commit 3911c90d7b
6 changed files with 38 additions and 8 deletions

View File

@@ -2286,7 +2286,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 '('
@@ -2303,9 +2303,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 =

View File

@@ -1824,9 +1824,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)

View File

@@ -590,14 +590,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

View File

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

View File

@@ -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)']#

View File

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