summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/lexer.nim9
-rw-r--r--compiler/semstmts.nim16
-rw-r--r--doc/manual/stmts.txt74
-rw-r--r--tests/parser/ttupleunpack.nim10
4 files changed, 67 insertions, 42 deletions
diff --git a/compiler/lexer.nim b/compiler/lexer.nim
index 27cbfa9da..8080e0e8c 100644
--- a/compiler/lexer.nim
+++ b/compiler/lexer.nim
@@ -868,9 +868,14 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       tok.tokType = tkAccent
       inc(L.bufpos)
     of '_':
-      tok.tokType = tkSymbol
-      tok.ident = getIdent("_")
       inc(L.bufpos)
+      if L.buf[L.bufpos] notin SymChars:
+        tok.tokType = tkSymbol
+        tok.ident = getIdent("_")
+      else:
+        tok.literal = $c
+        tok.tokType = tkInvalid
+        lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
     of '\"':
       # check for extended raw string literal:
       var rawMode = L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index a9331b75a..c355a5bf1 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -369,9 +369,10 @@ proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) =
   else:
     result.add identDefs
 
-proc isDiscardUnderscore(n: PNode): bool =
-  if n.kind != nkIdent: return false
-  return n.ident.s == "_"
+proc isDiscardUnderscore(v: PSym): bool =
+  if v.name.s == "_":
+    v.flags.incl(sfGenSym)
+    result = true
 
 proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
   var b: PNode
@@ -436,10 +437,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
 
     for j in countup(0, length-3):
       var v = semIdentDef(c, a.sons[j], symkind)
-      if sfGenSym notin v.flags and
-         not isDiscardUnderscore(a.sons[j]): addInterfaceDecl(c, v)
-      if isDiscardUnderscore(a.sons[j]):
-        v.flags.incl(sfGenSym)
+      if sfGenSym notin v.flags and not isDiscardUnderscore(v):
+        addInterfaceDecl(c, v)
       when oKeepVariableNames:
         if c.inUnrolledContext > 0: v.flags.incl(sfShadowed)
         else:
@@ -554,7 +553,8 @@ proc semForVars(c: PContext, n: PNode): PNode =
       if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal)
       v.typ = iter.sons[i]
       n.sons[i] = newSymNode(v)
-      if sfGenSym notin v.flags: addForVarDecl(c, v)
+      if sfGenSym notin v.flags and not isDiscardUnderscore(v):
+        addForVarDecl(c, v)
   inc(c.p.nestedLoopCounter)
   n.sons[length-1] = semStmt(c, n.sons[length-1])
   dec(c.p.nestedLoopCounter)
diff --git a/doc/manual/stmts.txt b/doc/manual/stmts.txt
index 5b1284872..5e47110e9 100644
--- a/doc/manual/stmts.txt
+++ b/doc/manual/stmts.txt
@@ -2,7 +2,7 @@ Statements and expressions
 ==========================
 
 Nim uses the common statement/expression paradigm: Statements do not
-produce a value in contrast to expressions. However, some expressions are 
+produce a value in contrast to expressions. However, some expressions are
 statements.
 
 Statements are separated into `simple statements`:idx: and
@@ -16,9 +16,9 @@ statements always have to be intended. The details can be found in the grammar.
 Statement list expression
 -------------------------
 
-Statements can also occur in an expression context that looks 
+Statements can also occur in an expression context that looks
 like ``(stmt1; stmt2; ...; ex)``. This is called
-an statement list expression or ``(;)``. The type 
+an statement list expression or ``(;)``. The type
 of ``(stmt1; stmt2; ...; ex)`` is the type of ``ex``. All the other statements
 must be of type ``void``. (One can use ``discard`` to produce a ``void`` type.)
 ``(;)`` does not introduce a new scope.
@@ -30,24 +30,24 @@ Discard statement
 Example:
 
 .. code-block:: nim
-  proc p(x, y: int): int = 
+  proc p(x, y: int): int =
     result = x + y
 
   discard p(3, 4) # discard the return value of `p`
 
 The ``discard`` statement evaluates its expression for side-effects and
