Browse Source

Merge branch 'development' into haxe.Unit

Simon Krajewski 5 months ago
parent
commit
51dc4a85a7

+ 11 - 0
src/context/resolution.ml

@@ -180,6 +180,17 @@ class resolution_list (id : string list) = object(self)
 	method get_list =
 	method get_list =
 		l
 		l
 
 
+	method set_list l' =
+		l <- l';
+		cached_type_imports <- false
+
+	method clone_as (id : string list) =
+		self#resolve_lazies;
+		let rl = new resolution_list id in
+		rl#set_list l;
+		rl#cache_type_imports;
+		rl
+
 	method cache_type_imports =
 	method cache_type_imports =
 		let rec loop = function
 		let rec loop = function
 		| [] ->
 		| [] ->

+ 0 - 1
src/context/typecore.ml

@@ -97,7 +97,6 @@ type typer_globals = {
 	retain_meta : bool;
 	retain_meta : bool;
 	mutable core_api : typer option;
 	mutable core_api : typer option;
 	mutable macros : ((unit -> unit) * typer) option;
 	mutable macros : ((unit -> unit) * typer) option;
-	mutable std_types : module_def;
 	mutable module_check_policies : (string list * module_check_policy list * bool) list;
 	mutable module_check_policies : (string list * module_check_policy list * bool) list;
 	mutable global_using : (tclass * pos) list;
 	mutable global_using : (tclass * pos) list;
 	(* Indicates that Typer.create() finished building this instance *)
 	(* Indicates that Typer.create() finished building this instance *)

+ 1 - 1
src/generators/genhl.ml

@@ -770,7 +770,7 @@ and enum_class ctx e =
 			fid
 			fid
 		in
 		in
 			PMap.iter (fun _ ef -> 
 			PMap.iter (fun _ ef -> 
-			(match ef.ef_type with
+			(match follow ef.ef_type with
 				| TEnum _ -> ignore(add_field ef.ef_name (to_type ctx ef.ef_type))
 				| TEnum _ -> ignore(add_field ef.ef_name (to_type ctx ef.ef_type))
 				| TFun (args, ret) ->
 				| TFun (args, ret) ->
 					let fid = add_field ef.ef_name (to_type ctx ef.ef_type) in
 					let fid = add_field ef.ef_name (to_type ctx ef.ef_type) in

+ 7 - 0
src/macro/eval/evalStdLib.ml

@@ -2429,6 +2429,12 @@ module StdStringBuf = struct
 		vnull
 		vnull
 	)
 	)
 
 
+	let clear = vifun0 (fun vthis ->
+		let this = this vthis in
+		VStringBuffer.clear this;
+		vnull
+	)
+
 	let get_length = vifun0 (fun vthis ->
 	let get_length = vifun0 (fun vthis ->
 		let this = this vthis in
 		let this = this vthis in
 		vint this.blength
 		vint this.blength
@@ -3691,6 +3697,7 @@ let init_standard_library builtins =
 		"add",StdStringBuf.add;
 		"add",StdStringBuf.add;
 		"addChar",StdStringBuf.addChar;
 		"addChar",StdStringBuf.addChar;
 		"addSub",StdStringBuf.addSub;
 		"addSub",StdStringBuf.addSub;
+		"clear",StdStringBuf.clear;
 		"get_length",StdStringBuf.get_length;
 		"get_length",StdStringBuf.get_length;
 		"toString",StdStringBuf.toString;
 		"toString",StdStringBuf.toString;
 	];
 	];

+ 4 - 0
src/macro/eval/evalString.ml

@@ -284,6 +284,10 @@ module VStringBuffer = struct
 		Buffer.add_substring this.bbuffer s.sstring b_pos b_len;
 		Buffer.add_substring this.bbuffer s.sstring b_pos b_len;
 		this.blength <- this.blength + c_len
 		this.blength <- this.blength + c_len
 
 
+	let clear this =
+		Buffer.clear this.bbuffer;
+		this.blength <- 0
+
 	let contents this =
 	let contents this =
 		create_with_length (Buffer.contents this.bbuffer) this.blength
 		create_with_length (Buffer.contents this.bbuffer) this.blength
 end
 end

+ 10 - 17
src/typing/forLoop.ml

@@ -297,19 +297,6 @@ module IterationKind = struct
 		let get_array_length arr p =
 		let get_array_length arr p =
 			mk (mk_field arr "length") ctx.com.basic.tint p
 			mk (mk_field arr "length") ctx.com.basic.tint p
 		in
 		in
-		let check_loop_var_modification vl e =
-			let rec loop e =
-				match e.eexpr with
-				| TBinop (OpAssign,{ eexpr = TLocal l },_)
-				| TBinop (OpAssignOp _,{ eexpr = TLocal l },_)
-				| TUnop (Increment,_,{ eexpr = TLocal l })
-				| TUnop (Decrement,_,{ eexpr = TLocal l })  when List.memq l vl ->
-					raise_typing_error "Loop variable cannot be modified" e.epos
-				| _ ->
-					Type.iter loop e
-			in
-			loop e
-		in
 		let gen_int_iter e1 pt f_get f_length =
 		let gen_int_iter e1 pt f_get f_length =
 			let index = gen_local ctx t_int v.v_pos in
 			let index = gen_local ctx t_int v.v_pos in
 			index.v_meta <- (Meta.ForLoopVariable,[],null_pos) :: index.v_meta;
 			index.v_meta <- (Meta.ForLoopVariable,[],null_pos) :: index.v_meta;
@@ -340,7 +327,6 @@ module IterationKind = struct
 		in
 		in
 		match iterator.it_kind with
 		match iterator.it_kind with
 		| IteratorIntUnroll(offset,length,ascending,unroll_params) ->
 		| IteratorIntUnroll(offset,length,ascending,unroll_params) ->
-			check_loop_var_modification [v] e2;
 			if not ascending then raise_typing_error "Cannot iterate backwards" p;
 			if not ascending then raise_typing_error "Cannot iterate backwards" p;
 			let rec unroll acc i =
 			let rec unroll acc i =
 				if i = length then
 				if i = length then
@@ -351,6 +337,8 @@ module IterationKind = struct
 					let rec loop e = match e.eexpr with
 					let rec loop e = match e.eexpr with
 					| TLocal v' when v == v' ->
 					| TLocal v' when v == v' ->
 						{ei with epos = e.epos}
 						{ei with epos = e.epos}
+					| TUnop ((Decrement | Increment), _, { eexpr = TLocal v' }) when v == v' -> raise Exit
+					| TBinop ((OpAssign | OpAssignOp _), { eexpr = TLocal v' }, _) when v == v' -> raise Exit
 					| TVar(v,eo) when has_var_flag v VStatic ->
 					| TVar(v,eo) when has_var_flag v VStatic ->
 						if acc = [] then
 						if acc = [] then
 							local_vars := {e with eexpr = TVar(v,eo)} :: !local_vars;
 							local_vars := {e with eexpr = TVar(v,eo)} :: !local_vars;
@@ -358,7 +346,14 @@ module IterationKind = struct
 					| _ ->
 					| _ ->
 						map_expr loop e
 						map_expr loop e
 				in
 				in
-				let e2 = loop e2 in
+				let e2 = try
+					loop e2
+				with Exit ->
+					{ e2 with eexpr = TBlock (
+						(mk (TVar(v,Some ei)) t_void p) ::
+						(match e2.eexpr with TBlock el -> el | _ -> [e2])
+					)}
+				in
 				let acc = acc @ !local_vars in
 				let acc = acc @ !local_vars in
 				let e2 = Texpr.duplicate_tvars e_identity e2 in
 				let e2 = Texpr.duplicate_tvars e_identity e2 in
 				unroll (e2 :: acc) (i + 1)
 				unroll (e2 :: acc) (i + 1)
@@ -366,7 +361,6 @@ module IterationKind = struct
 			let el = unroll [] 0 in
 			let el = unroll [] 0 in
 			mk (TBlock el) t_void p
 			mk (TBlock el) t_void p
 		| IteratorIntConst(a,b,ascending) ->
 		| IteratorIntConst(a,b,ascending) ->
-			check_loop_var_modification [v] e2;
 			if not ascending then raise_typing_error "Cannot iterate backwards" p;
 			if not ascending then raise_typing_error "Cannot iterate backwards" p;
 			let v_index = gen_local ctx t_int a.epos in
 			let v_index = gen_local ctx t_int a.epos in
 			let evar_index = mk (TVar(v_index,Some a)) t_void a.epos in
 			let evar_index = mk (TVar(v_index,Some a)) t_void a.epos in
@@ -382,7 +376,6 @@ module IterationKind = struct
 				ewhile;
 				ewhile;
 			]) t_void p
 			]) t_void p
 		| IteratorInt(a,b) ->
 		| IteratorInt(a,b) ->
-			check_loop_var_modification [v] e2;
 			let v_index = gen_local ctx t_int a.epos in
 			let v_index = gen_local ctx t_int a.epos in
 			let evar_index = mk (TVar(v_index,Some a)) t_void a.epos in
 			let evar_index = mk (TVar(v_index,Some a)) t_void a.epos in
 			let ev_index = make_local v_index v_index.v_pos in
 			let ev_index = make_local v_index v_index.v_pos in

+ 1 - 1
src/typing/nullSafety.ml

@@ -1370,7 +1370,7 @@ class expr_checker mode immediate_execution report =
 				(* Local named functions like `function fn() {}`, which are generated as `var fn = null; fn = function(){}` *)
 				(* Local named functions like `function fn() {}`, which are generated as `var fn = null; fn = function(){}` *)
 				| Some { eexpr = TConst TNull } when v.v_kind = VUser TVOLocalFunction -> ()
 				| Some { eexpr = TConst TNull } when v.v_kind = VUser TVOLocalFunction -> ()
 				(* `_this = null` is generated for local `inline function` *)
 				(* `_this = null` is generated for local `inline function` *)
-				| Some { eexpr = TConst TNull } when v.v_kind = VGenerated -> ()
+				(* | Some { eexpr = TConst TNull } when v.v_kind = VGenerated -> () *)
 				| Some e ->
 				| Some e ->
 					let local = { eexpr = TLocal v; epos = v.v_pos; etype = v.v_type } in
 					let local = { eexpr = TLocal v; epos = v.v_pos; etype = v.v_type } in
 					self#check_binop OpAssign local e p
 					self#check_binop OpAssign local e p

+ 1 - 10
src/typing/typeloadModule.ml

@@ -686,10 +686,7 @@ module TypeLevel = struct
 end
 end
 
 
 let make_curmod com g m =
 let make_curmod com g m =
-	let rl = new resolution_list ["import";s_type_path m.m_path] in
-	List.iter (fun mt ->
-		rl#add (module_type_resolution mt None null_pos))
-	(List.rev g.std_types.m_types);
+	let rl = g.root_typer.m.import_resolution#clone_as ["import";s_type_path m.m_path] in
 	{
 	{
 		curmod = m;
 		curmod = m;
 		import_resolution = rl;
 		import_resolution = rl;
@@ -706,12 +703,6 @@ let make_curmod com g m =
 let type_types_into_module com g m tdecls p =
 let type_types_into_module com g m tdecls p =
 	let ctx_m = TyperManager.clone_for_module g.root_typer (make_curmod com g m) in
 	let ctx_m = TyperManager.clone_for_module g.root_typer (make_curmod com g m) in
 	let imports_and_usings,decls = ModuleLevel.create_module_types ctx_m m tdecls p in
 	let imports_and_usings,decls = ModuleLevel.create_module_types ctx_m m tdecls p in
-	(* define the per-module context for the next pass *)
-	if ctx_m.g.std_types != null_module then begin
-		add_dependency m ctx_m.g.std_types MDepFromTyping;
-		(* this will ensure both String and (indirectly) Array which are basic types which might be referenced *)
-		ignore(load_instance ctx_m (make_ptp (mk_type_path (["std"],"String")) null_pos) ParamNormal LoadNormal)
-	end;
 	ModuleLevel.init_type_params ctx_m decls;
 	ModuleLevel.init_type_params ctx_m decls;
 	List.iter (TypeLevel.init_imports_or_using ctx_m) imports_and_usings;
 	List.iter (TypeLevel.init_imports_or_using ctx_m) imports_and_usings;
 	(* setup module types *)
 	(* setup module types *)

+ 4 - 8
src/typing/typerEntry.ml

@@ -7,17 +7,14 @@ open Resolution
 open Error
 open Error
 
 
 let load_std_types ctx =
 let load_std_types ctx =
-	ctx.g.std_types <- (try
+	let std_types = try
 		TypeloadModule.load_module ctx ([],"StdTypes") null_pos
 		TypeloadModule.load_module ctx ([],"StdTypes") null_pos
 	with
 	with
 		Error { err_message = Module_not_found ([],"StdTypes") } ->
 		Error { err_message = Module_not_found ([],"StdTypes") } ->
 			Error.raise_std_not_found ()
 			Error.raise_std_not_found ()
-	);
-	(* We always want core types to be available so we add them as default imports (issue #1904 and #3131). *)
-	List.iter (fun mt ->
-		ctx.m.import_resolution#add (module_type_resolution mt None null_pos))
-	(List.rev ctx.g.std_types.m_types);
+	in
 	List.iter (fun t ->
 	List.iter (fun t ->
+		ctx.m.import_resolution#add (module_type_resolution t None null_pos);
 		match t with
 		match t with
 		| TAbstractDecl a ->
 		| TAbstractDecl a ->
 			(match snd a.a_path with
 			(match snd a.a_path with
@@ -64,7 +61,7 @@ let load_std_types ctx =
 			end
 			end
 		| TEnumDecl _ | TClassDecl _ ->
 		| TEnumDecl _ | TClassDecl _ ->
 			()
 			()
-	) ctx.g.std_types.m_types
+	) (List.rev std_types.m_types)
 
 
 let load_string ctx =
 let load_string ctx =
 	let m = TypeloadModule.load_module ctx ([],"String") null_pos in
 	let m = TypeloadModule.load_module ctx ([],"String") null_pos in
@@ -139,7 +136,6 @@ let create com macros =
 			delayed_min_index = 0;
 			delayed_min_index = 0;
 			debug_delayed = [];
 			debug_delayed = [];
 			retain_meta = Common.defined com Define.RetainUntypedMeta;
 			retain_meta = Common.defined com Define.RetainUntypedMeta;
-			std_types = null_module;
 			global_using = [];
 			global_using = [];
 			complete = false;
 			complete = false;
 			type_hints = [];
 			type_hints = [];

+ 7 - 0
std/StringBuf.hx

@@ -88,6 +88,13 @@ class StringBuf {
 		b += (len == null ? s.substr(pos) : s.substr(pos, len));
 		b += (len == null ? s.substr(pos) : s.substr(pos, len));
 	}
 	}
 
 
+	/**
+		Removes all characters from `this` StringBuf, making it possible to reuse it.
+	**/
+	public inline function clear():Void {
+		b = "";
+	}
+
 	/**
 	/**
 		Returns the content of `this` StringBuf as String.
 		Returns the content of `this` StringBuf as String.
 
 

+ 19 - 11
std/cpp/_std/StringBuf.hx

@@ -21,31 +21,34 @@
  */
  */
 
 
 import cpp.NativeString;
 import cpp.NativeString;
-
-using cpp.NativeArray;
+import cpp.Pointer;
 
 
 @:coreApi
 @:coreApi
 class StringBuf {
 class StringBuf {
-	private var b:Array<String>;
+	private var b:Null<Array<String>> = null;
 
 
 	public var length(get, never):Int;
 	public var length(get, never):Int;
 
 
-	var charBuf:Array<cpp.Char>;
+	var charBuf:Null<Array<cpp.Char>> = null;
 
 
 	public function new():Void {}
 	public function new():Void {}
 
 
-	private function charBufAsString():String {
-		var len = charBuf.length;
-		charBuf.push(0);
-		return NativeString.fromGcPointer(charBuf.address(0), len);
+	private function drainCharBuf():String {
+		final buffer = this.charBuf;
+		final length = buffer.length;
+		buffer.push(0);
+		final bufferPtr = Pointer.arrayElem(buffer, 0);
+		final bufferString = NativeString.fromGcPointer(bufferPtr, length);
+		this.charBuf = null;
+		return bufferString;
 	}
 	}
 
 
 	private function flush():Void {
 	private function flush():Void {
+		final charBufAsString = drainCharBuf();
 		if (b == null)
 		if (b == null)
-			b = [charBufAsString()];
+			b = [charBufAsString];
 		else
 		else
-			b.push(charBufAsString());
-		charBuf = null;
+			b.push(charBufAsString);
 	}
 	}
 
 
 	function get_length():Int {
 	function get_length():Int {
@@ -89,6 +92,11 @@ class StringBuf {
 		}
 		}
 	}
 	}
 
 
+	public function clear():Void {
+		this.charBuf?.resize(0);
+		this.b?.resize(0);
+	}
+
 	public function toString():String {
 	public function toString():String {
 		if (charBuf != null)
 		if (charBuf != null)
 			flush();
 			flush();

+ 1 - 0
std/eval/_std/StringBuf.hx

@@ -27,5 +27,6 @@ extern class StringBuf {
 	function add<T>(x:T):Void;
 	function add<T>(x:T):Void;
 	function addChar(c:Int):Void;
 	function addChar(c:Int):Void;
 	function addSub(s:String, pos:Int, ?len:Int):Void;
 	function addSub(s:String, pos:Int, ?len:Int):Void;
+	function clear():Void;
 	function toString():String;
 	function toString():String;
 }
 }

+ 26 - 26
std/haxe/Serializer.hx

@@ -44,33 +44,33 @@ import haxe.ds.List;
 **/
 **/
 class Serializer {
 class Serializer {
 	/**
 	/**
-		Enables object caching during serialization to handle circular references and 
+		Enables object caching during serialization to handle circular references and
 		repeated objects.
 		repeated objects.
-	
-		Set `USE_CACHE` to `true` if the values you are serializing may contain 
-		circular references or repeated objects. This prevents infinite loops and 
+
+		Set `USE_CACHE` to `true` if the values you are serializing may contain
+		circular references or repeated objects. This prevents infinite loops and
 		ensures that shared references are preserved in the serialized output.
 		ensures that shared references are preserved in the serialized output.
-	
-		Enabling this option may also reduce the size of the resulting serialized 
+
+		Enabling this option may also reduce the size of the resulting serialized
 		string, but can have a minor performance impact.
 		string, but can have a minor performance impact.
-	
-		This is a global default. You can override it per instance using the 
+
+		This is a global default. You can override it per instance using the
 		`useCache` field on a `Serializer`.
 		`useCache` field on a `Serializer`.
 	 */
 	 */
 	public static var USE_CACHE = false;
 	public static var USE_CACHE = false;
 
 
 	/**
 	/**
 		Serializes enum values using constructor indices instead of names.
 		Serializes enum values using constructor indices instead of names.
-	
-		When `USE_ENUM_INDEX` is set to `true`, enum constructors are serialized by 
-		their numeric index. This can reduce the size of the serialized data, 
+
+		When `USE_ENUM_INDEX` is set to `true`, enum constructors are serialized by
+		their numeric index. This can reduce the size of the serialized data,
 		especially for enums with long or frequently used constructor names.
 		especially for enums with long or frequently used constructor names.
-	
-		However, using indices makes serialized data more fragile for long-term 
-		storage. If enum definitions change (e.g., by adding or removing constructors), 
+
+		However, using indices makes serialized data more fragile for long-term
+		storage. If enum definitions change (e.g., by adding or removing constructors),
 		the indices may no longer match the intended constructors.
 		the indices may no longer match the intended constructors.
-	
-		This is a global default. You can override it per instance using the 
+
+		This is a global default. You can override it per instance using the
 		`useEnumIndex` field on a `Serializer`.
 		`useEnumIndex` field on a `Serializer`.
 	 */
 	 */
 	public static var USE_ENUM_INDEX = false;
 	public static var USE_ENUM_INDEX = false;
@@ -84,20 +84,20 @@ class Serializer {
 	var scount:Int;
 	var scount:Int;
 
 
 	/**
 	/**
-	 	Determines whether this `Serializer` instance uses object caching.
-	
-	 	When enabled, repeated references to the same object are serialized using references 
-	 	instead of duplicating data, reducing output size and preserving object identity.
-	
-	 	See `USE_CACHE` for a complete description.
- 	*/
+		Determines whether this `Serializer` instance uses object caching.
+
+		When enabled, repeated references to the same object are serialized using references
+		instead of duplicating data, reducing output size and preserving object identity.
+
+		See `USE_CACHE` for a complete description.
+	 */
 	public var useCache:Bool;
 	public var useCache:Bool;
 
 
 	/**
 	/**
 		Determines whether this `Serializer` instance serializes enum values using their index
 		Determines whether this `Serializer` instance serializes enum values using their index
 		instead of their constructor name.
 		instead of their constructor name.
 
 
-		Using indexes can reduce the size of the serialized data but may be less readable and 
+		Using indexes can reduce the size of the serialized data but may be less readable and
 		more fragile if enum definitions change.
 		more fragile if enum definitions change.
 
 
 		See `USE_ENUM_INDEX` for a complete description.
 		See `USE_ENUM_INDEX` for a complete description.
@@ -125,12 +125,12 @@ class Serializer {
 
 
 	/**
 	/**
 		Resets the internal state of the Serializer, allowing it to be reused.
 		Resets the internal state of the Serializer, allowing it to be reused.
-		
+
 		This does not affect the `useCache` or `useEnumIndex` properties;
 		This does not affect the `useCache` or `useEnumIndex` properties;
 		their values will remain unchanged after calling this method.
 		their values will remain unchanged after calling this method.
 	**/
 	**/
 	public function reset() {
 	public function reset() {
-		buf = new StringBuf();
+		buf.clear();
 		cache.resize(0);
 		cache.resize(0);
 		shash.clear();
 		shash.clear();
 		scount = 0;
 		scount = 0;

+ 3 - 1
std/haxe/http/HttpNodeJs.hx

@@ -123,7 +123,9 @@ class HttpNodeJs extends haxe.http.HttpBase {
 				req.setHeader("Content-Length", '${postBytes.length}');
 				req.setHeader("Content-Length", '${postBytes.length}');
 				req.write(Buffer.from(postBytes.getData()));
 				req.write(Buffer.from(postBytes.getData()));
 			}
 			}
-
+		req.on('error', function(e) {
+            		onError(e);
+         	});
 		req.end();
 		req.end();
 	}
 	}
 }
 }

+ 12 - 4
std/hl/_std/StringBuf.hx

@@ -27,6 +27,10 @@
 	public var length(get, never):Int;
 	public var length(get, never):Int;
 
 
 	public function new():Void {
 	public function new():Void {
+		initialize();
+	}
+
+	inline function initialize():Void {
 		pos = 0;
 		pos = 0;
 		size = 8; // ensure 4 bytes expand for addChar()
 		size = 8; // ensure 4 bytes expand for addChar()
 		b = new hl.Bytes(size);
 		b = new hl.Bytes(size);
@@ -55,16 +59,16 @@
 
 
 	public function add<T>(x:T):Void {
 	public function add<T>(x:T):Void {
 		var slen = 0;
 		var slen = 0;
-		var str = Std.downcast((x:Dynamic),String);
-		if( str != null ) {
-			__add(@:privateAccess str.bytes, 0, str.length<<1);
+		var str = Std.downcast((x : Dynamic), String);
+		if (str != null) {
+			__add(@:privateAccess str.bytes, 0, str.length << 1);
 			return;
 			return;
 		}
 		}
 		var sbytes = hl.Bytes.fromValue(x, new hl.Ref(slen));
 		var sbytes = hl.Bytes.fromValue(x, new hl.Ref(slen));
 		__add(sbytes, 0, slen << 1);
 		__add(sbytes, 0, slen << 1);
 	}
 	}
 
 
-	public function addSub(s:String, pos:Int, ?len:Int):Void@:privateAccess {
+	public function addSub(s:String, pos:Int, ?len:Int):Void @:privateAccess {
 		if (pos < 0)
 		if (pos < 0)
 			pos = 0;
 			pos = 0;
 		if (pos >= s.length)
 		if (pos >= s.length)
@@ -101,6 +105,10 @@
 			throw "Invalid unicode char " + c;
 			throw "Invalid unicode char " + c;
 	}
 	}
 
 
+	public function clear():Void {
+		initialize();
+	}
+
 	public function toString():String {
 	public function toString():String {
 		if (pos + 2 > size)
 		if (pos + 2 > size)
 			__expand(0);
 			__expand(0);

+ 4 - 0
std/jvm/_std/StringBuf.hx

@@ -99,6 +99,10 @@ class StringBuf {
 		b.appendCodePoint(c);
 		b.appendCodePoint(c);
 	}
 	}
 
 
+	public function clear():Void {
+		b.setLength(0);
+	}
+
 	public function toString():String {
 	public function toString():String {
 		return b.toString();
 		return b.toString();
 	}
 	}

+ 29 - 10
std/lua/_std/StringBuf.hx

@@ -20,15 +20,25 @@
  * DEALINGS IN THE SOFTWARE.
  * DEALINGS IN THE SOFTWARE.
  */
  */
 
 
+import lua.Lua;
 import lua.Table;
 import lua.Table;
 
 
 class StringBuf {
 class StringBuf {
-	var b:Table<Int, String>;
+	private var b:Table<Int, String>;
+
+	/**
+		Count of "good" elements in the internal buffer table.
+
+		If `this` StringBuf has been `clear`ed previously,
+		this value might not be equal to the length (`#`) of that table.
+	**/
+	private var bufferLength:Int;
 
 
 	public var length(get, null):Int;
 	public var length(get, null):Int;
 
 
 	public inline function new() {
 	public inline function new() {
 		b = Table.create();
 		b = Table.create();
+		this.bufferLength = 0;
 		this.length = 0;
 		this.length = 0;
 	}
 	}
 
 
@@ -37,23 +47,32 @@ class StringBuf {
 	}
 	}
 
 
 	public inline function add<T>(x:T):Void {
 	public inline function add<T>(x:T):Void {
-		var str = Std.string(x);
-		Table.insert(b, str);
-		length += str.length;
+		final str = Std.string(x);
+		final i = this.bufferLength += 1;
+		Lua.rawset(this.b, i, str);
+		this.length += str.length;
 	}
 	}
 
 
 	public inline function addChar(c:Int):Void {
 	public inline function addChar(c:Int):Void {
-		Table.insert(b, String.fromCharCode(c));
-		length += 1;
+		final i = this.bufferLength += 1;
+		Lua.rawset(this.b, i, String.fromCharCode(c));
+		this.length += 1;
 	}
 	}
 
 
 	public inline function addSub(s:String, pos:Int, ?len:Int):Void {
 	public inline function addSub(s:String, pos:Int, ?len:Int):Void {
-		var part = len == null ? s.substr(pos) : s.substr(pos, len);
-		Table.insert(b, part);
-		length += part.length;
+		this.add(s.substr(pos, len));
+	}
+
+	public inline function clear():Void {
+		this.bufferLength = 0;
+		this.length = 0;
 	}
 	}
 
 
 	public inline function toString():String {
 	public inline function toString():String {
-		return Table.concat(b);
+		final len = this.bufferLength;
+		if (len == 0) {
+			return "";
+		}
+		return Table.concat(this.b, "", 1, len);
 	}
 	}
 }
 }

+ 5 - 0
std/neko/_std/StringBuf.hx

@@ -45,6 +45,10 @@
 			__add_char(b, c);
 			__add_char(b, c);
 		}
 		}
 
 
+	public inline function clear():Void {
+		buffer_reset(b);
+	}
+
 	public inline function toString():String {
 	public inline function toString():String {
 		return new String(__to_string(b));
 		return new String(__to_string(b));
 	}
 	}
@@ -54,5 +58,6 @@
 	static var __add_char:Dynamic = neko.Lib.load("std", "buffer_add_char", 2);
 	static var __add_char:Dynamic = neko.Lib.load("std", "buffer_add_char", 2);
 	static var __add_sub:Dynamic = neko.Lib.load("std", "buffer_add_sub", 4);
 	static var __add_sub:Dynamic = neko.Lib.load("std", "buffer_add_sub", 4);
 	static var __to_string:Dynamic = neko.Lib.load("std", "buffer_string", 1);
 	static var __to_string:Dynamic = neko.Lib.load("std", "buffer_string", 1);
+	static var buffer_reset:Dynamic = neko.Lib.load("std", "buffer_reset", 1);
 	static var __get_length:Dynamic = try neko.Lib.load("std", "buffer_get_length", 1) catch (e:Dynamic) null;
 	static var __get_length:Dynamic = try neko.Lib.load("std", "buffer_get_length", 1) catch (e:Dynamic) null;
 }
 }

+ 4 - 0
std/php/_std/StringBuf.hx

@@ -56,6 +56,10 @@ import php.Syntax;
 		b += String.fromCharCode(c);
 		b += String.fromCharCode(c);
 	}
 	}
 
 
+	public inline function clear():Void {
+		b = "";
+	}
+
 	public inline function toString():String {
 	public inline function toString():String {
 		return b;
 		return b;
 	}
 	}

+ 9 - 8
std/python/_std/StringBuf.hx

@@ -20,7 +20,6 @@
  * DEALINGS IN THE SOFTWARE.
  * DEALINGS IN THE SOFTWARE.
  */
  */
 
 
-import python.lib.io.IOBase.SeekSet;
 import python.lib.io.StringIO;
 import python.lib.io.StringIO;
 
 
 @:coreApi
 @:coreApi
@@ -34,11 +33,7 @@ class StringBuf {
 	public var length(get, never):Int;
 	public var length(get, never):Int;
 
 
 	function get_length():Int {
 	function get_length():Int {
-		var pos = b.tell();
-		b.seek(0, SeekEnd);
-		var len = b.tell();
-		b.seek(pos, SeekSet);
-		return len;
+		return b.tell();
 	}
 	}
 
 
 	public inline function add<T>(x:T):Void {
 	public inline function add<T>(x:T):Void {
@@ -57,7 +52,13 @@ class StringBuf {
 		add1((len == null ? s.substr(pos) : s.substr(pos, len)));
 		add1((len == null ? s.substr(pos) : s.substr(pos, len)));
 	}
 	}
 
 
-	public inline function toString():String {
-		return b.getvalue();
+	public inline function clear():Void {
+		b.seek(0, SeekSet);
+	}
+
+	public function toString():String {
+		final length = this.length;
+		b.seek(0, SeekSet);
+		return b.read(length);
 	}
 	}
 }
 }

+ 8 - 4
std/sys/Http.hx

@@ -98,6 +98,7 @@ class Http extends haxe.http.HttpBase {
 			return;
 			return;
 		}
 		}
 		var secure = (url_regexp.matched(1) == "https://");
 		var secure = (url_regexp.matched(1) == "https://");
+		var ownsSocket = false;
 		if (sock == null) {
 		if (sock == null) {
 			if (secure) {
 			if (secure) {
 				#if php
 				#if php
@@ -117,6 +118,7 @@ class Http extends haxe.http.HttpBase {
 				sock = new Socket();
 				sock = new Socket();
 			}
 			}
 			sock.setTimeout(cnxTimeout);
 			sock.setTimeout(cnxTimeout);
+			ownsSocket = true;
 		}
 		}
 		var host = url_regexp.matched(2);
 		var host = url_regexp.matched(2);
 		var portString = url_regexp.matched(3);
 		var portString = url_regexp.matched(3);
@@ -246,11 +248,13 @@ class Http extends haxe.http.HttpBase {
 			else
 			else
 				writeBody(b, null, 0, null, sock);
 				writeBody(b, null, 0, null, sock);
 			readHttpResponse(api, sock);
 			readHttpResponse(api, sock);
-			sock.close();
+			if (ownsSocket)
+				sock.close();
 		} catch (e:Dynamic) {
 		} catch (e:Dynamic) {
-			try
-				sock.close()
-			catch (e:Dynamic) {};
+			if (ownsSocket)
+				try
+					sock.close()
+				catch (e:Dynamic) {};
 			onError(Std.string(e));
 			onError(Std.string(e));
 		}
 		}
 	}
 	}

+ 0 - 7
tests/misc/projects/Issue7734/Main.hx

@@ -1,7 +0,0 @@
-class Main {
-	static public function main() {
-		for (i in 0...10) {
-			i++;
-		}
-	}
-}

+ 0 - 2
tests/misc/projects/Issue7734/compile-fail.hxml

@@ -1,2 +0,0 @@
---main Main
---interp

+ 0 - 1
tests/misc/projects/Issue7734/compile-fail.hxml.stderr

@@ -1 +0,0 @@
-Main.hx:4: characters 4-7 : Loop variable cannot be modified

+ 32 - 0
tests/misc/projects/Issue8574/Main.hx

@@ -0,0 +1,32 @@
+class Main {
+	static function main() {
+		var a = [];
+		for(i in #if noOpt 0...three() #else 0...3 #end) {
+			a.push(i);
+			i++;
+			a.push(i);
+		}
+		check([0,1, 1,2, 2,3], a);
+	}
+
+	@:pure(false)
+	static public function three():Int {
+		return 3;
+	}
+
+	static function check(a1:Array<Int>, a2:Array<Int>) {
+		if(a1.length != a2.length) {
+			fail(a1, a2);
+		}
+		for(i in 0...a1.length) {
+			if(a1[i] != a2[i]) {
+				fail(a1, a2);
+			}
+		}
+	}
+
+	static function fail(a1:Array<Int>, a2:Array<Int>) {
+		Sys.stderr().writeString('Arrays are not equal: $a1 != $a2\n');
+		Sys.exit(1);
+	}
+}

+ 3 - 0
tests/misc/projects/Issue8574/constIntIterator.hxml

@@ -0,0 +1,3 @@
+-main Main
+-D loop_unroll_max_cost=0
+--interp

+ 3 - 0
tests/misc/projects/Issue8574/noOptimization.hxml

@@ -0,0 +1,3 @@
+-main Main
+-D noOpt
+--interp

+ 3 - 0
tests/misc/projects/Issue8574/unrolledLoop.hxml

@@ -0,0 +1,3 @@
+-main Main
+-D loop_unroll_max_cost=1000
+--interp

+ 14 - 1
tests/nullsafety/src/cases/TestStrict.hx

@@ -289,6 +289,13 @@ class TestStrict {
 		shouldFail((true ? 'hello' : v).length);
 		shouldFail((true ? 'hello' : v).length);
 	}
 	}
 
 
+	static function inlineCallWithNullArg_shouldFail() {
+		inline function check(value: Int): Int {
+			return value;
+		}
+		shouldFail(check((null : Null<Int>)));
+	}
+
 	static function ternary_returnedFromInlinedFunction_shouldPass() {
 	static function ternary_returnedFromInlinedFunction_shouldPass() {
 		var str:String = inlinedNullableSafeString([null]);
 		var str:String = inlinedNullableSafeString([null]);
 	}
 	}
@@ -779,6 +786,12 @@ class TestStrict {
 				cb();
 				cb();
 			}
 			}
 		}
 		}
+		inline function cb2() {
+			if(foo != null) {
+				trace(foo);
+			}
+		}
+		cb2();
 	}
 	}
 
 
 	static public function closure_immediatelyExecuted_shouldInheritSafety(?s:String) {
 	static public function closure_immediatelyExecuted_shouldInheritSafety(?s:String) {
@@ -1068,4 +1081,4 @@ abstract NullFloat(Null<Float>) from Null<Float> to Null<Float> {
 	@:op(A + B) static inline function addOp1(lhs: NullFloat, rhs: Float): Float {
 	@:op(A + B) static inline function addOp1(lhs: NullFloat, rhs: Float): Float {
 		return lhs != null ? lhs.val() + rhs : rhs;
 		return lhs != null ? lhs.val() + rhs : rhs;
 	}
 	}
-}
+}

+ 32 - 0
tests/unit/src/unit/TestHttps.hx

@@ -1,5 +1,6 @@
 package unit;
 package unit;
 
 
+import utest.Assert;
 import utest.Async;
 import utest.Async;
 
 
 class TestHttps extends Test {
 class TestHttps extends Test {
@@ -59,4 +60,35 @@ class TestHttps extends Test {
 		}
 		}
 		req.request();
 		req.request();
 	});
 	});
