summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelogs/changelog_2_0_0.md34
-rw-r--r--compiler/parser.nim7
-rw-r--r--compiler/semexprs.nim1
-rw-r--r--compiler/semtypes.nim30
-rw-r--r--compiler/sigmatch.nim19
-rw-r--r--compiler/typesrenderer.nim3
-rw-r--r--doc/astspec.txt36
-rw-r--r--doc/manual.md13
-rw-r--r--lib/pure/hashes.nim2
-rw-r--r--lib/system.nim51
-rw-r--r--tests/typerel/tproctypeclass.nim76
-rw-r--r--tests/typerel/ttynilinstantiation.nim7
12 files changed, 222 insertions, 57 deletions
diff --git a/changelogs/changelog_2_0_0.md b/changelogs/changelog_2_0_0.md
index 60b380d12..bef5f6073 100644
--- a/changelogs/changelog_2_0_0.md
+++ b/changelogs/changelog_2_0_0.md
@@ -175,6 +175,40 @@
 
 - - Added the `--legacy:verboseTypeMismatch` switch to get legacy type mismatch error messages.
 
+- The `proc` and `iterator` type classes now respectively only match
+  procs and iterators. Previously both type classes matched any of
+  procs or iterators.
+
+  ```nim
+  proc prc(): int =
+    123
+
+  iterator iter(): int =
+    yield 123
+  
+  proc takesProc[T: proc](x: T) = discard
+  proc takesIter[T: iterator](x: T) = discard
+
+  # always compiled:
+  takesProc(prc)
+  takesIter(iter)
+  # no longer compiles:
+  takesProc(iter)
+  takesIter(prc)
+  ```
+
+- The `proc` and `iterator` type classes now accept a calling convention pragma
+  (i.e. `proc {.closure.}`) that must be shared by matching proc or iterator
+  types. Previously pragmas were parsed but discarded if no parameter list
+  was given.
+
+  This is represented in the AST by an `nnkProcTy`/`nnkIteratorTy` node with
+  an `nnkEmpty` node in the place of the `nnkFormalParams` node, and the pragma
+  node in the same place as in a concrete `proc` or `iterator` type node. This
+  state of the AST may be unexpected to existing code, both due to the
+  replacement of the `nnkFormalParams` node as well as having child nodes
+  unlike other type class AST.
+
 ## Standard library additions and changes
 
 [//]: # "Changes:"
diff --git a/compiler/parser.nim b/compiler/parser.nim
index e921bf1f4..c62cb0d8e 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -1204,8 +1204,11 @@ proc parseProcExpr(p: var Parser; isExpr: bool; kind: TNodeKind): PNode =
     result[bodyPos] = parseStmt(p)
   else:
     result = newNodeI(if kind == nkIteratorDef: nkIteratorTy else: nkProcTy, info)
-    if hasSignature:
-      result.add(params)
+    if hasSignature or pragmas.kind != nkEmpty:
+      if hasSignature:
+        result.add(params)
+      else: # pragmas but no param list, implies typeclass with pragmas
+        result.add(p.emptyNode)
       if kind == nkFuncDef:
         parMessage(p, "func keyword is not allowed in type descriptions, use proc with {.noSideEffect.} pragma instead")
       result.add(pragmas)
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 7ba4099d3..a765a92b9 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -469,6 +469,7 @@ proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode =
       res = t.kind == tyProc and
             t.callConv == ccClosure
     of "iterator":
+      # holdover from when `is iterator` didn't work
       let t = skipTypes(t1, abstractRange)
       res = t.kind == tyProc and
             t.callConv == ccClosure and
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index f2846377c..d86f5ddb2 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -2054,25 +2054,27 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   of nkOutTy: result = semVarOutType(c, n, prev, {tfIsOutParam})
   of nkDistinctTy: result = semDistinct(c, n, prev)
   of nkStaticTy: result = semStaticType(c, n[0], prev)
-  of nkIteratorTy:
-    if n.len == 0:
+  of nkProcTy, nkIteratorTy:
+    if n.len == 0 or n[0].kind == nkEmpty:
+      # 0 length or empty param list with possible pragmas imply typeclass
       result = newTypeS(tyBuiltInTypeClass, c)
       let child = newTypeS(tyProc, c)
-      child.flags.incl tfIterator
+      var symKind: TSymKind
+      if n.kind == nkIteratorTy:
+        child.flags.incl tfIterator
+      if n.len > 0 and n[1].kind != nkEmpty and n[1].len > 0:
+        # typeclass with pragma
+        let symKind = if n.kind == nkIteratorTy: skIterator else: skProc
+        # dummy symbol for `pragma`:
+        var s = newSymS(symKind, newIdentNode(getIdent(c.cache, "dummy"), n.info), c)
+        s.typ = child
+        # for now only call convention pragmas supported in proc typeclass
+        pragma(c, s, n[1], {FirstCallConv..LastCallConv})
       result.addSonSkipIntLit(child, c.idgen)
     else:
-      result = semProcTypeWithScope(c, n, prev, skIterator)
-      if result.kind == tyProc:
-        result.flags.incl(tfIterator)
-        if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline):
-          result.callConv = ccInline
-        else:
-          result.callConv = ccClosure
-  of nkProcTy:
-    if n.len == 0:
-      result = newConstraint(c, tyProc)
-    else:
       result = semProcTypeWithScope(c, n, prev, skProc)
