summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md1
-rw-r--r--compiler/ccgtypes.nim20
-rw-r--r--compiler/cgen.nim14
-rw-r--r--compiler/cgmeth.nim23
-rw-r--r--compiler/ic/cbackend.nim7
-rw-r--r--compiler/ic/ic.nim12
-rw-r--r--compiler/ic/integrity.nim4
-rw-r--r--compiler/ic/replayer.nim15
-rw-r--r--compiler/ic/rodfiles.nim4
-rw-r--r--compiler/jsgen.nim5
-rw-r--r--compiler/modulegraphs.nim40
-rw-r--r--compiler/nim.cfg1
-rw-r--r--compiler/pipelines.nim15
-rw-r--r--compiler/sem.nim10
-rw-r--r--compiler/sempass2.nim64
-rw-r--r--compiler/vtables.nim167
-rw-r--r--config/config.nims1
-rw-r--r--lib/system.nim2
-rw-r--r--lib/system/arc.nim5
-rw-r--r--tests/config.nims1
-rw-r--r--tests/generics/tobjecttyperel.nim1
-rw-r--r--tests/method/nim.cfg1
-rw-r--r--tests/method/tcompilegenerics.nim24
-rw-r--r--tests/method/tgeneric_methods.nim84
-rw-r--r--tests/method/tmethod_various.nim7
-rw-r--r--tests/method/tmethods_old.nim12
-rw-r--r--tests/method/tmultim.nim193
-rw-r--r--tests/method/tvtable.nim19
28 files changed, 555 insertions, 197 deletions
diff --git a/changelog.md b/changelog.md
index ab121f5a1..fb70a9a6b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -5,6 +5,7 @@
 
 - `-d:nimStrictDelete` becomes the default. An index error is produced when the index passed to `system.delete` was out of bounds. Use `-d:nimAuditDelete` to mimic the old behavior for backwards compatibility.
 - The default user-agent in `std/httpclient` has been changed to `Nim-httpclient/<version>` instead of `Nim httpclient/<version>` which was incorrect according to the HTTP spec.
+- Methods now support implementations based on vtable by using `-d:nimPreviewVtables`. methods are confined in the same module where the type has been defined.
 - With `-d:nimPreviewNonVarDestructor`, non-var destructors become the default.
 
 ## Standard library additions and changes
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index 855ec4cd9..2f304852b 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -13,6 +13,7 @@
 
 import sighashes, modulegraphs, std/strscans
 import ../dist/checksums/src/checksums/md5
+import std/sequtils
 
 type
   TypeDescKind = enum
@@ -1718,6 +1719,13 @@ proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLin
   if t.kind == tyObject and t.len > 0 and t[0] != nil and optEnableDeepCopy in m.config.globalOptions:
     discard genTypeInfoV1(m, t, info)
 
+proc genVTable(seqs: seq[PSym]): string =
+  result = "{"
+  for i in 0..<seqs.len:
+    if i > 0: result.add ", "
+    result.add "(void *) " & seqs[i].loc.r
+  result.add "}"
+
 proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineInfo) =
   cgsym(m, "TNimTypeV2")
   m.s[cfsStrData].addf("N_LIB_PRIVATE TNimTypeV2 $1;$n", [name])
@@ -1754,8 +1762,16 @@ proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineIn
   add(typeEntry, ", .traceImpl = (void*)")
   genHook(m, t, info, attachedTrace, typeEntry)
 
-  addf(typeEntry, ", .flags = $1};$n", [rope(flags)])
-  m.s[cfsVars].add typeEntry
+  let dispatchMethods = toSeq(getMethodsPerType(m.g.graph, t))
+  if dispatchMethods.len > 0:
+    addf(typeEntry, ", .flags = $1", [rope(flags)])
+    for i in dispatchMethods:
+      genProcPrototype(m, i)
+    addf(typeEntry, ", .vTable = $1};$n", [genVTable(dispatchMethods)])
+    m.s[cfsVars].add typeEntry
+  else:
+    addf(typeEntry, ", .flags = $1};$n", [rope(flags)])
+    m.s[cfsVars].add typeEntry
 
   if t.kind == tyObject and t.len > 0 and t[0] != nil and optEnableDeepCopy in m.config.globalOptions:
     discard genTypeInfoV1(m, t, info)
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 0011cb90e..5a331ae7c 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -366,6 +366,8 @@ proc dataField(p: BProc): Rope =
   else:
     result = rope"->data"
 
+proc genProcPrototype(m: BModule, sym: PSym)
+
 include ccgliterals
 include ccgtypes
 
@@ -734,7 +736,7 @@ proc genVarPrototype(m: BModule, n: PNode)
 proc requestConstImpl(p: BProc, sym: PSym)
 proc genStmts(p: BProc, t: PNode)
 proc expr(p: BProc, n: PNode, d: var TLoc)
-proc genProcPrototype(m: BModule, sym: PSym)
+
 proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc)
 proc intLiteral(i: BiggestInt; result: var Rope)
 proc genLiteral(p: BProc, n: PNode; result: var Rope)
@@ -2181,9 +2183,8 @@ proc updateCachedModule(m: BModule) =
   cf.flags = {CfileFlag.Cached}
   addFileToCompile(m.config, cf)
 
-proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode): PNode =
+proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
   ## Also called from IC.
-  result = nil
   if sfMainModule in m.module.flags:
     # phase ordering problem here: We need to announce this
     # dependency to 'nimTestErrorFlag' before system.c has been written to disk.
@@ -2231,7 +2232,12 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode): PNode =
 
       if m.g.forwardedProcs.len == 0:
         incl m.flags, objHasKidsValid
-      result = generateMethodDispatchers(graph, m.idgen)
+      if optMultiMethods in m.g.config.globalOptions or
+          m.g.config.selectedGC notin {gcArc, gcOrc, gcAtomicArc} or
+          not m.g.config.isDefined("nimPreviewVtables") or
+          m.g.config.backend == backendCpp or sfCompileToCpp in m.module.flags:
+        generateIfMethodDispatchers(graph, m.idgen)
+
 
   let mm = m
   m.g.modulesClosed.add mm
diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim
index 2cd5f3672..3bbe7b3f4 100644
--- a/compiler/cgmeth.nim
+++ b/compiler/cgmeth.nim
@@ -11,14 +11,14 @@
 
 import
   options, ast, msgs, idents, renderer, types, magicsys,
-  sempass2, modulegraphs, lineinfos
-
+  sempass2, modulegraphs, lineinfos, astalgo
 
 import std/intsets
 
 when defined(nimPreviewSlimSystem):
   import std/assertions
 
+import std/[tables]
 
 proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
   var dest = skipTypes(d, abstractPtrs)
@@ -157,6 +157,9 @@ proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
 
 proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
   var witness: PSym = nil
+  if s.typ[1].owner.getModule != s.getModule and g.config.isDefined("nimPreviewVtables"):
+    localError(g.config, s.info, errGenerated, "method `" & s.name.s &
+          "` can be defined only in the same module with its type (" & s.typ[1].typeToString() & ")")
   for i in 0..<g.methods.len:
     let disp = g.methods[i].dispatcher
     case sameMethodBucket(disp, s, multimethods = optMultiMethods in g.config.globalOptions)
@@ -175,6 +178,11 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
     of Invalid:
       if witness.isNil: witness = g.methods[i].methods[0]
   # create a new dispatcher:
+  # stores the id and the position
+  if s.typ[1].skipTypes(skipPtrs).itemId notin g.bucketTable:
+    g.bucketTable[s.typ[1].skipTypes(skipPtrs).itemId] = 1
+  else:
+    g.bucketTable.inc(s.typ[1].skipTypes(skipPtrs).itemId)
   g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen)))
   #echo "adding ", s.info
   if witness != nil:
@@ -183,7 +191,7 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
   elif sfBase notin s.flags:
     message(g.config, s.info, warnUseBase)
 
-proc relevantCol(methods: seq[PSym], col: int): bool =
+proc relevantCol*(methods: seq[PSym], col: int): bool =
   # returns true iff the position is relevant
   result = false
   var t = methods[0].typ[col].skipTypes(skipPtrs)
@@ -203,7 +211,7 @@ proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
       if (d != high(int)) and d != 0:
         return d
 
-proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
+proc sortBucket*(a: var seq[PSym], relevantCols: IntSet) =
   # we use shellsort here; fast and simple
   var n = a.len
   var h = 1
@@ -222,7 +230,7 @@ proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
       a[j] = v
     if h == 1: break
 
-proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idgen: IdGenerator): PSym =
+proc genIfDispatcher*(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idgen: IdGenerator): PSym =
   var base = methods[0].ast[dispatcherPos].sym
   result = base
   var paramLen = base.typ.len
@@ -281,8 +289,7 @@ proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idg
   nilchecks.flags.incl nfTransf # should not be further transformed
   result.ast[bodyPos] = nilchecks
 
-proc generateMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator): PNode =
-  result = newNode(nkStmtList)
+proc generateIfMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator) =
   for bucket in 0..<g.methods.len:
     var relevantCols = initIntSet()
     for col in 1..<g.methods[bucket].methods[0].typ.len:
@@ -291,4 +298,4 @@ proc generateMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator): PNode =
         # if multi-methods are not enabled, we are interested only in the first field
         break
     sortBucket(g.methods[bucket].methods, relevantCols)
-    result.add newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols, idgen))
+    g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, idgen)
diff --git a/compiler/ic/cbackend.nim b/compiler/ic/cbackend.nim
index c2e6b08fe..83f1b4cc7 100644
--- a/compiler/ic/cbackend.nim
+++ b/compiler/ic/cbackend.nim
@@ -50,10 +50,9 @@ proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var Alive
     let n = unpackTree(g, m.module.position, m.fromDisk.topLevel, p)
     cgen.genTopLevelStmt(bmod, n)
 
-  let disps = finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
-  if disps != nil:
-    for disp in disps:
-      genProcAux(bmod, disp.sym)
+  finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
+  for disp in getDispatchers(g):
+    genProcAux(bmod, disp)
   m.fromDisk.backendFlags = cgen.whichInitProcs(bmod)
 
 proc replayTypeInfo(g: ModuleGraph; m: var LoadedModule; origin: FileIndex) =
diff --git a/compiler/ic/ic.nim b/compiler/ic/ic.nim
index 867bc95ae..2f03ffb43 100644
--- a/compiler/ic/ic.nim
+++ b/compiler/ic/ic.nim
@@ -51,8 +51,10 @@ type
     typeInstCache*: seq[(PackedItemId, PackedItemId)]
     procInstCache*: seq[PackedInstantiation]
     attachedOps*: seq[(PackedItemId, TTypeAttachedOp, PackedItemId)]
-    methodsPerType*: seq[(PackedItemId, int, PackedItemId)]
+    methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
     enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
+    methodsPerType*: seq[(PackedItemId, PackedItemId)]
+    dispatchers*: seq[PackedItemId]
 
     emittedTypeInfo*: seq[string]
     backendFlags*: set[ModuleBackendFlag]