+
+	#if sys
+	@:timeout(3000)
+	public function testCustomRequestWithSocket(async:Async) run(async, () -> {
+		final url = 'http://build.haxe.org/builds/haxe/linux64/haxe_latest.tar.gz';
+
+		final http = new haxe.Http(url);
+		final socket = new sys.net.Socket();
+		socket.setTimeout(10);
+		final output = new haxe.io.BytesOutput();
+		http.customRequest(false, output, socket);
+
+		try {
+			// our socket shouldn't be closed until we close it,
+			// so this should work
+			Assert.notNull(socket.host());
+			socket.setTimeout(10);
+			noAssert();
+		} catch (e) {
+			assert('socket should be unclosed, but got error: $e');
+		}
+
+		try {
+			socket.close();
+			noAssert();
+		} catch (e) {
+			assert("Failed to close socket");
+		}
+		async.done();
+	});
+	#end
 }
 }

+ 11 - 0
tests/unit/src/unit/issues/Issue12135.hx

@@ -0,0 +1,11 @@
+package unit.issues;
+
+enum Foo12135 {
+	Foo:Null<Foo12135>;
+}
+
+class Issue12135 extends Test {
+	function test() {
+		eq(Foo12135.Foo, Foo12135.Foo);
+	}
+}

+ 41 - 1
tests/unit/src/unitstd/StringBuf.unit.hx

