From b773f1ab043d84f7248f79c0be3d6240f89c3096 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 20 Dec 2024 22:50:25 +0300 Subject: [PATCH 1/3] start with stmtlist --- compiler/sigmatch.nim | 13 ++++ tests/template/moverloadedblockparam.nim | 3 + tests/template/toverloadedblockparam.nim | 90 ++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 tests/template/moverloadedblockparam.nim create mode 100644 tests/template/toverloadedblockparam.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 01eed9df4d73e..9557efcd164d2 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2920,7 +2920,20 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int internalError(c.config, n[a].info, "matches") noMatch() if flexibleOptionalParams in c.features and a >= firstArgBlock: + # 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: diff --git a/tests/template/moverloadedblockparam.nim b/tests/template/moverloadedblockparam.nim new file mode 100644 index 0000000000000..d9e1ec2ebe992 --- /dev/null +++ b/tests/template/moverloadedblockparam.nim @@ -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 diff --git a/tests/template/toverloadedblockparam.nim b/tests/template/toverloadedblockparam.nim new file mode 100644 index 0000000000000..cd2296024ab24 --- /dev/null +++ b/tests/template/toverloadedblockparam.nim @@ -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, covers 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 + +when false: # 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) From d87aea20f147060c33a974b69f8349a6082c33f2 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 20 Dec 2024 23:16:46 +0300 Subject: [PATCH 2/3] generalize to post expr blocks and pragma macros --- compiler/sigmatch.nim | 31 +++++++++++++++++------- tests/template/toverloadedblockparam.nim | 4 +-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 9557efcd164d2..892da05824ead 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -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) = @@ -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 @@ -2919,7 +2932,7 @@ 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)) diff --git a/tests/template/toverloadedblockparam.nim b/tests/template/toverloadedblockparam.nim index cd2296024ab24..7bd4642f1219c 100644 --- a/tests/template/toverloadedblockparam.nim +++ b/tests/template/toverloadedblockparam.nim @@ -50,7 +50,7 @@ block: # adapted tests from PR #18618 for RFC 402, covers issue #19556 t1(x = 1, 3): discard v t1(2): discard v - block: # multiple overloads, covers issue #14827 + 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 @@ -63,7 +63,7 @@ block: # adapted tests from PR #18618 for RFC 402, covers issue #19556 varargsUntypedRedirection(1): nonexistant varargsUntypedRedirection: nonexistant -when false: # issue #20274, pragma macros +block: # issue #20274, pragma macros macro a(path: string, fn: untyped): untyped = result = fn macro a(fn: untyped): untyped = From aa37f43b6f6d60638655417bb064bfc8e1135b18 Mon Sep 17 00:00:00 2001 From: metagn Date: Sat, 21 Dec 2024 00:05:04 +0300 Subject: [PATCH 3/3] might as well test globally --- config/config.nims | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.nims b/config/config.nims index 45c6ec58cd3cb..eab4f0d45302f 100644 --- a/config/config.nims +++ b/config/config.nims @@ -22,3 +22,4 @@ when defined(nimStrictMode): switch("define", "nimVersion:" & NimVersion) # deadcode switch("experimental", "strictDefs") +switch("experimental", "flexibleOptionalParams")