summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/semtypes.nim25
-rw-r--r--tests/template/tdefaultparam.nim41
2 files changed, 59 insertions, 7 deletions
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index 9af71ba8f..113946fef 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -1372,14 +1372,25 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
           "either use ';' (semicolon) or explicitly write each default value")
         message(c.config, a.info, warnImplicitDefaultValue, msg)
       block determineType:
-        if kind == skTemplate and hasUnresolvedArgs(c, def):
-          # template default value depends on other parameter
-          # don't do any typechecking
-          def.typ = makeTypeFromExpr(c, def.copyTree)
-          break determineType
+        var canBeVoid = false
+        if kind == skTemplate:
+          if typ != nil and typ.kind == tyUntyped:
+            # don't do any typechecking or assign a type for
+            # `untyped` parameter default value
+            break determineType
+          elif hasUnresolvedArgs(c, def):
+            # template default value depends on other parameter
+            # don't do any typechecking
+            def.typ = makeTypeFromExpr(c, def.copyTree)
+            break determineType
+          elif typ != nil and typ.kind == tyTyped:
+            canBeVoid = true
         let isGeneric = isCurrentlyGeneric()
         inc c.inGenericContext, ord(isGeneric)
-        def = semExprWithType(c, def, {efDetermineType, efAllowSymChoice}, typ)
+        if canBeVoid:
+          def = semExpr(c, def, {efDetermineType, efAllowSymChoice}, typ)
+        else:
+          def = semExprWithType(c, def, {efDetermineType, efAllowSymChoice}, typ)
         dec c.inGenericContext, ord(isGeneric)
         if def.referencesAnotherParam(getCurrOwner(c)):
           def.flags.incl nfDefaultRefsParam
@@ -1399,7 +1410,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
           typ = newTypeS(tyTypeDesc, c, newTypeS(tyNone, c))
           typ.flags.incl tfCheckedForDestructor
 
-      elif def.typ.kind != tyFromExpr:
+      elif def.typ != nil and def.typ.kind != tyFromExpr: # def.typ can be void
         # if def.typ != nil and def.typ.kind != tyNone:
         # example code that triggers it:
         # proc sort[T](cmp: proc(a, b: T): int = cmp)
diff --git a/tests/template/tdefaultparam.nim b/tests/template/tdefaultparam.nim
index ecbb0e145..7ea0b2b25 100644
--- a/tests/template/tdefaultparam.nim
+++ b/tests/template/tdefaultparam.nim
@@ -13,3 +13,44 @@ block: # issue #23506
     a = $($x, $y)
   foo(1)
   doAssert a == "(\"1\", \"1\")"
+
+block: # untyped params with default value
+  macro foo(x: typed): untyped =
+    result = x
+  template test(body: untyped, alt: untyped = (;), maxTries = 3): untyped {.foo.} =
+    body
+    alt
+  var s = "a"
+  test:
+    s.add "b"
+  do:
+    s.add "c"
+  doAssert s == "abc"
+  template test2(body: untyped, alt: untyped = s.add("e"), maxTries = 3): untyped =
+    body
+    alt
+  test2:
+    s.add "d"
+  doAssert s == "abcde"
+  template test3(body: untyped = willNotCompile) =
+    discard
+  test3()
+
+block: # typed params with `void` default value
+  macro foo(x: typed): untyped =
+    result = x
+  template test(body: untyped, alt: typed = (;), maxTries = 3): untyped {.foo.} =
+    body
+    alt
+  var s = "a"
+  test:
+    s.add "b"
+  do:
+    s.add "c"
+  doAssert s == "abc"
+  template test2(body: untyped, alt: typed = s.add("e"), maxTries = 3): untyped =
+    body
+    alt
+  test2:
+    s.add "d"
+  doAssert s == "abcde"