@@ -650,8 +652,10 @@ proc loadRodFile*(filename: AbsoluteFile; m: var PackedModule; config: ConfigRef
     loadSeqSection typeInstCacheSection, m.typeInstCache
     loadSeqSection procInstCacheSection, m.procInstCache
     loadSeqSection attachedOpsSection, m.attachedOps
-    loadSeqSection methodsPerTypeSection, m.methodsPerType
+    loadSeqSection methodsPerGenericTypeSection, m.methodsPerGenericType
     loadSeqSection enumToStringProcsSection, m.enumToStringProcs
+    loadSeqSection methodsPerTypeSection, m.methodsPerType
+    loadSeqSection dispatchersSection, m.dispatchers
     loadSeqSection typeInfoSection, m.emittedTypeInfo
 
     f.loadSection backendFlagsSection
@@ -718,8 +722,10 @@ proc saveRodFile*(filename: AbsoluteFile; encoder: var PackedEncoder; m: var Pac
   storeSeqSection typeInstCacheSection, m.typeInstCache
   storeSeqSection procInstCacheSection, m.procInstCache
   storeSeqSection attachedOpsSection, m.attachedOps
-  storeSeqSection methodsPerTypeSection, m.methodsPerType
+  storeSeqSection methodsPerGenericTypeSection, m.methodsPerGenericType
   storeSeqSection enumToStringProcsSection, m.enumToStringProcs
+  storeSeqSection methodsPerTypeSection, m.methodsPerType
+  storeSeqSection dispatchersSection, m.dispatchers
   storeSeqSection typeInfoSection, m.emittedTypeInfo
 
   f.storeSection backendFlagsSection
diff --git a/compiler/ic/integrity.nim b/compiler/ic/integrity.nim
index 0ffd87c2f..d78e56847 100644
--- a/compiler/ic/integrity.nim
+++ b/compiler/ic/integrity.nim
@@ -135,8 +135,10 @@ proc checkModule(c: var CheckedContext; m: PackedModule) =
     typeInstCache*: seq[(PackedItemId, PackedItemId)]
     procInstCache*: seq[PackedInstantiation]
     attachedOps*: seq[(TTypeAttachedOp, PackedItemId, PackedItemId)]
-    methodsPerType*: seq[(PackedItemId, int, PackedItemId)]
+    methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
     enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
+    methodsPerType*: seq[(PackedItemId, PackedItemId)]
+    dispatchers*: seq[PackedItemId]
   ]#
 
 proc checkIntegrity*(g: ModuleGraph) =
diff --git a/compiler/ic/replayer.nim b/compiler/ic/replayer.nim
index 3bbe4fa31..b244ec885 100644
--- a/compiler/ic/replayer.nim
+++ b/compiler/ic/replayer.nim
@@ -103,6 +103,17 @@ proc replayBackendProcs*(g: ModuleGraph; module: int) =
     let symId = FullId(module: tmp.module, packed: it[1])
     g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)
 
+  for it in mitems(g.packed[module].fromDisk.methodsPerType):
+    let key = translateId(it[0], g.packed, module, g.config)
+    let tmp = translateId(it[1], g.packed, module, g.config)
+    let symId = FullId(module: tmp.module, packed: it[1])
+    g.methodsPerType.mgetOrPut(key, @[]).add LazySym(id: symId, sym: nil)
+
+  for it in mitems(g.packed[module].fromDisk.dispatchers):
+    let tmp = translateId(it, g.packed, module, g.config)
+    let symId = FullId(module: tmp.module, packed: it)
+    g.dispatchers.add LazySym(id: symId, sym: nil)
+
 proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
   ## We remember the generic instantiations a module performed
   ## in order to to avoid the code bloat that generic code tends
@@ -127,12 +138,12 @@ proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
       module: module, sym: FullId(module: sym.module, packed: it.sym),
       concreteTypes: concreteTypes, inst: nil)
 
-  for it in mitems(g.packed[module].fromDisk.methodsPerType):
+  for it in mitems(g.packed[module].fromDisk.methodsPerGenericType):
     let key = translateId(it[0], g.packed, module, g.config)
     let col = it[1]
     let tmp = translateId(it[2], g.packed, module, g.config)
     let symId = FullId(module: tmp.module, packed: it[2])
-    g.methodsPerType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
+    g.methodsPerGenericType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
 
   replayBackendProcs(g, module)
 
diff --git a/compiler/ic/rodfiles.nim b/compiler/ic/rodfiles.nim
index 968bf255f..5eef3874a 100644
--- a/compiler/ic/rodfiles.nim
+++ b/compiler/ic/rodfiles.nim
@@ -92,8 +92,10 @@ type
     typeInstCacheSection
     procInstCacheSection
     attachedOpsSection
-    methodsPerTypeSection
+    methodsPerGenericTypeSection
     enumToStringProcsSection
+    methodsPerTypeSection
+    dispatchersSection
     typeInfoSection  # required by the backend
     backendFlagsSection
     aliveSymsSection # beware, this is stored in a `.alivesyms` file.
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 74edb4694..7c636af8b 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -3097,9 +3097,8 @@ proc wholeCode(graph: ModuleGraph; m: BModule): Rope =
       var p = newInitProc(globals, m)
       attachProc(p, prc)
 
-  var disp = generateMethodDispatchers(graph, m.idgen)
-  for i in 0..<disp.len:
-    let prc = disp[i].sym
+  generateIfMethodDispatchers(graph, m.idgen)
+  for prc in getDispatchers(graph):
     if not globals.generatedSyms.containsOrIncl(prc.id):
       var p = newInitProc(globals, m)
       attachProc(p, prc)
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
index 99175815b..95fa193dc 100644
--- a/compiler/modulegraphs.nim
+++ b/compiler/modulegraphs.nim
@@ -16,6 +16,7 @@ import ../dist/checksums/src/checksums/md5
 import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages
 import ic / [packed_ast, ic]
 
+
 when defined(nimPreviewSlimSystem):
   import std/assertions
 
@@ -81,7 +82,7 @@ type
     typeInstCache*: Table[ItemId, seq[LazyType]] # A symbol's ItemId.
     procInstCache*: Table[ItemId, seq[LazyInstantiation]] # A symbol's ItemId.
     attachedOps*: array[TTypeAttachedOp, Table[ItemId, LazySym]] # Type ID, destructors, etc.
-    methodsPerType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods
+    methodsPerGenericType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods
     memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far).
     initializersPerType*: Table[ItemId, PNode] # Type ID, AST call to the default ctor (c++ only)
     enumToStringProcs*: Table[ItemId, LazySym]
@@ -110,6 +111,11 @@ type
     suggestSymbols*: Table[FileIndex, seq[SymInfoPair]]
     suggestErrors*: Table[FileIndex, seq[Suggest]]
     methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
+    bucketTable*: CountTable[ItemId]
+    objectTree*: Table[ItemId, seq[tuple[depth: int, value: PType]]]
+    methodsPerType*: Table[ItemId, seq[LazySym]]
+    dispatchers*: seq[LazySym]
+
     systemModule*: PSym
     sysTypes*: array[TTypeKind, PType]
     compilerprocs*: TStrTable
