Skip to content

Commit

Permalink
test type bound ops
Browse files Browse the repository at this point in the history
  • Loading branch information
metagn committed Oct 15, 2024
1 parent 53460f3 commit b4c93f5
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 0 deletions.
2 changes: 2 additions & 0 deletions compiler/modulegraphs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
64 changes: 64 additions & 0 deletions compiler/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
69 changes: 69 additions & 0 deletions tests/sandwich/mobjhash.nim
Original file line number Diff line number Diff line change
@@ -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))
46 changes: 46 additions & 0 deletions tests/sandwich/tobjhash.nim
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit b4c93f5

Please sign in to comment.