-throws the expression's resulting value away. 
+throws the expression's resulting value away.
 
 Ignoring the return value of a procedure without using a discard statement is
 a static error.
 
 The return value can be ignored implicitly if the called proc/iterator has
-been declared with the `discardable`:idx: pragma: 
+been declared with the `discardable`:idx: pragma:
 
 .. code-block:: nim
-  proc p(x, y: int): int {.discardable.} = 
+  proc p(x, y: int): int {.discardable.} =
     result = x + y
-    
+
   p(3, 4) # now valid
 
 An empty ``discard`` statement is often used as a null statement:
@@ -98,11 +98,11 @@ T = enum                        cast[T](0); this may be an invalid value
 
 
 The implicit initialization can be avoided for optimization reasons with the
-`noinit`:idx: pragma: 
+`noinit`:idx: pragma:
 
 .. code-block:: nim
   var
-    a {.noInit.}: array [0..1023, char] 
+    a {.noInit.}: array [0..1023, char]
 
 If a proc is annotated with the ``noinit`` pragma this refers to its implicit
 ``result`` variable:
@@ -113,13 +113,13 @@ If a proc is annotated with the ``noinit`` pragma this refers to its implicit
 
 The implicit initialization can be also prevented by the `requiresInit`:idx:
 type pragma. The compiler requires an explicit initialization then. However
-it does a `control flow analysis`:idx: to prove the variable has been 
+it does a `control flow analysis`:idx: to prove the variable has been
 initialized and does not rely on syntactic properties:
 
 .. code-block:: nim
   type
     MyObject = object {.requiresInit.}
-    
+
   proc p() =
     # the following is valid:
     var x: MyObject
@@ -129,11 +129,12 @@ initialized and does not rely on syntactic properties:
       x = a()
     use x
 
+
 let statement
 -------------
 
 A ``let`` statement declares new local and global `single assignment`:idx:
-variables and binds a value to them. The syntax is the same as that of the ``var`` 
+variables and binds a value to them. The syntax is the same as that of the ``var``
 statement, except that the keyword ``var`` is replaced by the keyword ``let``.
 Let variables are not l-values and can thus not be passed to ``var`` parameters
 nor can their address be taken. They cannot be assigned new values.
@@ -141,6 +142,19 @@ nor can their address be taken. They cannot be assigned new values.
 For let variables the same pragmas are available as for ordinary variables.
 
 
+Tuple unpacking
+---------------
+
+In a ``var`` or ``let`` statement tuple unpacking can be performed. The special
+identifier ``_`` can be used to ignore some parts of the tuple:
+
+.. code-block:: nim
+    proc returnsTuple(): (int, int, int) = (4, 2, 3)
+
+    let (x, _, z) = returnsTuple()
+
+
+
 Const section
 -------------
 
@@ -157,33 +171,33 @@ have no side-effect can be used in constant expressions too:
     constEval = contains("abc", 'b') # computed at compile time!
 
 
-The rules for compile-time computability are: 
+The rules for compile-time computability are:
 
 1. Literals are compile-time computable.
 2. Type conversions are compile-time computable.
 3. Procedure calls of the form ``p(X)`` are compile-time computable if
-   ``p`` is a proc without side-effects (see the `noSideEffect pragma`_ 
-   for details) and if ``X`` is a (possibly empty) list of compile-time 
+   ``p`` is a proc without side-effects (see the `noSideEffect pragma`_
+   for details) and if ``X`` is a (possibly empty) list of compile-time
    computable arguments.
 
 
-Constants cannot be of type ``ptr``, ``ref``, ``var`` or ``object``, nor can 
+Constants cannot be of type ``ptr``, ``ref``, ``var`` or ``object``, nor can
 they contain such a type.
 
 
 Static statement/expression
 ---------------------------
 
-A static statement/expression can be used to enforce compile 
+A static statement/expression can be used to enforce compile
 time evaluation explicitly. Enforced compile time evaluation can even evaluate
-code that has side effects: 
+code that has side effects:
 
 .. code-block::
 
   static:
     echo "echo at compile time"
 
