diff --git a/std/haxe/Copy.hx b/std/haxe/Copy.hx index 131e6c2d5b7..d2bc3fc0e7b 100644 --- a/std/haxe/Copy.hx +++ b/std/haxe/Copy.hx @@ -1,152 +1,124 @@ package haxe; +import haxe.ds.StringMap; +import haxe.ds.IntMap; +import haxe.ds.ObjectMap; +import haxe.io.Bytes; + class Copy { - var cacheMap:Array; - var cache:Array; + // TODO: check __id__ stuff on JS/neko + var cacheMap:ObjectMap<{}, {}>; + var cacheMapLength:Int; - public function new() { - cacheMap = []; - cache = []; + function new() { + cacheMap = new ObjectMap(); + cacheMapLength = 0; } - public function copyValue(v:Dynamic):Dynamic { - switch (Type.typeof(v)) { - case TNull, TInt, TFloat, TBool: - return v; + function copyValue(v:T):T { + return switch (Type.typeof(v)) { + case TNull, TInt, TFloat, TBool, TClass(String | Date): + v; case TClass(c): - if (#if neko untyped c.__is_String #else c == String #end) - return v; - var index = getRefIndex(v); - if (index >= 0) - return cacheMap[index]; - switch (#if (neko || python) Type.getClassName(c) #else c #end) { - case #if (neko || python) "Array" #else cast Array #end: - var nv:Array = []; - cacheMap.push(nv); - #if (flash || python || hl) - var v:Array = v; - #end - var l = v.length; - for (i in 0...l) { - var e:Dynamic = v[i]; - if (e == null) - nv.push(null); - else - nv.push(copyValue(e)); + var v:O = cast v; + var vCopy = getRef(v); + if (vCopy != null) { + return vCopy; + } + switch (c) { + case Array: + var a = []; + cacheMap.set(v, a); + var v:Array = cast v; + for (x in v) { + if (x == null) { + a.push(null); + } else { + a.push(copyValue(x)); + } + } + cast a; + case haxe.ds.StringMap: + var map = new StringMap(); + cacheMap.set(v, map); + var v:StringMap = cast v; + for (k => v in v) { + map.set(k, copyValue(v)); } - return nv; - case #if (neko || python) "haxe.ds.List" #else cast List #end: - var nv = new List(); - cacheMap.push(nv); - var v:List = v; - for (e in v) - nv.add(copyValue(e)); - return nv; - case #if (neko || python) "Date" #else cast Date #end: - return v; - case #if (neko || python) "haxe.ds.StringMap" #else cast haxe.ds.StringMap #end: - var nv = new haxe.ds.StringMap(); - cacheMap.push(nv); - var v:haxe.ds.StringMap = v; - for (k => e in v) - nv.set(k, copyValue(e)); - return nv; - case #if (neko || python) "haxe.ds.IntMap" #else cast haxe.ds.IntMap #end: - var nv = new haxe.ds.IntMap(); - cacheMap.push(nv); - var v:haxe.ds.IntMap = v; - for (k => e in v) - nv.set(k, copyValue(e)); - return nv; - case #if (neko || python) "haxe.ds.ObjectMap" #else cast haxe.ds.ObjectMap #end: - var nv = new haxe.ds.ObjectMap(); - cacheMap.push(nv); - var v:haxe.ds.ObjectMap = v; - for (k => e in v) { - #if (js || neko) - var id = Reflect.field(k, "__id__"); - Reflect.deleteField(k, "__id__"); - nv.set(k, copyValue(e)); - Reflect.setField(k, "__id__", id); - #else - nv.set(k, copyValue(e)); - #end + cast map; + case haxe.ds.IntMap: + var map = new IntMap(); + cacheMap.set(v, map); + var v:IntMap = cast v; + for (k => v in v) { + map.set(k, copyValue(v)); } - return nv; - case #if (neko || python) "haxe.io.Bytes" #else cast haxe.io.Bytes #end: - var v:haxe.io.Bytes = v; + cast map; + case haxe.ds.ObjectMap: + var map = new ObjectMap(); + cacheMap.set(v, map); + var v:ObjectMap<{}, Dynamic> = cast v; + for (k => v in v) { + // TODO: check the __id__ situation + map.set(copyValue(k), copyValue(v)); + } + cast map; + case haxe.io.Bytes: + var v:Bytes = cast v; var nv = v.sub(0, v.length); - cacheMap.push(nv); - return nv; - default: - var nv = Type.createEmptyInstance(c); - cacheMap.push(nv); - copyFields(v, nv); - return nv; + cacheMap.set(v, nv); + cast nv; + case _: + vCopy = Type.createEmptyInstance(c); + cacheMap.set(v, vCopy); + copyFields(v, vCopy); + vCopy; } case TObject: - if (v is Class || v is Enum) + if (v is Class || v is Enum) { return v; - var index = getRefIndex(v); - if (index >= 0) - return cacheMap[index]; - var nv = {}; - cacheMap.push(nv); - copyFields(v, nv); - return nv; - case TEnum(e): - var index = getRefIndex(v); - if (index >= 0) - return cacheMap[index]; - var v:EnumValue = v; - var args = v.getParameters(); + } + var v:O = cast v; + var vCopy = getRef(v); + if (vCopy != null) { + return vCopy; + } + var o:O = cast {}; + cacheMap.set(v, o); + copyFields(v, o); + o; + case TEnum(en): + var v:O = cast v; + var vEnumValue:EnumValue = cast v; + var vCopy = getRef(v); + if (vCopy != null) { + return vCopy; + } + var args = vEnumValue.getParameters(); if (args.length == 0) { - cacheMap.push(v); + cacheMap.set(v, v); return v; } - var nv:Dynamic = Type.createEnumIndex(e, v.getIndex(), args); - var needCopy = false; - cacheMap.push(nv); - for (i => e in args) { - var e = copyValue(e); - #if neko - nv.args[i] = e; - #elseif php - nv.params[i] = e; - #elseif (js && !js_enums_as_arrays) - nv.__params__[i] = e; - #elseif js - nv[i + 2] = e; - #else - needCopy = true; - args[i] = e; - #end + var newArgs = []; + for (arg in args) { + // TODO: check wtf was happening here in the original implementation + newArgs.push(copyValue(arg)); } - // only for platforms that don't know how to modify enum after create - // as a result, this might break some circular depencies - if (needCopy) - nv = Type.createEnumIndex(e, v.getIndex(), args); - return nv; - default: - return v; // assume not mutable + var nv:O = cast Type.createEnumIndex(en, vEnumValue.getIndex(), newArgs); + cacheMap.set(v, nv); + nv; + case TUnknown | TFunction: + v; } } - function getRefIndex(v:Dynamic) { - #if js - var vt = js.Syntax.typeof(v); - #end - for (i in 0...cache.length) { - #if js - var ci = cache[i]; - if (js.Syntax.typeof(ci) == vt && ci == v) - #else - if (cache[i] == v) - #end - return i; + function getRef(v:T):T { + var vCopy = cacheMap.get(v); + if (vCopy != null) { + return cast vCopy; } - cache.push(v); - return -1; + return null; } function copyFields(v:Dynamic, nv:Dynamic) { diff --git a/tests/unit/src/unitstd/haxe/Copy.unit.hx b/tests/unit/src/unitstd/haxe/Copy.unit.hx index d81d00b58dc..3adc774b893 100644 --- a/tests/unit/src/unitstd/haxe/Copy.unit.hx +++ b/tests/unit/src/unitstd/haxe/Copy.unit.hx @@ -10,6 +10,19 @@ var d = haxe.Copy.copy(c); d[0] != a; d[1] != a; d[0] == d[1]; +// List +var l = new haxe.ds.List(); +l.add(1); +l.add(2); +var lCopy = haxe.Copy.copy(l); +1 == lCopy.pop(); +2 == lCopy.pop(); +l != lCopy; +var l = new haxe.ds.List(); +l.add(l); +var lCopy = haxe.Copy.copy(l); +l != lCopy; +lCopy == lCopy.pop(); // Anon var a = {f1: 1, f2: 2}; @@ -26,9 +39,45 @@ d.f1 == d.f2; var a = (macro 1); var b = haxe.Copy.copy(a); +a != b; +a.expr != b.expr; switch [a.expr, b.expr] { case [EConst(CInt(a)), EConst(CInt(b))]: eq(a, b); case _: utest.Assert.fail('match failure: ${a.expr} ${b.expr}'); } +// Class +var c = new MyClass(0); +var d = haxe.Copy.copy(c); +c != d; +c.ref = c; +var d = haxe.Copy.copy(c); +c != d; +d == d.ref; +// StringMap +var map = new haxe.ds.StringMap(); +map.set("foo", map); +var mapCopy = haxe.Copy.copy(map); +map != mapCopy; +mapCopy == mapCopy.get("foo"); +// IntMap +var map = new haxe.ds.IntMap(); +map.set(0, map); +var mapCopy = haxe.Copy.copy(map); +map != mapCopy; +mapCopy == mapCopy.get(0); +// ObjectMap +var map = new haxe.ds.ObjectMap<{}, Dynamic>(); +var key = {}; +map.set(key, map); +var mapCopy = haxe.Copy.copy(map); +map != mapCopy; +var keyCopy = [for (key in mapCopy.keys()) key][0]; +t(mapCopy == mapCopy.get(keyCopy)); +key != keyCopy; +// Bytes +var bytes = haxe.io.Bytes.ofString("foo"); +var bytesCopy = haxe.Copy.copy(bytes); +bytes != bytesCopy; +bytesCopy.getString(0, 3) == "foo";