-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fixes #8577, #8580, other bug fixes #8584
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,22 @@ import macros | |
when not defined(nimhygiene): | ||
{.pragma: dirty.} | ||
|
||
|
||
macro evalOnce(v, exp: untyped, letAssigneable: static[bool]): untyped = | ||
expectKind(v, nnkIdent) | ||
var val = exp | ||
|
||
result = newStmtList() | ||
# If `exp` is not a symbol we evaluate it once here and then use the temporary | ||
# symbol as alias | ||
if exp.kind != nnkSym and letAssigneable: | ||
val = genSym() | ||
result.add(newLetStmt(val, exp)) | ||
|
||
result.add( | ||
newProc(name = genSym(nskTemplate, $v), params = [getType(untyped)], | ||
body = val, procType = nnkTemplateDef)) | ||
|
||
proc concat*[T](seqs: varargs[seq[T]]): seq[T] = | ||
## Takes several sequences' items and returns them inside a new sequence. | ||
## | ||
|
@@ -635,30 +651,7 @@ template mapIt*(s, typ, op: untyped): untyped = | |
result.add(op) | ||
result | ||
|
||
# This is needed in order not to break the bootstrap, the fallback | ||
# implementation is a "dumb" let that won't work in some cases (eg. when `exp` | ||
# is an openArray) | ||
when declared(macros.symKind): | ||
macro evalOnce(v, exp: untyped): untyped = | ||
expectKind(v, nnkIdent) | ||
var val = exp | ||
|
||
result = newStmtList() | ||
|
||
# Not a parameter we can pass as-is, evaluate and store in a temporary | ||
# variable | ||
if exp.kind != nnkSym or exp.symKind != nskParam: | ||
val = genSym() | ||
result.add(newLetStmt(val, exp)) | ||
|
||
result.add( | ||
newProc(name = genSym(nskTemplate, $v), params = [getType(untyped)], | ||
body = val, procType = nnkTemplateDef)) | ||
else: | ||
macro evalOnce(v, exp: untyped): untyped = | ||
result = newLetStmt(v, exp) | ||
|
||
template mapIt*(s, op: untyped): untyped = | ||
template mapIt*(s: typed, op: untyped): untyped = | ||
## Convenience template around the ``map`` proc to reduce typing. | ||
## | ||
## The template injects the ``it`` variable which you can use directly in an | ||
|
@@ -675,19 +668,27 @@ template mapIt*(s, op: untyped): untyped = | |
block: | ||
var it{.inject.}: type(items(s)); | ||
op)) | ||
var result: seq[outType] | ||
when compiles(s.len): | ||
evalOnce(t, s) | ||
var i = 0 | ||
result = newSeq[outType](t.len) | ||
for it {.inject.} in t: | ||
result[i] = op | ||
i += 1 | ||
block: # using a block avoids https://github.com/nim-lang/Nim/issues/8580 | ||
|
||
# avoid double substitution of `s`. | ||
# NOTE: if we used `type(s) isnot openArray` instead, the openarray | ||
# literal would be substituted twice | ||
# BUG: `evalOnce(t, s, false)` would lead to C compile errors | ||
# (`error: use of undeclared identifier`) instead of Nim compile errors | ||
evalOnce(t, s, compiles((let _ = s))) | ||
|
||
var i = 0 | ||
var result = newSeq[outType](t.len) | ||
for it {.inject.} in t: | ||
result[i] = op | ||
i += 1 | ||
result | ||
else: | ||
result = @[] | ||
var result: seq[outType] = @[] | ||
for it {.inject.} in s: | ||
result.add(op) | ||
result | ||
result | ||
|
||
template applyIt*(varSeq, op: untyped) = | ||
## Convenience template around the mutable ``apply`` proc to reduce typing. | ||
|
@@ -1067,9 +1068,30 @@ when isMainModule: | |
doAssert mapLiterals(([1], ("abc"), 2), `$`, nested=true) == (["1"], "abc", "2") | ||
|
||
block: # mapIt with openArray | ||
when declared(macros.symKind): | ||
proc foo(x: openArray[int]): seq[int] = x.mapIt(it + 1) | ||
doAssert foo([1,2,3]) == @[2,3,4] | ||
proc foo(x: openArray[int]): seq[int] = x.mapIt(it + 1) | ||
doAssert foo([1,2,3]) == @[2,3,4] | ||
|
||
block: # mapIt with direct openArray | ||
# see https://github.com/_render_node/MDExOlB1bGxSZXF1ZXN0MjA3MTQ3MTIw/pull_requests/timeline#issuecomment-411614711 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah... this URL doesn't load. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed it |
||
doAssert openArray[int]([1,2]).mapIt(it) == @[1,2] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A better test would be to create a Frankly, I'm surprised this works. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i already have a test for that, see "mapIt with openArray" |
||
|
||
block: # mapIt test, see https://github.com/nim-lang/Nim/pull/8584#pullrequestreview-144723468 | ||
# NOTE: `[].mapIt(it)` is illegal, just as `let a = @[]` is | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about mapIt([], it)
mapIt(@[], it) ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a test |
||
doAssert newSeq[int](0).mapIt(it) == @[] | ||
|
||
block: # mapIt redifinition check, see https://github.com/nim-lang/Nim/issues/8580 | ||
let t = [1,2].mapIt(it) | ||
doAssert t == @[1,2] | ||
|
||
block: | ||
var counter = 0 | ||
proc getInput():auto = | ||
counter.inc | ||
[1, 2] | ||
doAssert getInput().mapIt(it*2).mapIt(it*10) == @[20, 40] | ||
# make sure argument evaluated only once, analog to | ||
# https://github.com/nim-lang/Nim/issues/7187 test case | ||
doAssert counter == 1 | ||
|
||
block: # mapIt with invalid RHS for `let` (#8566) | ||
type X = enum | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t
,s
I know that you didn't introduce these names but I really despise single character variable names. In reviewing this PR I had to go back and forth between various templates to work out what
t
is meant to be here.Perhaps they could be changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note: this
evalOnce
macro feels like a total hack to me.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
compiles
check should be moved above in the enclosingif
, you don't need the third parameter.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
almost procs in this module use single char arg names so I don't wanna special case mapIt, feel free to change that in a subsequent PR.
change s to s2 to make it obvious it relates to s; also improved var names of evalOnce and added a bunch of documentation.
feel free to improve that in a subsequent PR. It's there for a reason (avoid double evaluation bugs, handle openArray and other edge cases) and neither @LemonBoy nor I could come up with something better