Skip to content

Commit

Permalink
document general use of _, error message, fixes
Browse files Browse the repository at this point in the history
fixes nim-lang#20687, fixes nim-lang#21435

Documentation and changelog updated to clarify new universal behavior
of `_`. Also new error message for attempting to use `_`, new tests,
and fixes with overloadable symbols and
implicit generics.
  • Loading branch information
metagn committed Mar 30, 2023
1 parent 51ced0d commit 7585f62
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 31 deletions.
36 changes: 26 additions & 10 deletions changelogs/changelog_2_0_0.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,26 +135,42 @@

- The experimental strictFuncs feature now disallows a store to the heap via a `ref` or `ptr` indirection.

- Underscores (`_`) as routine parameters are now ignored and cannot be used in the routine body.
The following code now does not compile:
- The underscore identifier (`_`) is now generally not added to scope when
used as the name of a definition. While this was already the case for
variables, it is now also the case for routine parameters, generic
parameters, routine declarations, type declarations, etc. This means that the following code now does not compile:

```nim
proc foo(_: int): int = _ + 1
echo foo(1)
proc foo[_](t: typedesc[_]): seq[_] = @[default(_)]
echo foo[int]()
proc _() = echo "_"
_()
type _ = int
let x: _ = 3
```

Instead, the following code now compiles:
Whereas the following code now compiles:

```nim
proc foo(_, _: int): int = 123
echo foo(1, 2)
```
- Underscores (`_`) as generic parameters are not supported and cannot be used.
Generics that use `_` as parameters will no longer compile requires you to replace `_` with something else:

```nim
proc foo[_](t: typedesc[_]): string = "BAR" # Can not compile
proc foo[T](t: typedesc[T]): string = "BAR" # Can compile
proc foo[_, _](): int = 123
echo foo[int, bool]()
proc foo[T, U](_: typedesc[T], _: typedesc[U]): (T, U) = (default(T), default(U))
echo foo(int, bool)
proc _() = echo "one"
proc _() = echo "two"
type _ = int
type _ = float
```

- - Added the `--legacy:verboseTypeMismatch` switch to get legacy type mismatch error messages.
Expand Down
27 changes: 16 additions & 11 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -396,11 +396,12 @@ proc addOverloadableSymAt*(c: PContext; scope: PScope, fn: PSym) =
if fn.kind notin OverloadableSyms:
internalError(c.config, fn.info, "addOverloadableSymAt")
return
let check = strTableGet(scope.symbols, fn.name)
if check != nil and check.kind notin OverloadableSyms:
wrongRedefinition(c, fn.info, fn.name.s, check.info)
else:
scope.addSym(fn)
if fn.name.s != "_":
let check = strTableGet(scope.symbols, fn.name)
if check != nil and check.kind notin OverloadableSyms:
wrongRedefinition(c, fn.info, fn.name.s, check.info)
else:
scope.addSym(fn)

proc addInterfaceOverloadableSymAt*(c: PContext, scope: PScope, sym: PSym) =
## adds an overloadable symbol on the scope and the interface if appropriate
Expand Down Expand Up @@ -546,12 +547,16 @@ proc errorUseQualifier*(c: PContext; info:TLineInfo; choices: PNode) =
errorUseQualifier(c, info, candidates, prefix)

proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string, extra = "") =
var err = "undeclared identifier: '" & name & "'" & extra
if c.recursiveDep.len > 0:
err.add "\nThis might be caused by a recursive module dependency:\n"
err.add c.recursiveDep
# prevent excessive errors for 'nim check'
c.recursiveDep = ""
var err: string
if name == "_":
err = "the special identifier '_' is ignored in declarations and cannot be used"
else:
err = "undeclared identifier: '" & name & "'" & extra
if c.recursiveDep.len > 0:
err.add "\nThis might be caused by a recursive module dependency:\n"
err.add c.recursiveDep
# prevent excessive errors for 'nim check'
c.recursiveDep = ""
localError(c.config, info, errGenerated, err)

proc errorUndeclaredIdentifierHint*(c: PContext; n: PNode, ident: PIdent): PSym =
Expand Down
13 changes: 8 additions & 5 deletions compiler/semtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,10 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,

