Browse Source

[js] Shared state for multiple haxe-generated js modules (#8084)

* [js] shared state for multiple  js modules (fixes #7936)

* switched to the single unique id
Alexander Kuzmenko 6 years ago
parent
commit
3c4190a300

+ 22 - 14
src/generators/genjs.ml

@@ -1637,12 +1637,7 @@ let generate com =
 		"typeof window != \"undefined\" ? window : typeof global != \"undefined\" ? global : typeof self != \"undefined\" ? self : this"
 	) in
 
-	let closureArgs = [] in
-	let closureArgs = if has_feature ctx "js.Lib.global" then
-		var_global :: closureArgs
-	else
-		closureArgs
-	in
+	let closureArgs = [var_global] in
 	let closureArgs = if (anyExposed && not (Common.defined com Define.ShallowExpose)) then
 		var_exports :: closureArgs
 	else
@@ -1694,7 +1689,7 @@ let generate com =
 	if (not ctx.js_modern) && (ctx.es_version < 5) then
 		add_feature ctx "js.Lib.global"; (* console polyfill will check console from $global *)
 
-	if (not ctx.js_modern) && (has_feature ctx "js.Lib.global") then
+	if (not ctx.js_modern) then
 		print ctx "var %s = %s;\n" (fst var_global) (snd var_global);
 
 	if (not ctx.js_modern) && (ctx.es_version < 5) then
@@ -1765,16 +1760,14 @@ let generate com =
 		newline ctx;
 	end;
 	if has_feature ctx "use.$bind" then begin
-		let value = if not ctx.js_modern then "typeof $fid == \"undefined\" ? 0 : $fid" else "0" in
-		if has_dollar_underscore then
-			print ctx "var $fid = %s" value
-		else
-			print ctx "var $_, $fid = %s" value;
+		add_feature ctx "$global.$haxeUID";
+		if not has_dollar_underscore then
+			print ctx "var $_";
 		newline ctx;
 		(if ctx.es_version < 5 then
-			print ctx "function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $fid++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = function(){ return f.method.apply(f.scope, arguments); }; f.scope = o; f.method = m; o.hx__closures__[m.__id__] = f; } return f; }"
+			print ctx "function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = function(){ return f.method.apply(f.scope, arguments); }; f.scope = o; f.method = m; o.hx__closures__[m.__id__] = f; } return f; }"
 		else
-			print ctx "function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $fid++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = m.bind(o); o.hx__closures__[m.__id__] = f; } return f; }"
+			print ctx "function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = m.bind(o); o.hx__closures__[m.__id__] = f; } return f; }"
 		);
 		newline ctx;
 	end;
@@ -1782,12 +1775,27 @@ let generate com =
 		print ctx "function $arrayPush(x) { this.push(x); }";
 		newline ctx
 	end;
+	if has_feature ctx "$global.$haxeUID" then begin
+		add_feature ctx "js.Lib.global";
+		print ctx "if(typeof $global.$haxeUID == \"undefined\") $global.$haxeUID = 0;\n";
+	end;
 	List.iter (gen_block_element ~after:true ctx) (List.rev ctx.inits);
 	List.iter (generate_static ctx) (List.rev ctx.statics);
 	(match com.main with
 	| None -> ()
 	| Some e -> gen_expr ctx e; newline ctx);
 	if ctx.js_modern then begin
+		let closureArgs =
+			if has_feature ctx "js.Lib.global" then
+				closureArgs
+			else
+				(* no need for `typeof window != "undefined" ? window : typeof global != "undefined" ? <...>` *)
+				match List.rev closureArgs with
+					| (global_name,global_value) :: rest ->
+						List.rev ((global_name,"{}") :: rest)
+					| _ ->
+						closureArgs
+		in
 		print ctx "})(%s)" (String.concat ", " (List.map snd closureArgs));
 		newline ctx;
 	end;

+ 8 - 0
std/js/Lib.hx

@@ -133,4 +133,12 @@ class Lib {
 	public static function getOriginalException():Dynamic {
 		return null; // function is implemented in the compiler
 	}
+
+	/**
+		Generate next unique id
+	**/
+	@:allow(haxe.ds.ObjectMap.assignId)
+	static inline function getNextHaxeUID():{ function nextId(counterName:String):Int; } {
+		return js.Syntax.code("{0}.$haxeUID++", untyped __define_feature__("$global.$haxeUID", global));
+	}
 }

+ 4 - 1
std/js/_std/haxe/ds/ObjectMap.hx

@@ -22,6 +22,9 @@
 
 package haxe.ds;
 
+import js.Syntax;
+import js.Lib;
+
 @:coreApi
 class ObjectMap<K:{ }, V> implements haxe.Constraints.IMap<K,V> {
 
@@ -33,7 +36,7 @@ class ObjectMap<K:{ }, V> implements haxe.Constraints.IMap<K,V> {
 	static inline function __init__():Void count = 0;
 
 	static inline function assignId(obj: { } ):Int {
-		return untyped obj.__id__ = ++count;
+		return Syntax.code('({0}.__id__ = {1})', obj, Lib.getNextHaxeUID());
 	}
 
 	static inline function getId(obj: { } ):Int {

+ 1 - 0
tests/misc/projects/Issue7936/.gitignore

@@ -0,0 +1 @@
+bin/*

+ 44 - 0
tests/misc/projects/Issue7936/compile.hxml

@@ -0,0 +1,44 @@
+-cp src
+--each
+
+# Closures
+
+-main Closures1
+-js bin/closures1.js
+
+--next
+-main Closures2
+-js bin/closures2.js
+
+--next
+-D js-classic
+-main Closures1
+-js bin/closures1-classic.js
+
+--next
+-D js-classic
+-main Closures2
+-js bin/closures2-classic.js
+
+#ObjectMap
+--next
+-main ObjectMap1
+-js bin/objectMap1.js
+
+--next
+-main ObjectMap2
+-js bin/objectMap2.js
+
+--next
+-D js-classic
+-main ObjectMap1
+-js bin/objectMap1-classic.js
+
+--next
+-D js-classic
+-main ObjectMap2
+-js bin/objectMap2-classic.js
+
+# Run tests
+--next
+-cmd node run.js

+ 11 - 0
tests/misc/projects/Issue7936/run.js

@@ -0,0 +1,11 @@
+require('./bin/closures1.js');
+require('./bin/closures2.js');
+
+require('./bin/objectMap1.js');
+require('./bin/objectMap2.js');
+
+require('./bin/closures1-classic.js');
+require('./bin/closures2-classic.js');
+
+require('./bin/objectMap1-classic.js');
+require('./bin/objectMap2-classic.js');

+ 21 - 0
tests/misc/projects/Issue7936/src/Closures1.hx

@@ -0,0 +1,21 @@
+class Closures1 {
+	static var tmp:Any;
+
+	static public function main() {
+		var inst = new Test();
+		js.Syntax.code('global.inst = {0}', inst);
+		tmp = inst.test1;
+	}
+}
+
+@:keep
+class Test {
+	public function new() {}
+
+	public function test1() {
+		return 1;
+	}
+	public function test2() {
+		return 2;
+	}
+}

+ 16 - 0
tests/misc/projects/Issue7936/src/Closures2.hx

@@ -0,0 +1,16 @@
+class Closures2 {
+	static var test2:()->Int;
+
+	static public function main() {
+		var inst:Test2 = js.Syntax.code('global.inst');
+		test2 = inst.test2;
+		if(test2() != 2) {
+			throw 'Cross-module closure binding failed.';
+		}
+	}
+}
+
+extern class Test2 {
+	public function test1():Int;
+	public function test2():Int;
+}

+ 9 - 0
tests/misc/projects/Issue7936/src/ObjectMap1.hx

@@ -0,0 +1,9 @@
+class ObjectMap1 {
+	static var tmp:Any;
+
+	static public function main() {
+		var inst = {};
+		js.Syntax.code('global.inst = {0}', inst);
+		tmp = [inst => 'first'];
+	}
+}

+ 10 - 0
tests/misc/projects/Issue7936/src/ObjectMap2.hx

@@ -0,0 +1,10 @@
+class ObjectMap2 {
+	static public function main() {
+		var inst:{} = js.Syntax.code('global.inst');
+
+		var map:Map<{},String> = [{field:1} => 'second'];
+		if(map.exists(inst)) {
+			throw 'Cross-module ObjectMap failed.';
+		}
+	}
+}