@@ -153,8 +159,10 @@ proc resetForBackend*(g: ModuleGraph) =
   g.procInstCache.clear()
   for a in mitems(g.attachedOps):
     a.clear()
-  g.methodsPerType.clear()
+  g.methodsPerGenericType.clear()
   g.enumToStringProcs.clear()
+  g.dispatchers.setLen(0)
+  g.methodsPerType.clear()
 
 const
   cb64 = [
@@ -325,6 +333,7 @@ iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation =
     for t in mitems(x[]):
       yield resolveInst(g, t)
 
+
 proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
   ## returns the requested attached operation for type `t`. Can return nil
   ## if no such operation exists.
@@ -348,6 +357,27 @@ proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttached
     toPackedGeneratedProcDef(value, g.encoders[module], g.packed[module].fromDisk)
     #storeAttachedProcDef(t, op, value, g.encoders[module], g.packed[module].fromDisk)
 
+iterator getDispatchers*(g: ModuleGraph): PSym =
+  for i in g.dispatchers.mitems:
+    yield resolveSym(g, i)
+
+proc addDispatchers*(g: ModuleGraph, value: PSym) =
+  # TODO: add it for packed modules
+  g.dispatchers.add LazySym(sym: value)
+
+iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[LazySym]): PSym =
+  for it in list.mitems:
+    yield resolveSym(g, it)
+
+proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[LazySym]) =
+  # TODO: add it for packed modules
+  g.methodsPerType[id] = methods
+
+iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym =
+  if g.methodsPerType.contains(t.itemId):
+    for it in mitems g.methodsPerType[t.itemId]:
+      yield resolveSym(g, it)
+
 proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
   result = resolveSym(g, g.enumToStringProcs[t.itemId])
   assert result != nil
@@ -356,12 +386,12 @@ proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
   g.enumToStringProcs[t.itemId] = LazySym(sym: value)
 
 iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
-  if g.methodsPerType.contains(t.itemId):
-    for it in mitems g.methodsPerType[t.itemId]:
+  if g.methodsPerGenericType.contains(t.itemId):
+    for it in mitems g.methodsPerGenericType[t.itemId]:
       yield (it[0], resolveSym(g, it[1]))
 
 proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
-  g.methodsPerType.mgetOrPut(t.itemId, @[]).add (col, LazySym(sym: m))
+  g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, LazySym(sym: m))
 
 proc hasDisabledAsgn*(g: ModuleGraph; t: PType): bool =
   let op = getAttachedOp(g, t, attachedAsgn)
diff --git a/compiler/nim.cfg b/compiler/nim.cfg
index 8488cb9ef..43da7e3e0 100644
--- a/compiler/nim.cfg
+++ b/compiler/nim.cfg
@@ -9,6 +9,7 @@ define:nimPreviewSlimSystem
 define:nimPreviewCstringConversion
 define:nimPreviewProcConversion
 define:nimPreviewRangeDefault
+define:nimPreviewVtables
 define:nimPreviewNonVarDestructor
 threads:off
 
diff --git a/compiler/pipelines.nim b/compiler/pipelines.nim
index b72207607..91f3428be 100644
--- a/compiler/pipelines.nim
+++ b/compiler/pipelines.nim
@@ -191,15 +191,16 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
   case graph.pipelinePass
   of CgenPass:
     if bModule != nil:
-      let disps = finalCodegenActions(graph, BModule(bModule), finalNode)
-      if disps != nil:
+      let m = BModule(bModule)
+      finalCodegenActions(graph, m, finalNode)
+      if graph.dispatchers.len > 0:
         let ctx = preparePContext(graph, module, idgen)
-        for disp in disps:
-          let retTyp = disp.sym.typ[0]
+        for disp in getDispatchers(graph):
+          let retTyp = disp.typ[0]
           if retTyp != nil:
-            # todo properly semcheck the code of dispatcher?
-            createTypeBoundOps(graph, ctx, retTyp, disp.info, idgen)
-          genProcAux(BModule(bModule), disp.sym)
+            # TODO: properly semcheck the code of dispatcher?
+            createTypeBoundOps(graph, ctx, retTyp, disp.ast.info, idgen)
+          genProcAux(m, disp)
         discard closePContext(graph, ctx, nil)
   of JSgenPass:
     when not defined(leanCompiler):
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 960fc9165..0dcc66432 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -20,6 +20,7 @@ import
   isolation_check, typeallowed, modulegraphs, enumtostr, concepts, astmsgs,
   extccomp
 
+import vtables
 import std/[strtabs, math, tables, intsets, strutils]
 
 when not defined(leanCompiler):
@@ -839,6 +840,15 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
   if c.config.cmd == cmdIdeTools:
     appendToModule(c.module, result)
   trackStmt(c, c.module, result, isTopLevel = true)
+  if optMultiMethods notin c.config.globalOptions and
+      c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
+      c.config.isDefined("nimPreviewVtables") and 
+      c.config.backend != backendCpp and
+      sfCompileToCpp notin c.module.flags:
+    sortVTableDispatchers(c.graph)
+
+    if sfMainModule in c.module.flags:
+      collectVTableDispatchers(c.graph)
 
 proc recoverContext(c: PContext) =
   # clean up in case of a semantic error: We clean up the stacks, etc. This is
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 54f46fa73..e010bf179 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -24,6 +24,9 @@ when defined(useDfa):
 import liftdestructors
 include sinkparameter_inference
 
+
+import std/options as opt
+
 #[ Second semantic checking pass over the AST. Necessary because the old
    way had some inherent problems. Performs:
 
@@ -91,6 +94,37 @@ const
   errXCannotBeAssignedTo = "'$1' cannot be assigned to"
   errLetNeedsInit = "'let' symbol requires an initialization"
 