-It's a static error if the compiler cannot perform the evaluation at compile 
+It's a static error if the compiler cannot perform the evaluation at compile
 time.
 
 The current implementation poses some restrictions for compile time
@@ -217,7 +231,7 @@ the ``:`` are executed. This goes on until the last ``elif``. If all
 conditions fail, the ``else`` part is executed. If there is no ``else``
 part, execution continues with the statement after the ``if`` statement.
 
-The scoping for an ``if`` statement is slightly subtle to support an important 
+The scoping for an ``if`` statement is slightly subtle to support an important
 use case. A new scope starts for the ``if``/``elif`` condition and ends after
 the corresponding *then* block:
 
@@ -229,7 +243,7 @@ the corresponding *then* block:
   else:
     # 'm' not declared here
 
-In the example the scopes have been enclosed in ``{|  |}``. 
+In the example the scopes have been enclosed in ``{|  |}``.
 
 
 Case statement
@@ -244,7 +258,7 @@ Example:
     echo("permission denied")
   of "go-for-a-walk":     echo("please yourself")
   else:                   echo("unknown command")
-  
+
   # indentation of the branches is also allowed; and so is an optional colon
   # after the selecting expression:
   case readline(stdin):
@@ -252,15 +266,15 @@ Example:
       echo("permission denied")
     of "go-for-a-walk":     echo("please yourself")
     else:                   echo("unknown command")
-  
+
 
 The ``case`` statement is similar to the if statement, but it represents
 a multi-branch selection. The expression after the keyword ``case`` is
 evaluated and if its value is in a *slicelist* the corresponding statements
 (after the ``of`` keyword) are executed. If the value is not in any
 given *slicelist* the ``else`` part is executed. If there is no ``else``
-part and not all possible values that ``expr`` can hold occur in a 
-``slicelist``, a static error occurs. This holds only for expressions of 
+part and not all possible values that ``expr`` can hold occur in a
+``slicelist``, a static error occurs. This holds only for expressions of
 ordinal types. "All possible values" of ``expr`` are determined by ``expr``'s
 type. To suppress the static error an ``else`` part with an
 empty ``discard`` statement should be used.
@@ -281,7 +295,7 @@ expanded into a list of its elements:
     of SymChars, '_': echo "an identifier"
     of '0'..'9': echo "a number"
     else: echo "other"
-  
+
   # is equivalent to:
   proc classify(s: string) =
     case s[0]
@@ -580,14 +594,14 @@ A table constructor is syntactic sugar for an array constructor:
 
 .. code-block:: nim
   {"key1": "value1", "key2", "key3": "value2"}
-  
+
   # is the same as:
   [("key1", "value1"), ("key2", "value2"), ("key3", "value2")]
 
 
-The empty table can be written ``{:}`` (in contrast to the empty set 
+The empty table can be written ``{:}`` (in contrast to the empty set
 which is ``{}``) which is thus another way to write as the empty array
-constructor ``[]``. This slightly unusal way of supporting tables 
+constructor ``[]``. This slightly unusal way of supporting tables
 has lots of advantages:
 
 * The order of the (key,value)-pairs is preserved, thus it is easy to
diff --git a/tests/parser/ttupleunpack.nim b/tests/parser/ttupleunpack.nim
index 581e6e940..aaa06f9f4 100644
--- a/tests/parser/ttupleunpack.nim
+++ b/tests/parser/ttupleunpack.nim
@@ -4,6 +4,11 @@ discard """
   exitcode: 0
 """
 
+proc returnsTuple(): (int, int, int) = (4, 2, 3)
+
+proc main2 =
+  let (x, _, z) = returnsTuple()
+
 proc main() =
 
   proc foo(): tuple[x, y, z: int] =
@@ -16,8 +21,8 @@ proc main() =
   var (a, _, _) = foo()
   doAssert a == 4
 
-  var (a, _, _xx) = foo()
-  doAssert a == 4
+  var (aa, _, _) = foo()
+  doAssert aa == 4
 
   iterator bar(): tuple[x, y, z: int] =
     yield (1,2,3)
@@ -27,3 +32,4 @@ proc main() =
     doAssert y == 2
 
 main()
+main2()