From b4c93f53195430ec53d719da90bf5d148ee9e9a0 Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 15 Oct 2024 18:42:35 +0300 Subject: [PATCH] test type bound ops implements https://github.com/nim-lang/RFCs/issues/380 --- compiler/modulegraphs.nim | 2 ++ compiler/semcall.nim | 10 ++++++ compiler/semstmts.nim | 9 +++++ compiler/types.nim | 64 ++++++++++++++++++++++++++++++++++ tests/sandwich/mobjhash.nim | 69 +++++++++++++++++++++++++++++++++++++ tests/sandwich/tobjhash.nim | 46 +++++++++++++++++++++++++ 6 files changed, 200 insertions(+) create mode 100644 tests/sandwich/mobjhash.nim create mode 100644 tests/sandwich/tobjhash.nim diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index dd6a590e4fc7..26fb92f388fc 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -75,6 +75,8 @@ 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. + typeBoundOps*: Table[PIdent, seq[PSym]] + ## overloads of each symbol name that are defined alongside a nominal type 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) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 72dcedf99cf3..761fca9b16b1 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -46,9 +46,11 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode, ## puts all overloads into a seq and prepares best+alt result = @[] var symx = initOverloadIter(o, c, headSymbol) + var symMarker = initIntSet() while symx != nil: if symx.kind in filter: result.add((symx, o.lastOverloadScope)) + symMarker.incl(symx.id) elif symx.kind == skGenericParam: #[ This code handles looking up a generic parameter when it's a static callable. @@ -62,6 +64,14 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode, result.add((paramTyp.n.sym, o.lastOverloadScope)) symx = nextOverloadIter(o, c, headSymbol) + # add all type bound ops with this specific name + # may be slow to do this for every single call, but the nominal types + # imply a straightforward match + let name = considerQuotedIdent(c, headSymbol) + for boundOp in c.graph.typeBoundOps.getOrDefault(name, @[]): + if boundOp.id notin symMarker: + # no need to add to symMarker, type bound ops should be unique already + result.add((boundOp, -1)) if result.len > 0: best = initCandidate(c, result[0].s, initialBinding, result[0].scope, diagnostics) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 506e0694c610..7241c0af2a9e 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2468,6 +2468,15 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, addInterfaceOverloadableSymAt(c, declarationScope, s) else: addInterfaceDeclAt(c, declarationScope, s) + if sfExported in s.flags: # also implies top level + for i in 1 ..< s.typ.len: + let t = nominalRoot(s.typ[i]) + if t != nil and t.owner == s.owner: + # parameter `i` is a nominal type in this module + # add this symbol as a global type bound op + c.graph.typeBoundOps.mgetOrPut(s.name, @[]).add(s) + # only need to add it once + break pragmaCallable(c, s, n, validPragmas) if not hasProto: diff --git a/compiler/types.nim b/compiler/types.nim index a441b0ea2b52..dcd70f867848 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1935,3 +1935,67 @@ proc isCharArrayPtr*(t: PType; allowPointerToChar: bool): bool = result = false else: result = false + +proc nominalRoot*(t: PType): PType = + ## the "name" type of a given instance of a nominal type, + ## i.e. the type directly associated with the symbol where the root + ## nominal type of `t` was defined, skipping things like generic instances, + ## aliases, `var`/`sink`/`typedesc` modifiers + ## + ## instead of returning the uninstantiated body of a generic type, + ## returns the type of the symbol instead (with tyGenericBody type) + result = nil + case t.kind + of tyAlias, tyVar, tySink: + # varargs? + result = nominalRoot(t.skipModifier) + of tyTypeDesc: + # for proc foo(_: type T) + result = nominalRoot(t.skipModifier) + of tyGenericInvocation, tyGenericInst: + result = t + # skip aliases, so this works in the same module but not in another module: + # type Foo[T] = object + # type Bar[T] = Foo[T] + # proc foo[T](x: Bar[T]) = ... # attached to type + while result.skipModifier.kind in {tyGenericInvocation, tyGenericInst}: + result = result.skipModifier + result = nominalRoot(result[0]) + of tyGenericBody: + result = t + # this time skip the aliases but take the generic body + while result.skipModifier.kind in {tyGenericInvocation, tyGenericInst}: + result = result.skipModifier[0] + let val = result.skipModifier + if val.kind in {tyDistinct, tyEnum, tyObject} or + (val.kind in {tyRef, tyPtr} and tfRefsAnonObj in val.flags): + # atomic nominal types, this generic body is attached to them + discard + else: + result = nominalRoot(val) + of tyCompositeTypeClass: + # parameter with type Foo + result = nominalRoot(t.skipModifier) + of tyGenericParam: + if t.genericParamHasConstraints: + # T: Foo + result = nominalRoot(t.genericConstraint) + else: + result = nil + of tyDistinct, tyEnum, tyObject: + result = t + of tyPtr, tyRef: + if tfRefsAnonObj in t.flags: + # in the case that we have `type Foo = ref object` etc + result = t + else: + # we could allow this in general, but there's things like `seq[Foo]` + #result = nominalRoot(t.skipModifier) + result = nil + of tyStatic: + # ? + result = nil + else: + # skips all typeclasses + # is this correct for `concept`? + result = nil diff --git a/tests/sandwich/mobjhash.nim b/tests/sandwich/mobjhash.nim new file mode 100644 index 000000000000..409c31ff9095 --- /dev/null +++ b/tests/sandwich/mobjhash.nim @@ -0,0 +1,69 @@ +import hashes + +type + Obj* = object + x*, y*: int + z*: string # to be ignored for equality + +proc `==`*(a, b: Obj): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: Obj): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + RefObj* = ref object + x*, y*: int + z*: string # to be ignored for equality + +proc `==`*(a, b: RefObj): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: RefObj): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj1*[T] = object + x*, y*: T + z*: string # to be ignored for equality + +proc `==`*[T](a, b: GenericObj1[T]): bool = + a.x == b.x and a.y == b.y + +proc hash*[T](a: GenericObj1[T]): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj2*[T] = object + x*, y*: T + z*: string # to be ignored for equality + +proc `==`*(a, b: GenericObj2): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: GenericObj2): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj3*[T] = object + x*, y*: T + z*: string # to be ignored for equality + GenericObj3Alias*[T] = GenericObj3[T] + +proc `==`*[T](a, b: GenericObj3Alias[T]): bool = + a.x == b.x and a.y == b.y + +proc hash*[T](a: GenericObj3Alias[T]): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj4*[T] = object + x*, y*: T + z*: string # to be ignored for equality + GenericObj4Alias*[T] = GenericObj4[T] + +proc `==`*(a, b: GenericObj4): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: GenericObj4): Hash = + !$(hash(a.x) !& hash(a.y)) diff --git a/tests/sandwich/tobjhash.nim b/tests/sandwich/tobjhash.nim new file mode 100644 index 000000000000..6443a99823c3 --- /dev/null +++ b/tests/sandwich/tobjhash.nim @@ -0,0 +1,46 @@ +# https://github.com/nim-lang/RFCs/issues/380 + +from mobjhash import Obj, RefObj, GenericObj1, GenericObj2, GenericObj3, GenericObj4 +import tables + +block: + var t: Table[Obj, int] + t[Obj(x: 3, y: 4, z: "debug")] = 34 + doAssert t[Obj(x: 3, y: 4, z: "ignored")] == 34 + doAssert Obj(x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[RefObj, int] + t[RefObj(x: 3, y: 4, z: "debug")] = 34 + doAssert t[RefObj(x: 3, y: 4, z: "ignored")] == 34 + doAssert RefObj(x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj1[float], int] + t[GenericObj1[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj1[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj1[float](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj1[int], int] + t[GenericObj1[int](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj1[int](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj1[int](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj2[float], int] + t[GenericObj2[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj2[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj2[float](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj3[float], int] + t[GenericObj3[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj3[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj3[float](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj4[float], int] + t[GenericObj4[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj4[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj4[float](x: 4, y: 3, z: "debug") notin t