+proc getObjDepth(t: PType): Option[tuple[depth: int, root: ItemId]] =
+  var x = t
+  var res: tuple[depth: int, root: ItemId]
+  res.depth = -1
+  var stack = newSeq[ItemId]()
+  while x != nil:
+    x = skipTypes(x, skipPtrs)
+    if x.kind != tyObject:
+      return none(tuple[depth: int, root: ItemId])
+    stack.add x.itemId
+    x = x[0]
+    inc(res.depth)
+  res.root = stack[^2]
+  result = some(res)
+
+proc collectObjectTree(graph: ModuleGraph, n: PNode) =
+  for section in n:
+    if section.kind == nkTypeDef and section[^1].kind in {nkObjectTy, nkRefTy, nkPtrTy}:
+      let typ = section[^1].typ.skipTypes(skipPtrs)
+      if typ.len > 0 and typ[0] != nil:
+        let depthItem = getObjDepth(typ)
+        if isSome(depthItem):
+          let (depthLevel, root) = depthItem.unsafeGet
+          if depthLevel == 1:
+            graph.objectTree[root] = @[]
+          else:
+            if root notin graph.objectTree:
+              graph.objectTree[root] = @[(depthLevel, typ)]
+            else:
+              graph.objectTree[root].add (depthLevel, typ)
+
 proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
   if typ == nil or sfGeneratedOp in tracked.owner.flags:
     # don't create type bound ops for anything in a function with a `nodestroy` pragma
@@ -1323,8 +1357,11 @@ proc track(tracked: PEffects, n: PNode) =
   of nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkLambda, nkFuncDef, nkDo:
     if n[0].kind == nkSym and n[0].sym.ast != nil:
       trackInnerProc(tracked, getBody(tracked.graph, n[0].sym))
-  of nkTypeSection, nkMacroDef, nkTemplateDef:
+  of nkMacroDef, nkTemplateDef:
     discard
+  of nkTypeSection:
+    if tracked.isTopLevel:
+      collectObjectTree(tracked.graph, n)
   of nkCast:
     if n.len == 2:
       track(tracked, n[1])
@@ -1637,13 +1674,18 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
     checkNil(s, body, g.config, c.idgen)
 
 proc trackStmt*(c: PContext; module: PSym; n: PNode, isTopLevel: bool) =
-  if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
-                nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:
-    return
-  let g = c.graph
-  var effects = newNodeI(nkEffectList, n.info)
-  var t: TEffects = initEffects(g, effects, module, c)
-  t.isTopLevel = isTopLevel
-  track(t, n)
-  when defined(drnim):
-    if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, module, n)
+  case n.kind
+  of {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
+                nkConverterDef, nkMethodDef, nkIteratorDef}:
+    discard
+  of nkTypeSection:
+    if isTopLevel:
+      collectObjectTree(c.graph, n)
+  else:
+    let g = c.graph
+    var effects = newNodeI(nkEffectList, n.info)
+    var t: TEffects = initEffects(g, effects, module, c)
+    t.isTopLevel = isTopLevel
+    track(t, n)
+    when defined(drnim):
+      if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, module, n)
diff --git a/compiler/vtables.nim b/compiler/vtables.nim
new file mode 100644
index 000000000..f57b59eae
--- /dev/null
+++ b/compiler/vtables.nim
@@ -0,0 +1,167 @@
+import ast, modulegraphs, magicsys, lineinfos, options, cgmeth, types

+import std/[algorithm, tables, intsets, assertions]

+

+

+

+proc genVTableDispatcher(g: ModuleGraph; methods: seq[PSym]; index: int): PSym =

+#[

+proc dispatch(x: Base, params: ...) =

+  cast[proc bar(x: Base, params: ...)](x.vTable[index])(x, params)

+]#

+  var base = methods[0].ast[dispatcherPos].sym

+  result = base

+  var paramLen = base.typ.len

+  var body = newNodeI(nkStmtList, base.info)

+

+  var disp = newNodeI(nkIfStmt, base.info)

+

+  var vTableAccess = newNodeIT(nkBracketExpr, base.info, base.typ)

+  let nimGetVTableSym = getCompilerProc(g, "nimGetVTable")

+  let ptrPNimType = nimGetVTableSym.typ.n[1].sym.typ

+

+  var nTyp = base.typ.n[1].sym.typ

+  var dispatchObject = newSymNode(base.typ.n[1].sym)

+  if nTyp.kind == tyObject:

+    dispatchObject = newTree(nkAddr, dispatchObject)

+  else:

+    if g.config.backend != backendCpp: # TODO: maybe handle ptr?

+      if nTyp.kind == tyVar and nTyp.skipTypes({tyVar}).kind != tyObject:

+        dispatchObject = newTree(nkDerefExpr, dispatchObject)

+

+  var getVTableCall = newTree(nkCall,

+    newSymNode(nimGetVTableSym),

+    dispatchObject,

+    newIntNode(nkIntLit, index)

+  )

+  getVTableCall.typ = base.typ

+  var vTableCall = newNodeIT(nkCall, base.info, base.typ[0])

+  var castNode = newTree(nkCast,

+        newNodeIT(nkType, base.info, base.typ),

+        getVTableCall)

+

+  castNode.typ = base.typ

+  vTableCall.add castNode

+  for col in 1..<paramLen:

+    let param = base.typ.n[col].sym

+    vTableCall.add newSymNode(param)

+

+  var ret: PNode

+  if base.typ[0] != nil:

+    var a = newNodeI(nkFastAsgn, base.info)

+    a.add newSymNode(base.ast[resultPos].sym)

+    a.add vTableCall

+    ret = newNodeI(nkReturnStmt, base.info)

+    ret.add a

+  else:

+    ret = vTableCall

+

+  if base.typ.n[1].sym.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:

+    let ifBranch = newNodeI(nkElifBranch, base.info)

+    let boolType = getSysType(g, unknownLineInfo, tyBool)

+    var isNil = getSysMagic(g, unknownLineInfo, "isNil", mIsNil)

+    let checkSelf = newNodeIT(nkCall, base.info, boolType)

+    checkSelf.add newSymNode(isNil)

+    checkSelf.add newSymNode(base.typ.n[1].sym)

+    ifBranch.add checkSelf