+      if n.kind == nkIteratorTy and result.kind == tyProc:
+        result.flags.incl(tfIterator)
   of nkEnumTy: result = semEnum(c, n, prev)
   of nkType: result = n.typ
   of nkStmtListType: result = semStmtListType(c, n, prev)
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index 0c6456e68..92971b072 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -624,6 +624,9 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
     if f.len != a.len: return
     result = isEqual      # start with maximum; also correct for no
                           # params at all
+    
+    if f.flags * {tfIterator} != a.flags * {tfIterator}:
+      return isNone
 
     template checkParam(f, a) =
       result = minRel(result, procParamTypeRel(c, f, a))
@@ -1636,13 +1639,19 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
 
   of tyBuiltInTypeClass:
     considerPreviousT:
-      let targetKind = f[0].kind
+      let target = f[0]
+      let targetKind = target.kind
       let effectiveArgType = a.skipTypes({tyRange, tyGenericInst,
                                           tyBuiltInTypeClass, tyAlias, tySink, tyOwned})
-      let typeClassMatches = targetKind == effectiveArgType.kind and
-                             not effectiveArgType.isEmptyContainer
-      if typeClassMatches or
-        (targetKind in {tyProc, tyPointer} and effectiveArgType.kind == tyNil):
+      if targetKind == effectiveArgType.kind:
+        if effectiveArgType.isEmptyContainer:
+          return isNone
+        if targetKind == tyProc:
+          if target.flags * {tfIterator} != effectiveArgType.flags * {tfIterator}:
+            return isNone
+          if tfExplicitCallConv in target.flags and
+              target.callConv != effectiveArgType.callConv:
+            return isNone
         put(c, f, a)
         return isGeneric
       else:
diff --git a/compiler/typesrenderer.nim b/compiler/typesrenderer.nim
index f59f63659..b5bceccc2 100644
--- a/compiler/typesrenderer.nim
+++ b/compiler/typesrenderer.nim
@@ -64,9 +64,8 @@ proc renderType(n: PNode, toNormalize: bool): string =
       result = "ptr"
   of nkProcTy:
     assert n.len != 1
-    if n.len > 1:
+    if n.len > 1 and n[0].kind == nkFormalParams:
       let params = n[0]
-      assert params.kind == nkFormalParams
       assert params.len > 0
       result = "proc("
       for i in 1..<params.len: result.add(renderType(params[i], toNormalize) & ',')
diff --git a/doc/astspec.txt b/doc/astspec.txt
index bfaec7155..9929d8ccd 100644
--- a/doc/astspec.txt
+++ b/doc/astspec.txt
@@ -1348,7 +1348,7 @@ Generic parameters are treated in the type, not the ``proc`` itself.
 Concrete syntax:
 
   ```nim
-  type MyProc[T] = proc(x: T)
+  type MyProc[T] = proc(x: T) {.nimcall.}
   ```
 
 AST:
