Seems like == operator doesn't work for object variants. Is there better options than writing it by hands?
Writing == proc manually feels a bit o
type
Obj = object
case type: bool
of false:
a: int
of true:
b: int
echo Obj() == Obj() # Error
P.S.
There's an option to convert objects to json and compare string blobs for equality, not good for performance, but still better than writing comparison code manually :).
To compare records, Nim use the fields iterator which doesn’t work for variant records.
So, you have to define your own == proc:
type
Obj = object
case type: bool
of false:
a: int
of true:
b: int
proc `==`(obj1, obj2: Obj): bool =
result = obj1.type == obj2.type
if result:
case obj1.type
of false:
result = obj1.a == obj2.a
of true:
result = obj1.b == obj2.b
echo Obj() == Obj()
Thanks, but such code is not a good solution. It requires to write lots of code manually, and is error prone, as you may forget to update the comparison code when you add new field to the object.
Isn't it possible to convert the object to list (or iterator) of key/value tuples (or value references) and compare it?
This is used in our test suite to do a recursive deep diff of our objects: https://github.com/status-im/nim-beacon-chain/blob/a84a8ba1/tests/helpers/debug_state.nim#L81-L83
This includes dealing with object variants.
Took a crack at it, it's big and ugly and I'm sure I missed some edge cases, but:
https://play.nim-lang.org/#ix=2wrM
proc ifReturn(a,b,fld:NimNode):NimNode =
newIfStmt(
(infix(newDotExpr(a,fld),"!=",newDotExpr(b,fld)),
nnkReturnStmt.newTree(newLit(false))
)
)
macro equalsImpl[T:object](a,b:T): untyped=
let tImpl = a.getTypeImpl
result = newStmtList()
result.add quote do:
result = true
let records = tImpl[2]
records.expectKind(nnkRecList)
for field in records:
case field.kind
of nnkIdentDefs:
result.add(ifReturn(a,b,field[0]))
of nnkRecCase:
let discrim = field[0][0]
result.add(ifReturn(a,b,discrim))
var casestmt = newNimNode(nnkCaseStmt)
casestmt.add newDotExpr(a,discrim)
for ofbranch in field[1..^1]:
case ofbranch.kind
of nnkOfBranch:
let testVal = ofbranch[0]
let reclst = ofbranch[1]
expectKind(reclst,nnkRecList)
var ifstmts = newStmtList()
for idDef in reclst:
ifstmts.add(ifReturn(a,b,idDef[0]))
casestmt.add nnkOfBranch.newTree(testVal,ifstmts)
of nnkElse:
let reclst = ofbranch[0]
expectKind(reclst,nnkRecList)
var ifstmts = newStmtList()
for idDef in reclst:
ifstmts.add(ifReturn(a,b,idDef[0]))
casestmt.add nnkElse.newTree(ifstmts)
else: error "Unsupported branch" & ofbranch.repr
result.add casestmt
else:
error "Unsupported kind" & field.repr
#echo result.repr
proc `==`*[T:object](a,b:T):bool =
equalsImpl(a,b)
i did find a bug here's a better one
proc ifNeqRetFalse(fld,w,v:NimNode):NimNode =
quote do:
if `w`.`fld` != `v`.`fld`: return false
proc genIfStmts(recList,i,j:NimNode):NimNode =
result = newStmtList()
case recList.kind
of nnkRecList:
for idDef in recList:
expectKind(idDef,nnkIdentDefs)
result.add idDef[0].ifNeqRetFalse(i,j)
of nnkIdentDefs:
result.add recList[0].ifNeqRetFalse(i,j)
else: error "expected RecList or IdentDefs got" & recList.repr
macro equalsImpl[T:object](a,b:T): untyped =
template ifNeqRetFalse(fld:typed):untyped = ifNeqRetFalse(fld,a,b)
template genIfStmts(recList:typed):untyped = genIfStmts(recList,a,b)
let tImpl = a.getTypeImpl
result = newStmtList()
result.add quote do:
result = true
let records = tImpl[2]
records.expectKind(nnkRecList)
for field in records:
case field.kind
of nnkIdentDefs:
result.add field[0].ifNeqRetFalse
of nnkRecCase:
let discrim = field[0][0]
result.add discrim.ifNeqRetFalse
var casestmt = newNimNode(nnkCaseStmt)
casestmt.add newDotExpr(a,discrim)
for ofbranch in field[1..^1]:
case ofbranch.kind
of nnkOfBranch:
let testVal = ofbranch[0]
let reclst = ofbranch[1]
casestmt.add nnkOfBranch.newTree(testVal,reclst.genIfStmts)
of nnkElse:
let reclst = ofbranch[0]
casestmt.add nnkElse.newTree(reclst.genIfStmts)
else: error "Expected OfBranch or Else, got" & ofbranch.repr
result.add casestmt
else:
error "Expected IdentDefs or RecCase, got " & field.repr
#echo result.repr
proc `==`*[T:object](x,y:T):bool =
equalsImpl(x,y)