+    ifBranch.add newTree(nkCall,

+        newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(base.typ.n[1].sym))

+    let elseBranch = newTree(nkElifBranch, ret)

+    disp.add ifBranch

+    disp.add elseBranch

+  else:

+    disp = ret

+

+  body.add disp

+  body.flags.incl nfTransf # should not be further transformed

+  result.ast[bodyPos] = body

+

+proc containGenerics(base: PType, s: seq[tuple[depth: int, value: PType]]): bool =

+  result = tfHasMeta in base.flags

+  for i in s:

+    if tfHasMeta in i.value.flags:

+      result = true

+      break

+

+proc collectVTableDispatchers*(g: ModuleGraph) =

+  var itemTable = initTable[ItemId, seq[LazySym]]()

+  var rootTypeSeq = newSeq[PType]()

+  var rootItemIdCount = initCountTable[ItemId]()

+  for bucket in 0..<g.methods.len:

+    var relevantCols = initIntSet()

+    if relevantCol(g.methods[bucket].methods, 1): incl(relevantCols, 1)

+    sortBucket(g.methods[bucket].methods, relevantCols)

+    let base = g.methods[bucket].methods[^1]

+    let baseType = base.typ[1].skipTypes(skipPtrs-{tyTypeDesc})

+    if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):

+      let methodIndexLen = g.bucketTable[baseType.itemId]

+      if baseType.itemId notin itemTable: # once is enough

+        rootTypeSeq.add baseType

+        itemTable[baseType.itemId] = newSeq[LazySym](methodIndexLen)

+

+        sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =

+          if x.depth >= y.depth: 1

+          else: -1

+          )

+

+        for item in g.objectTree[baseType.itemId]:

+          if item.value.itemId notin itemTable:

+            itemTable[item.value.itemId] = newSeq[LazySym](methodIndexLen)

+

+      var mIndex = 0 # here is the correpsonding index

+      if baseType.itemId notin rootItemIdCount:

+        rootItemIdCount[baseType.itemId] = 1

+      else:

+        mIndex = rootItemIdCount[baseType.itemId]

+        rootItemIdCount.inc(baseType.itemId)

+      for idx in 0..<g.methods[bucket].methods.len:

+        let obj = g.methods[bucket].methods[idx].typ[1].skipTypes(skipPtrs)

+        itemTable[obj.itemId][mIndex] = LazySym(sym: g.methods[bucket].methods[idx])

+      g.addDispatchers genVTableDispatcher(g, g.methods[bucket].methods, mIndex)

+    else: # if the base object doesn't have this method

+      g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, g.idgen)

+

+proc sortVTableDispatchers*(g: ModuleGraph) =

+  var itemTable = initTable[ItemId, seq[LazySym]]()

+  var rootTypeSeq = newSeq[ItemId]()

+  var rootItemIdCount = initCountTable[ItemId]()

+  for bucket in 0..<g.methods.len:

+    var relevantCols = initIntSet()

+    if relevantCol(g.methods[bucket].methods, 1): incl(relevantCols, 1)

+    sortBucket(g.methods[bucket].methods, relevantCols)

+    let base = g.methods[bucket].methods[^1]

+    let baseType = base.typ[1].skipTypes(skipPtrs-{tyTypeDesc})

+    if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):

+      let methodIndexLen = g.bucketTable[baseType.itemId]

+      if baseType.itemId notin itemTable: # once is enough

+        rootTypeSeq.add baseType.itemId

+        itemTable[baseType.itemId] = newSeq[LazySym](methodIndexLen)

+

+        sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =

+          if x.depth >= y.depth: 1

+          else: -1

+          )

+

+        for item in g.objectTree[baseType.itemId]:

+          if item.value.itemId notin itemTable:

+            itemTable[item.value.itemId] = newSeq[LazySym](methodIndexLen)

+

+      var mIndex = 0 # here is the correpsonding index

+      if baseType.itemId notin rootItemIdCount:

+        rootItemIdCount[baseType.itemId] = 1

+      else:

+        mIndex = rootItemIdCount[baseType.itemId]

+        rootItemIdCount.inc(baseType.itemId)

+      for idx in 0..<g.methods[bucket].methods.len:

+        let obj = g.methods[bucket].methods[idx].typ[1].skipTypes(skipPtrs)

+        itemTable[obj.itemId][mIndex] = LazySym(sym: g.methods[bucket].methods[idx])

+

+  for baseType in rootTypeSeq:

+    g.setMethodsPerType(baseType, itemTable[baseType])

+    for item in g.objectTree[baseType]:

+      let typ = item.value.skipTypes(skipPtrs)

+      let idx = typ.itemId

+      for mIndex in 0..<itemTable[idx].len:

+        if itemTable[idx][mIndex].sym == nil:

+          let parentIndex = typ[0].skipTypes(skipPtrs).itemId

+          itemTable[idx][mIndex] = itemTable[parentIndex][mIndex]

+      g.setMethodsPerType(idx, itemTable[idx])

diff --git a/config/config.nims b/config/config.nims
index 5c9c88e8a..4b8582d9c 100644
--- a/config/config.nims
+++ b/config/config.nims
@@ -21,4 +21,3 @@ when defined(nimStrictMode):
     # future work: XDeclaredButNotUsed
 
 switch("define", "nimVersion:" & NimVersion)
-
diff --git a/lib/system.nim b/lib/system.nim
index 5d0bbb9c7..a072c5851 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -1606,6 +1606,8 @@ when not defined(js) and defined(nimV2):
       traceImpl: pointer
       typeInfoV1: pointer # for backwards compat, usually nil
       flags: int
+      when defined(nimPreviewVtables) and not defined(cpp):
+        vTable: UncheckedArray[pointer] # vtable for types
     PNimTypeV2 = ptr TNimTypeV2
 
 proc supportsCopyMem(t: typedesc): bool {.magic: "TypeTrait".}