@@ -1363,7 +1363,8 @@ AST:
     nnkProcTy( # behaves like a procedure declaration from here on
       nnkFormalParams(
         # ...
-      )
+      ),
+      nnkPragma(nnkIdent("nimcall"))
     )
   )
   ```
@@ -1371,6 +1372,37 @@ AST:
 The same syntax applies to ``iterator`` (with ``nnkIteratorTy``), but
 *does not* apply to ``converter`` or ``template``.
 
+Type class versions of these nodes generally share the same node kind but
+without any child nodes. The ``tuple`` type class is represented by
+``nnkTupleClassTy``, while a ``proc`` or ``iterator`` type class with pragmas
+has an ``nnkEmpty`` node in place of the ``nnkFormalParams`` node of a
+concrete ``proc`` or ``iterator`` type node.
+
+  ```nim
+  type TypeClass = proc {.nimcall.} | ref | tuple
+  ```
+
+AST:
+
+  ```nim
+  nnkTypeDef(
+    nnkIdent("TypeClass"),
+    nnkEmpty(),
+    nnkInfix(
+      nnkIdent("|"),
+      nnkProcTy(
+        nnkEmpty(),
+        nnkPragma(nnkIdent("nimcall"))
+      ),
+      nnkInfix(
+        nnkIdent("|"),
+        nnkRefTy(),
+        nnkTupleClassTy()
+      )
+    )
+  )
+  ```
+
 Mixin statement
 ---------------
 
diff --git a/doc/manual.md b/doc/manual.md
index 042773bcb..f5ee10fda 100644
--- a/doc/manual.md
+++ b/doc/manual.md
@@ -5467,9 +5467,9 @@ type class           matches
 ==================   ===================================================
 `object`             any object type
 `tuple`              any tuple type
-
 `enum`               any enumeration
 `proc`               any proc type
+`iterator`           any iterator type
 `ref`                any `ref` type
 `ptr`                any `ptr` type
 `var`                any `var` type
@@ -5529,6 +5529,17 @@ as `type constraints`:idx: of the generic type parameter:
   onlyIntOrString("xy", 50) # invalid as 'T' cannot be both at the same time
   ```
 
+`proc` and `iterator` type classes also accept a calling convention pragma
+to restrict the calling convention of the matching `proc` or `iterator` type.
+
+  ```nim
+  proc onlyClosure[T: proc {.closure.}](x: T) = discard
+
+  onlyClosure(proc() = echo "hello") # valid
+  proc foo() {.nimcall.} = discard
+  onlyClosure(foo) # type mismatch
+  ```
+
 
 Implicit generics
 -----------------
diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim
index 4ae4938c6..56c360138 100644
--- a/lib/pure/hashes.nim
+++ b/lib/pure/hashes.nim
@@ -501,7 +501,7 @@ proc hashIgnoreCase*(sBuf: string, sPos, ePos: int): Hash =
     h = h !& ord(c)
   result = !$h
 
-proc hash*[T: tuple | object | proc](x: T): Hash =
+proc hash*[T: tuple | object | proc | iterator {.closure.}](x: T): Hash =
   ## Efficient `hash` overload.
   runnableExamples:
     # for `tuple|object`, `hash` must be defined for each component of `x`.
diff --git a/lib/system.nim b/lib/system.nim
index 3a2487004..dcf09d604 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -62,11 +62,11 @@ proc typeof*(x: untyped; mode = typeOfIter): typedesc {.
     doAssert type(myFoo()) is string
     doAssert typeof(myFoo()) is string
     doAssert typeof(myFoo(), typeOfIter) is string
-    doAssert typeof(myFoo3) is "iterator"
+    doAssert typeof(myFoo3) is iterator
 
     doAssert typeof(myFoo(), typeOfProc) is float
     doAssert typeof(0.0, typeOfProc) is float
-    doAssert typeof(myFoo3, typeOfProc) is "iterator"
+    doAssert typeof(myFoo3, typeOfProc) is iterator
     doAssert not compiles(typeof(myFoo2(), typeOfProc))
       # this would give: Error: attempting to call routine: 'myFoo2'
       # since `typeOfProc` expects a typed expression and `myFoo2()` can
@@ -2188,39 +2188,30 @@ when notJSnotNims:
     include "system/profiler"
   {.pop.}
 
-  proc rawProc*[T: proc](x: T): pointer {.noSideEffect, inline.} =
+  proc rawProc*[T: proc {.closure.} | iterator {.closure.}](x: T): pointer {.noSideEffect, inline.} =
     ## Retrieves the raw proc pointer of the closure `x`. This is
     ## useful for interfacing closures with C/C++, hash compuations, etc.
-    when T is "closure":
-      #[
-      The conversion from function pointer to `void*` is a tricky topic, but this
-      should work at least for c++ >= c++11, e.g. for `dlsym` support.
-      refs: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57869,
-      https://stackoverflow.com/questions/14125474/casts-between-pointer-to-function-and-pointer-to-object-in-c-and-c
-      ]#
-      {.emit: """
-      `result` = (void*)`x`.ClP_0;
-      """.}
-    else:
-      {.error: "Only closure function and iterator are allowed!".}
-
-  proc rawEnv*[T: proc](x: T): pointer {.noSideEffect, inline.} =
+    #[
+    The conversion from function pointer to `void*` is a tricky topic, but this
+    should work at least for c++ >= c++11, e.g. for `dlsym` support.
+    refs: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57869,
+    https://stackoverflow.com/questions/14125474/casts-between-pointer-to-function-and-pointer-to-object-in-c-and-c
+    ]#
+    {.emit: """
+    `result` = (void*)`x`.ClP_0;
+    """.}
+
+  proc rawEnv*[T: proc {.closure.} | iterator {.closure.}](x: T): pointer {.noSideEffect, inline.} =
     ## Retrieves the raw environment pointer of the closure `x`. See also `rawProc`.
