summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md4
-rw-r--r--compiler/ccgtypes.nim5
-rw-r--r--compiler/semstmts.nim15
-rw-r--r--doc/manual/types.txt38
-rw-r--r--tests/package_level_objects/definefoo.nim3
-rw-r--r--tests/package_level_objects/mypackage.nimble0
-rw-r--r--tests/package_level_objects/tusefoo.nim16
-rw-r--r--tests/package_level_objects/tusefoo2.nim19
-rw-r--r--todo.txt4
9 files changed, 93 insertions, 11 deletions
diff --git a/changelog.md b/changelog.md
index 640cb98c5..ebd454ab7 100644
--- a/changelog.md
+++ b/changelog.md
@@ -16,3 +16,7 @@
   module.
 - The overloading rules changed slightly so that constrained generics are
   preferred over unconstrained generics. (Bug #6526)
+- It is now possible to forward declare object types so that mutually
+  recursive types can be created across module boundaries. See
+  [package level objects](https://nim-lang.org/docs/manual.html#package-level-objects)
+  for more information.
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index 76d0c0158..c5fc67fb0 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -981,7 +981,10 @@ proc genTypeInfoAux(m: BModule, typ, origType: PType, name: Rope;
   if sonsLen(typ) > 0 and typ.lastSon != nil:
     var x = typ.lastSon
     if typ.kind == tyObject: x = x.skipTypes(skipPtrs)
-    base = genTypeInfo(m, x, info)
+    if typ.kind == tyPtr and x.kind == tyObject and incompleteType(x):
+      base = rope("0")
+    else:
+      base = genTypeInfo(m, x, info)
   else:
     base = rope("0")
   genTypeInfoAuxBase(m, typ, origType, name, base, info)
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index e2ee9ac67..8ec93cacd 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -796,12 +796,6 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
         else:
           localError(name.info, typsym.name.s & " is not a type that can be forwarded")
           s = typsym
-      when false:
-        s = qualifiedLookUp(c, name, {checkUndeclared, checkModule})
-        if s.kind != skType or
-          s.typ.skipTypes(abstractPtrs).kind != tyObject or
-          tfPartial notin s.typ.skipTypes(abstractPtrs).flags:
-          localError(name.info, "only .partial objects can be extended")
     else:
       s = semIdentDef(c, name, skType)
       s.typ = newTypeS(tyForward, c)
@@ -812,11 +806,16 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
         # check if the symbol already exists:
         let pkg = c.module.owner
         if not isTopLevel(c) or pkg.isNil:
-          localError(name.info, "only top level types in a package can be 'forward'")
+          localError(name.info, "only top level types in a package can be 'package'")
         else:
           let typsym = pkg.tab.strTableGet(s.name)
           if typsym != nil:
-            typeCompleted(typsym)
+            if sfForward notin typsym.flags or sfNoForward notin typsym.flags:
+              typeCompleted(typsym)
+              typsym.info = s.info
+            else:
+              localError(name.info, "cannot complete type '" & s.name.s & "' twice; " &
+                      "previous type completion was here: " & $typsym.info)
             s = typsym
       # add it here, so that recursive types are possible:
       if sfGenSym notin s.flags: addInterfaceDecl(c, s)
diff --git a/doc/manual/types.txt b/doc/manual/types.txt
index 7cb4a8b8a..4d66bf664 100644
--- a/doc/manual/types.txt
+++ b/doc/manual/types.txt
@@ -698,6 +698,44 @@ branch switch ``system.reset`` has to be used. Also, when the fields of a
 particular branch are specified during object construction, the correct value
 for the discriminator must be supplied at compile-time.
 
+Package level objects
+---------------------
+
+Every Nim module resides in a (nimble) package. An object type can be attached
+to the package it resides in. If that is done, the type can be referenced from
+other modules as an `incomplete`:idx: object type. This features allows to
+break up recursive type dependencies accross module boundaries. Incomplete
+object types are always passed ``byref`` and can only be used in pointer like
+contexts (``var/ref/ptr IncompleteObject``) in general since the compiler does
+not yet know the size of the object. To complete an incomplete object
+the ``package`` pragma has to be used. ``package`` implies ``byref``.
+
+As long as a type ``T`` is incomplete ``sizeof(T)`` or "runtime type
+information" for ``T`` is not available.
+
+
+Example:
+
+.. code-block:: nim
+
+  # module A (in an arbitrary package)
+  type
+    Pack.SomeObject = object ## declare as incomplete object of package 'Pack'
+    Triple = object
+      a, b, c: ref SomeObject ## pointers to incomplete objects are allowed
+
+  ## Incomplete objects can be used as parameters:
+  proc myproc(x: SomeObject) = discard
+
+
+.. code-block:: nim
+
+  # module B (in package "Pack")
+  type
+    SomeObject* {.package.} = object ## Use 'package' to complete the object
+      s, t: string
+      x, y: int
+
 
 Set type
 --------
diff --git a/tests/package_level_objects/definefoo.nim b/tests/package_level_objects/definefoo.nim
new file mode 100644
index 000000000..36576ab59
--- /dev/null
+++ b/tests/package_level_objects/definefoo.nim
@@ -0,0 +1,3 @@
+type
+  Foo* {.package.} = object
+    x, y: int
diff --git a/tests/package_level_objects/mypackage.nimble b/tests/package_level_objects/mypackage.nimble
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/package_level_objects/mypackage.nimble
diff --git a/tests/package_level_objects/tusefoo.nim b/tests/package_level_objects/tusefoo.nim
new file mode 100644
index 000000000..f9bae9545
--- /dev/null
+++ b/tests/package_level_objects/tusefoo.nim
@@ -0,0 +1,16 @@
+discard """
+  output: '''@[(x: 3, y: 4)]'''
+"""
+
+type
+  mypackage.Foo = object
+  Other = proc (inp: Foo)
+
+import definefoo
+
+# after this import, Foo is a completely resolved type, so
+# we can create a sequence of it:
+var s: seq[Foo] = @[]
+
+s.add Foo(x: 3, y: 4)
+echo s
diff --git a/tests/package_level_objects/tusefoo2.nim b/tests/package_level_objects/tusefoo2.nim
new file mode 100644
index 000000000..be6b3fcda
--- /dev/null
+++ b/tests/package_level_objects/tusefoo2.nim
@@ -0,0 +1,19 @@
+discard """
+  output: '''compiles'''
+"""
+
+# Test that the object type does not need to be resolved at all:
+
+type
+  mypackage.Foo = object
+  Other = proc (inp: Foo)
+
+  Node = ref object
+    external: ptr Foo
+    data: string
+
+var x: Node
+new(x)
+x.data = "compiles"
+
+echo x.data
diff --git a/todo.txt b/todo.txt
index b0ea449f4..97e749e9e 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,8 +1,8 @@
 version 1.0 battle plan
 =======================
 
-- implement a way to forward object type declarations across module
-  boundaries; C++ style
+- make nimresolve part of the Nim compiler and add support for
+  'import staticExec()'
 - deprecate unary '<'
 - remove 'mod x' type rule
 - implement x[^1] differently, no compiler magic