diff --git a/lib/system/arc.nim b/lib/system/arc.nim
index b49a6a63b..88d21643a 100644
--- a/lib/system/arc.nim
+++ b/lib/system/arc.nim
@@ -243,3 +243,8 @@ template tearDownForeignThreadGc* =
 
 proc isObjDisplayCheck(source: PNimTypeV2, targetDepth: int16, token: uint32): bool {.compilerRtl, inl.} =
   result = targetDepth <= source.depth and source.display[targetDepth] == token
+
+when defined(nimPreviewVtables) and not defined(cpp):
+  proc nimGetVTable(p: pointer, index: int): pointer
+        {.compilerRtl, inline, raises: [].} =
+    result = cast[ptr PNimTypeV2](p).vTable[index]
diff --git a/tests/config.nims b/tests/config.nims
index e0de65fc4..0e58c7c14 100644
--- a/tests/config.nims
+++ b/tests/config.nims
@@ -44,4 +44,5 @@ switch("define", "nimPreviewNonVarDestructor")
 
 switch("warningAserror", "UnnamedBreak")
 switch("legacy", "verboseTypeMismatch")
+switch("define", "nimPreviewVtables")
 
diff --git a/tests/generics/tobjecttyperel.nim b/tests/generics/tobjecttyperel.nim
index 80fe23459..0349184bb 100644
--- a/tests/generics/tobjecttyperel.nim
+++ b/tests/generics/tobjecttyperel.nim
@@ -1,4 +1,5 @@
 discard """
+  matrix: "-u:nimPreviewVtables"
   output: '''(peel: 0, color: 15)
 (color: 15)
 17
diff --git a/tests/method/nim.cfg b/tests/method/nim.cfg
deleted file mode 100644
index 57698b028..000000000
--- a/tests/method/nim.cfg
+++ /dev/null
@@ -1 +0,0 @@
-multimethods:on
diff --git a/tests/method/tcompilegenerics.nim b/tests/method/tcompilegenerics.nim
new file mode 100644
index 000000000..d0732895b
--- /dev/null
+++ b/tests/method/tcompilegenerics.nim
@@ -0,0 +1,24 @@
+discard """
+  matrix: "--mm:arc; --mm:refc"
+  output: '''
+newDNode base
+'''
+"""
+
+type
+  SNodeAny = ref object of RootObj
+  SNode[T] = ref object of SNodeAny
+    m: T
+  DNode[T] = ref object
+
+method getStr(s: SNode[float]): string {.base.} = "blahblah"
+
+method newDNode(s: SNodeAny) {.base.} =
+  echo "newDNode base"
+
+method newDNode[T](s: SNode[T]) =
+  echo "newDNode generic"
+
+let m = SNode[float]()
+let s = SNodeAny(m)
+newDnode(s)
\ No newline at end of file
diff --git a/tests/method/tgeneric_methods.nim b/tests/method/tgeneric_methods.nim
index ab92c66d8..0f1e7e1f0 100644
--- a/tests/method/tgeneric_methods.nim
+++ b/tests/method/tgeneric_methods.nim
@@ -1,42 +1,42 @@
-discard """
-  matrix: "--mm:arc; --mm:refc"
-  output: '''wow2
-X 1
-X 3'''
-"""
-type
-  First[T] = ref object of RootObj
-    value: T
-
-  Second[T] = ref object of First[T]
-    value2: T
-
-method wow[T](y: int; x: First[T]) {.base.} =
-  echo "wow1"
-
-method wow[T](y: int; x: Second[T]) =
-  echo "wow2"
-
-var
-  x: Second[int]
-new(x)
-
-proc takeFirst(x: First[int]) =
-  wow(2, x)
-
-takeFirst(x)
-
-
-# bug #5479
-type
-  Base[T: static[int]] = ref object of RootObj
-
-method test[T](t: Base[T]) {.base.} =
-  echo "X ", t.T
-
-let ab = Base[1]()
-
-ab.test()
-
-let ac = Base[3]()
-ac.test()
+discard """

+  matrix: "--mm:arc --multimethods:on -u:nimPreviewVtables; --mm:refc --multimethods:on -u:nimPreviewVtables"

+  output: '''wow2

+X 1

+X 3'''