-    when T is "closure":
-      {.emit: """
-      `result` = `x`.ClE_0;
-      """.}
-    else:
-      {.error: "Only closure function and iterator are allowed!".}
+    {.emit: """
+    `result` = `x`.ClE_0;
+    """.}
 
-  proc finished*[T: proc](x: T): bool {.noSideEffect, inline, magic: "Finished".} =
+  proc finished*[T: iterator {.closure.}](x: T): bool {.noSideEffect, inline, magic: "Finished".} =
     ## It can be used to determine if a first class iterator has finished.
-    when T is "iterator":
-      {.emit: """
-      `result` = ((NI*) `x`.ClE_0)[1] < 0;
-      """.}
-    else:
-      {.error: "Only closure iterator is allowed!".}
+    {.emit: """
+    `result` = ((NI*) `x`.ClE_0)[1] < 0;
+    """.}
 
 from std/private/digitsutils import addInt
 export addInt
diff --git a/tests/typerel/tproctypeclass.nim b/tests/typerel/tproctypeclass.nim
new file mode 100644
index 000000000..4df9c558b
--- /dev/null
+++ b/tests/typerel/tproctypeclass.nim
@@ -0,0 +1,76 @@
+import std/assertions
+
+proc main =
+  iterator closureIter(): int {.closure.} =
+    yield 1
+    yield 2
+  iterator inlineIter(): int {.inline.} =
+    yield 1
+    yield 2
+  proc procNotIter(): int = 1
+
+  doAssert closureIter is iterator
+  doAssert inlineIter is iterator
+  doAssert procNotIter isnot iterator
+
+  doAssert closureIter isnot proc
+  doAssert inlineIter isnot proc
+  doAssert procNotIter is proc
+
+  doAssert typeof(closureIter) is iterator
+  doAssert typeof(inlineIter) is iterator
+  doAssert typeof(procNotIter) isnot iterator
+
+  doAssert typeof(closureIter) isnot proc
+  doAssert typeof(inlineIter) isnot proc
+  doAssert typeof(procNotIter) is proc
+
+  block:
+    proc fn1(iter: iterator {.closure.}) = discard
+    proc fn2[T: iterator {.closure.}](iter: T) = discard
+
+    fn1(closureIter)
+    fn2(closureIter)
+
+    doAssert not compiles(fn1(procNotIter))
+    doAssert not compiles(fn2(procNotIter))
+
+    doAssert not compiles(fn1(inlineIter))
+    doAssert not compiles(fn2(inlineIter))
+
+  block: # concrete iterator type
+    proc fn1(iter: iterator(): int) = discard
+    proc fn2[T: iterator(): int](iter: T) = discard
+
+    fn1(closureIter)
+    fn2(closureIter)
+
+    doAssert not compiles(fn1(procNotIter))
+    doAssert not compiles(fn2(procNotIter))
+
+    doAssert not compiles(fn1(inlineIter))
+    doAssert not compiles(fn2(inlineIter))
+
+  proc takesNimcall[T: proc {.nimcall.}](p: T) = discard
+  proc takesClosure[T: proc {.closure.}](p: T) = discard
+  proc takesAnyProc[T: proc](p: T) = discard
+
+  proc nimcallProc(): int {.nimcall.} = 1
+  proc closureProc(): int {.closure.} = 2
+
+  doAssert nimcallProc is proc {.nimcall.}
+  takesNimcall(nimcallProc)
+  doAssert closureProc isnot proc {.nimcall.}
+  doAssert not compiles(takesNimcall(closureProc))
+
+  doAssert nimcallProc isnot proc {.closure.}
+  doAssert not compiles(takesClosure(nimcallProc))
+  doAssert closureProc is proc {.closure.}
+  takesClosure(closureProc)
+  
+  doAssert nimcallProc is proc
+  takesAnyProc(nimcallProc)
+  doAssert closureProc is proc
+  takesAnyProc(closureProc)
+
+main()
diff --git a/tests/typerel/ttynilinstantiation.nim b/tests/typerel/ttynilinstantiation.nim
new file mode 100644
index 000000000..303506689
--- /dev/null
+++ b/tests/typerel/ttynilinstantiation.nim
@@ -0,0 +1,7 @@
+proc foo[T: proc](x: T) =
+  # old error here:
+  let y = x
+  # invalid type: 'typeof(nil)' for let
+
+foo(nil) #[tt.Error
+   ^ type mismatch: got <typeof(nil)>]#