Skip to content
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

tail args experiment #24555

Draft
wants to merge 3 commits into
base: devel
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2764,18 +2764,31 @@ template isVarargsUntyped(x): untyped =
template isVarargsTyped(x): untyped =
x.kind == tyVarargs and x[0].kind == tyTyped

proc findFirstArgBlock(m: var TCandidate, n: PNode): int =
proc findFirstTailArg(m: var TCandidate, n: PNode): int =
# see https://github.com/nim-lang/RFCs/issues/405
result = int.high
for a2 in countdown(n.len-1, 0):
# checking `nfBlockArg in n[a2].flags` wouldn't work inside templates
if n[a2].kind != nkStmtList: break
let formalLast = m.callee.n[m.callee.n.len - (n.len - a2)]
var arg = n.len - 1
template checkArg() =
# get last i'th arg of m.callee.n
let formalLast = m.callee.n[m.callee.len - (n.len - arg)]
# parameter has to occupy space (no default value, not void or varargs)
if formalLast.kind == nkSym and formalLast.sym.ast == nil and
formalLast.sym.typ.kind notin {tyVoid, tyVarargs}:
result = a2
else: break
result = arg
else:
return
if n[arg].kind in routineDefs + {nkVarSection, nkLetSection, nkConstSection, nkTypeDef}:
# definition that supports macro pragma, consider tail arg
# proc types and non-`do` lambdas excluded
checkArg()
dec arg
const postExprBlocks = {nkStmtList,
nkOfBranch, nkElifBranch, nkElse,
nkExceptBranch, nkFinally, nkDo}
while arg >= 0 and n[arg].kind in postExprBlocks:
# parameter has to occupy space (no default value, not void or varargs)
checkArg()
dec arg

proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) =

Expand Down Expand Up @@ -2817,7 +2830,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
formalLen = m.callee.n.len
formal = if formalLen > 1: m.callee.n[1].sym else: nil # current routine parameter
container: PNode = nil # constructed container
let firstArgBlock = findFirstArgBlock(m, n)
let firstTailArg = findFirstTailArg(m, n)
while a < n.len:
c.openShadowScope

Expand Down Expand Up @@ -2919,8 +2932,21 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
if m.callee.n[f].kind != nkSym:
internalError(c.config, n[a].info, "matches")
noMatch()
if flexibleOptionalParams in c.features and a >= firstArgBlock:
if flexibleOptionalParams in c.features and a >= firstTailArg:
# this is a post-expr block, matched to the tail of the routine params
let prevPos = f
f = max(f, m.callee.n.len - (n.len - a))
if f > prevPos:
# check that every previous required parameter is given
# fail the match early if not, to prevent semchecking for untyped args
for i in prevPos ..< f:
let prevFormal = m.callee.n[i].sym
if prevFormal.ast == nil and prevFormal.typ.kind notin {tyVoid, tyVarargs}:
# param is required but wasn't given
m.state = csNoMatch
m.firstMismatch.kind = kMissingParam
m.firstMismatch.formal = prevFormal
noMatch()
formal = m.callee.n[f].sym
m.firstMismatch.kind = kTypeMismatch
if containsOrIncl(marker, formal.position) and container.isNil:
Expand Down
1 change: 1 addition & 0 deletions config/config.nims
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ when defined(nimStrictMode):

switch("define", "nimVersion:" & NimVersion) # deadcode
switch("experimental", "strictDefs")
switch("experimental", "flexibleOptionalParams")
3 changes: 3 additions & 0 deletions tests/template/moverloadedblockparam.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
template fun2*(a: bool, body: untyped): untyped = discard
template fun2*(a: int, body: untyped): untyped = discard
template fun2*(body: untyped): untyped = discard
90 changes: 90 additions & 0 deletions tests/template/toverloadedblockparam.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{.experimental: "flexibleOptionalParams".}

block: # adapted tests from PR #18618 for RFC 402, covers issue #19556
template fails(body: untyped) =
doAssert not compiles(body)
static: doAssert not compiles(body)
block: # test basic template overload with untyped
template t1(x: int, body: untyped) =
block:
var v {.inject.} = x
body

template t1(body: untyped) = t1(1, body)

var outputs: seq[string]
t1: outputs.add($v)
t1(2): outputs.add($v)
t1: outputs.add("hello" & $v)
fails: t1("hello", 10)
fails: t1()
fails: t1(1,2,3)
doAssert outputs == @["1", "2", "hello1"]

block: # test template with varargs combine untyped
template t1(x: int, vs: varargs[string], body: untyped) =
block:
var v {.inject.} = x + vs.len
body

template t1(body: untyped) = t1(1, "hello", body)

var outputs: seq[string]
t1: outputs.add($v)
t1(2, "hello", "hello 2"): outputs.add($v)
fails:
t1(2, 3): discard v
fails:
t1("hello", "world"): discard v
doAssert outputs == @["2", "4"]

block: # test template with named parameter combine untyped
template t1(x: int, y = 4, body: untyped) =
block:
var v {.inject.} = x + y
body

template t1(body: untyped) = t1(1, 3, body)

t1: discard v
t1(x = 1, 3): discard v
t1(2): discard v

block: # multiple overloads, block version of issue #14827
template fun(a: bool, body: untyped): untyped = discard
template fun(a: int, body: untyped): untyped = discard
template fun(body: untyped): untyped = discard
fun(true): nonexistant # ok
fun(1): nonexistant # ok
fun: nonexistant # Error: undeclared identifier: 'nonexistant'
template varargsUntypedRedirection(x: varargs[untyped]) =
fun(x)
varargsUntypedRedirection(true): nonexistant
varargsUntypedRedirection(1): nonexistant
varargsUntypedRedirection: nonexistant

block: # issue #20274, pragma macros
macro a(path: string, fn: untyped): untyped =
result = fn
macro a(fn: untyped): untyped =
result = fn
proc b() {.a: "abc".} = discard
proc c() {.a.} = discard

import moverloadedblockparam

block:
fun2(true): nonexistant # ok
fun2(1): nonexistant # ok
fun2: nonexistant # Error: undeclared identifier: 'nonexistant'

block:
template fun2(body: untyped): int = 123
fun2(true): nonexistant # ok
fun2(1): nonexistant # ok
discard (fun2 do: nonexistant) # Error: undeclared identifier: 'nonexistant'
template fun2(a: bool, body: untyped): untyped = discard
template fun2(a: int, body: untyped): untyped = discard
fun2(true): nonexistant # ok
fun2(1): nonexistant # ok
discard (fun2 do: nonexistant)