+"""

+type

+  First[T] = ref object of RootObj

+    value: T

+

+  Second[T] = ref object of First[T]

+    value2: T

+

+method wow[T](y: int; x: First[T]) {.base.} =

+  echo "wow1"

+

+method wow[T](y: int; x: Second[T]) =

+  echo "wow2"

+

+var

+  x: Second[int]

+new(x)

+

+proc takeFirst(x: First[int]) =

+  wow(2, x)

+

+takeFirst(x)

+

+

+# bug #5479

+type

+  Base[T: static[int]] = ref object of RootObj

+

+method test[T](t: Base[T]) {.base.} =

+  echo "X ", t.T

+

+let ab = Base[1]()

+

+ab.test()

+

+let ac = Base[3]()

+ac.test()

diff --git a/tests/method/tmethod_various.nim b/tests/method/tmethod_various.nim
index c41d04983..3b64aea8d 100644
--- a/tests/method/tmethod_various.nim
+++ b/tests/method/tmethod_various.nim
@@ -1,15 +1,12 @@
 discard """
   matrix: "--mm:arc; --mm:refc"
   output: '''
-do nothing
 HELLO WORLD!
 '''
 """
 
 
-# tmethods1
-method somethin(obj: RootObj) {.base.} =
-  echo "do nothing"
+
 
 type
   TNode* {.inheritable.} = object
@@ -23,8 +20,6 @@ type
 method foo(a: PNode, b: PSomethingElse) {.base.} = discard
 method foo(a: PNodeFoo, b: PSomethingElse) = discard
 
-var o: RootObj
-o.somethin()
 
 
 
diff --git a/tests/method/tmethods_old.nim b/tests/method/tmethods_old.nim
new file mode 100644
index 000000000..cd3f67217
--- /dev/null
+++ b/tests/method/tmethods_old.nim
@@ -0,0 +1,12 @@
+discard """
+  matrix: "--mm:arc -u:nimPreviewVtables"
+  output: '''
+do nothing
+'''
+"""
+
+# tmethods1
+method somethin(obj: RootObj) {.base.} =
+  echo "do nothing"
+var o: RootObj
+o.somethin()
diff --git a/tests/method/tmultim.nim b/tests/method/tmultim.nim
index 126eb7526..bba4d8c5c 100644
--- a/tests/method/tmultim.nim
+++ b/tests/method/tmultim.nim
@@ -1,96 +1,97 @@
-discard """
-  output: '''
-collide: unit, thing
-collide: unit, thing
-collide: thing, unit
-collide: thing, thing
-collide: unit, thing |
-collide: unit, thing |
-collide: thing, unit |
-do nothing
-'''
-  joinable: false
-  disabled: true
-"""
-
-
-# tmultim2
-type
-  TThing {.inheritable.} = object
-  TUnit = object of TThing
-    x: int
-  TParticle = object of TThing
-    a, b: int
-
-method collide(a, b: TThing) {.base, inline.} =
-  echo "collide: thing, thing"
-
-method collide(a: TThing, b: TUnit) {.inline.} =
-  echo "collide: thing, unit"
-
-method collide(a: TUnit, b: TThing) {.inline.} =
-  echo "collide: unit, thing"
-
-proc test(a, b: TThing) {.inline.} =
-  collide(a, b)
-
-proc staticCollide(a, b: TThing) {.inline.} =
-  procCall collide(a, b)
-
-var
-  a: TThing
-  b, c: TUnit
-collide(b, c) # ambiguous (unit, thing) or (thing, unit)? -> prefer unit, thing!
-test(b, c)
-collide(a, b)
-staticCollide(a, b)
-
-
-
-# tmultim6
-type
-  Thing {.inheritable.} = object
-  Unit[T] = object of Thing
-    x: T
-  Particle = object of Thing
-    a, b: int
-
-method collide(a, b: Thing) {.base, inline.} =
-  quit "to override!"
-
-method collide[T](a: Thing, b: Unit[T]) {.inline.} =
-  echo "collide: thing, unit |"
-
-method collide[T](a: Unit[T], b: Thing) {.inline.} =
-  echo "collide: unit, thing |"
-
-proc test(a, b: Thing) {.inline.} =
-  collide(a, b)
-
-var
-  aaa: Thing
-  bbb, ccc: Unit[string]
-collide(bbb, Thing(ccc))
-test(bbb, ccc)
-collide(aaa, bbb)
-
-
-
-# tmethods1
-method somethin(obj: RootObj) {.base.} =
-  echo "do nothing"
-
-type
-  TNode* {.inheritable.} = object
-  PNode* = ref TNode
-
-  PNodeFoo* = ref object of TNode
-
-  TSomethingElse = object
-  PSomethingElse = ref TSomethingElse
-
-method foo(a: PNode, b: PSomethingElse) {.base.} = discard
-method foo(a: PNodeFoo, b: PSomethingElse) = discard
-
-var o: RootObj
-o.somethin()
+discard """

+  matrix: "--multimethods:on"

+  output: '''

+collide: unit, thing

+collide: unit, thing

+collide: thing, unit

+collide: thing, thing

+collide: unit, thing |

+collide: unit, thing |

+collide: thing, unit |

+do nothing

+'''

+  joinable: false

+  disabled: true

+"""

+

+

+# tmultim2

+type

+  TThing {.inheritable.} = object

+  TUnit = object of TThing

+    x: int

+  TParticle = object of TThing

+    a, b: int

+

+method collide(a, b: TThing) {.base, inline.} =

+  echo "collide: thing, thing"

+

+method collide(a: TThing, b: TUnit) {.inline.} =

+  echo "collide: thing, unit"

+

+method collide(a: TUnit, b: TThing) {.inline.} =

+  echo "collide: unit, thing"

+

+proc test(a, b: TThing) {.inline.} =

+  collide(a, b)

+

+proc staticCollide(a, b: TThing) {.inline.} =

+  procCall collide(a, b)

+

+var

+  a: TThing

+  b, c: TUnit

+collide(b, c) # ambiguous (unit, thing) or (thing, unit)? -> prefer unit, thing!

+test(b, c)

+collide(a, b)

+staticCollide(a, b)

+

+

+

+# tmultim6

+type

+  Thing {.inheritable.} = object

+  Unit[T] = object of Thing

+    x: T

+  Particle = object of Thing

+    a, b: int

+

+method collide(a, b: Thing) {.base, inline.} =

+  quit "to override!"

+

+method collide[T](a: Thing, b: Unit[T]) {.inline.} =

+  echo "collide: thing, unit |"

+

+method collide[T](a: Unit[T], b: Thing) {.inline.} =

+  echo "collide: unit, thing |"

+

+proc test(a, b: Thing) {.inline.} =

+  collide(a, b)

+

+var

+  aaa: Thing

+  bbb, ccc: Unit[string]

+collide(bbb, Thing(ccc))

+test(bbb, ccc)

+collide(aaa, bbb)

+

+

+

+# tmethods1

+method somethin(obj: RootObj) {.base.} =

+  echo "do nothing"

+

+type

+  TNode* {.inheritable.} = object

+  PNode* = ref TNode

+

+  PNodeFoo* = ref object of TNode

+

+  TSomethingElse = object

+  PSomethingElse = ref TSomethingElse

+

+method foo(a: PNode, b: PSomethingElse) {.base.} = discard

+method foo(a: PNodeFoo, b: PSomethingElse) = discard

+

+var o: RootObj

+o.somethin()

diff --git a/tests/method/tvtable.nim b/tests/method/tvtable.nim
new file mode 100644
index 000000000..8d98dd42c
--- /dev/null
+++ b/tests/method/tvtable.nim
@@ -0,0 +1,19 @@
+type FooBase = ref object of RootObj

+  dummy: int

+type Foo = ref object of FooBase

+  value : float32

+type Foo2 = ref object of Foo

+  change : float32

+method bar(x: FooBase, a: float32) {.base.} =

+  discard

+method bar(x: Foo, a: float32)  =

+  x.value += a

+method bar(x: Foo2, a: float32)  =

+  x.value += a

+

+

+proc test() =

+  var x = new Foo2

+  x.bar(2.3)

+

+test()
\ No newline at end of file