for j in 0..<a.len-2:
var arg = newSymG(skParam, if a[j].kind == nkPragmaExpr: a[j][0] else: a[j], c)
if arg.name.s == "_":
arg.flags.incl(sfGenSym)
elif containsOrIncl(check, arg.name.id):
localError(c.config, a[j].info, "attempt to redefine: '" & arg.name.s & "'")
if a[j].kind == nkPragmaExpr:
pragma(c, arg, a[j][1], paramPragmas)
if not hasType and not hasDefault and kind notin {skTemplate, skMacro}:
Expand All @@ -1367,19 +1371,18 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
else:
localError(c.config, a.info, "parameter '$1' requires a type" % arg.name.s)
typ = errorType(c)
var nameForLift = arg.name.s
if sfGenSym in arg.flags:
nameForLift.add("`gensym" & $arg.id)
let lifted = liftParamType(c, kind, genericParams, typ,
arg.name.s, arg.info)
nameForLift, arg.info)
let finalType = if lifted != nil: lifted else: typ.skipIntLit(c.idgen)
arg.typ = finalType
arg.position = counter
arg.constraint = constraint
inc(counter)
if def != nil and def.kind != nkEmpty:
arg.ast = copyTree(def)
if arg.name.s == "_":
arg.flags.incl(sfGenSym)
elif containsOrIncl(check, arg.name.id):
localError(c.config, a[j].info, "attempt to redefine: '" & arg.name.s & "'")
result.n.add newSymNode(arg)
rawAddSon(result, finalType)
addParamOrResult(c, arg, kind)
Expand Down
13 changes: 13 additions & 0 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -3076,6 +3076,19 @@ when they are declared. The only exception to this is if the `{.importc.}`
pragma (or any of the other `importX` pragmas) is applied, in this case the
value is expected to come from native code, typically a C/C++ `const`.

Special identifier `_` (underscore)
-----------------------------------

The identifier `_` has a special meaning in declarations.
Any definition with the name `_` will not be added to scope, meaning the
definition is evaluated, but cannot be used. As a result the name `_` can be
indefinitely redefined.

```nim
let _ = 123
echo _ # error
let _ = 456 # compiles
```

Tuple unpacking
---------------
Expand Down
13 changes: 13 additions & 0 deletions tests/proc/tunderscoreparam.nim
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ proc test() =
proc foo(_: int) =
let a = _
doAssert not compiles(main())

block: # generic params
doAssert not (compiles do:
proc foo[_](t: typedesc[_]): seq[_] = @[default(_)]
doAssert foo[int]() == 0)

block:
proc foo[_, _](): int = 123
doAssert foo[int, bool]() == 123

block:
proc foo[T; U](_: typedesc[T]; _: typedesc[U]): (T, U) = (default(T), default(U))
doAssert foo(int, bool) == (0, false)

proc closureTest() =
var x = 0
Expand Down
4 changes: 2 additions & 2 deletions tests/stmt/tforloop_tuple_multiple_underscore.nim
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
discard """
errormsg: "undeclared identifier: '_'"
errormsg: "the special identifier '_' is ignored in declarations and cannot be used"
"""

iterator iter(): (int, int, int) =
yield (1, 1, 2)


for (_, i, _) in iter():
echo _
echo _
2 changes: 1 addition & 1 deletion tests/stmt/tforloop_tuple_underscore.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
discard """
errormsg: "undeclared identifier: '_'"
errormsg: "the special identifier '_' is ignored in declarations and cannot be used"
"""

iterator iter(): (int, int) =
Expand Down
2 changes: 1 addition & 1 deletion tests/stmt/tforloop_underscore.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
discard """
errormsg: "undeclared identifier: '_'"
errormsg: "the special identifier '_' is ignored in declarations and cannot be used"
"""

for _ in ["a"]:
Expand Down
15 changes: 15 additions & 0 deletions tests/stmt/tmiscunderscore.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import std/assertions

block:
proc _() = echo "one"
doAssert not compiles(_())
proc _() = echo "two"
doAssert not compiles(_())

block:
type _ = int
doAssert not (compiles do:
let x: _ = 3)
type _ = float
doAssert not (compiles do:
let x: _ = 3)
2 changes: 1 addition & 1 deletion tests/template/tunderscore1.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
discard """
errormsg: "undeclared identifier: '_'"
errormsg: "the special identifier '_' is ignored in declarations and cannot be used"
"""

# issue #12094, #13804
Expand Down

0 comments on commit 7585f62

Please sign in to comment.