@@ -1,6 +1,7 @@
 // add, toString
 // add, toString
 var x = new StringBuf();
 var x = new StringBuf();
 x.toString() == "";
 x.toString() == "";
+x.length == 0;
 x.add(null);
 x.add(null);
 x.toString() == "null";
 x.toString() == "null";
 
 
@@ -37,8 +38,47 @@ x.addSub("a👽b", 1, 1);
 x.toString() == "👽";
 x.toString() == "👽";
 #end
 #end
 
 
+// StringBuf can store multiple elements
+final x = new StringBuf();
+x.add("ab");
+x.add("cd");
+x.addChar("e".code);
+x.add("fg");
+x.toString() == "abcdefg";
+
+// Calling toString() does not empty the buffer
+x.toString() == "abcdefg";
+x.toString() == "abcdefg";
+x.length == 7;
+
 // identity
 // identity
 function identityTest(s:StringBuf) {
 function identityTest(s:StringBuf) {
 	return s;
 	return s;
 }
 }
-identityTest(x) == x;
+identityTest(x) == x;
+
+// Clearing a buffer resets its visible state
+x.length > 0;
+x.clear();
+x.toString() == "";
+x.length == 0;
+
+// Previously cleared buffers do not leak past state
+x.add("foo");
+x.toString() == "foo";
+x.length == 3;
+
+// Buffers can be cleared multiple times
+x.clear();
+x.length == 0;
+x.clear();
+x.clear();
+x.clear();
+x.length == 0;
+
+// Buffers can be cleared immediately after creation
+// (ie. `clear` does not depend on any private state being non-null)
+final x = new StringBuf();
+x.clear();
+x.toString() == "";
+x.length == 0;