Forráskód Böngészése

Merge branch 'development' into haxe.Unit

Simon Krajewski 5 hónapja
szülő
commit
51dc4a85a7

+ 11 - 0
src/context/resolution.ml

@@ -180,6 +180,17 @@ class resolution_list (id : string list) = object(self)
 	method get_list =
 		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 =
 		let rec loop = function
 		| [] ->

+ 0 - 1
src/context/typecore.ml

@@ -97,7 +97,6 @@ type typer_globals = {
 	retain_meta : bool;
 	mutable core_api : 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 global_using : (tclass * pos) list;
 	(* Indicates that Typer.create() finished building this instance *)

+ 1 - 1
src/generators/genhl.ml

@@ -770,7 +770,7 @@ and enum_class ctx e =
 			fid
 		in
 			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))
 				| TFun (args, ret) ->
 					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
 	)
 
+	let clear = vifun0 (fun vthis ->
+		let this = this vthis in
+		VStringBuffer.clear this;
+		vnull
+	)
+
 	let get_length = vifun0 (fun vthis ->
 		let this = this vthis in
 		vint this.blength
@@ -3691,6 +3697,7 @@ let init_standard_library builtins =
 		"add",StdStringBuf.add;
 		"addChar",StdStringBuf.addChar;
 		"addSub",StdStringBuf.addSub;
+		"clear",StdStringBuf.clear;
 		"get_length",StdStringBuf.get_length;
 		"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;
 		this.blength <- this.blength + c_len
 
+	let clear this =
+		Buffer.clear this.bbuffer;
+		this.blength <- 0
+
 	let contents this =
 		create_with_length (Buffer.contents this.bbuffer) this.blength
 end

+ 10 - 17
src/typing/forLoop.ml

@@ -297,19 +297,6 @@ module IterationKind = struct
 		let get_array_length arr p =
 			mk (mk_field arr "length") ctx.com.basic.tint p
 		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 index = gen_local ctx t_int v.v_pos in
 			index.v_meta <- (Meta.ForLoopVariable,[],null_pos) :: index.v_meta;
@@ -340,7 +327,6 @@ module IterationKind = struct
 		in
 		match iterator.it_kind with
 		| IteratorIntUnroll(offset,length,ascending,unroll_params) ->
-			check_loop_var_modification [v] e2;
 			if not ascending then raise_typing_error "Cannot iterate backwards" p;
 			let rec unroll acc i =
 				if i = length then
@@ -351,6 +337,8 @@ module IterationKind = struct
 					let rec loop e = match e.eexpr with
 					| TLocal v' when v == v' ->
 						{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 ->
 						if acc = [] then
 							local_vars := {e with eexpr = TVar(v,eo)} :: !local_vars;
@@ -358,7 +346,14 @@ module IterationKind = struct
 					| _ ->
 						map_expr loop e
 				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 e2 = Texpr.duplicate_tvars e_identity e2 in
 				unroll (e2 :: acc) (i + 1)
@@ -366,7 +361,6 @@ module IterationKind = struct
 			let el = unroll [] 0 in
 			mk (TBlock el) t_void p
 		| IteratorIntConst(a,b,ascending) ->
-			check_loop_var_modification [v] e2;
 			if not ascending then raise_typing_error "Cannot iterate backwards" p;
 			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
@@ -382,7 +376,6 @@ module IterationKind = struct
 				ewhile;
 			]) t_void p
 		| IteratorInt(a,b) ->
-			check_loop_var_modification [v] e2;
 			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 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(){}` *)
 				| Some { eexpr = TConst TNull } when v.v_kind = VUser TVOLocalFunction -> ()
 				(* `_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 ->
 					let local = { eexpr = TLocal v; epos = v.v_pos; etype = v.v_type } in
 					self#check_binop OpAssign local e p

+ 1 - 10
src/typing/typeloadModule.ml

@@ -686,10 +686,7 @@ module TypeLevel = struct
 end
 
 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;
 		import_resolution = rl;
@@ -706,12 +703,6 @@ let make_curmod com g m =
 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 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;
 	List.iter (TypeLevel.init_imports_or_using ctx_m) imports_and_usings;
 	(* setup module types *)

+ 4 - 8
src/typing/typerEntry.ml

@@ -7,17 +7,14 @@ open Resolution
 open Error
 
 let load_std_types ctx =
-	ctx.g.std_types <- (try
+	let std_types = try
 		TypeloadModule.load_module ctx ([],"StdTypes") null_pos
 	with
 		Error { err_message = Module_not_found ([],"StdTypes") } ->
 			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 ->
+		ctx.m.import_resolution#add (module_type_resolution t None null_pos);
 		match t with
 		| TAbstractDecl a ->
 			(match snd a.a_path with
@@ -64,7 +61,7 @@ let load_std_types ctx =
 			end
 		| TEnumDecl _ | TClassDecl _ ->
 			()
-	) ctx.g.std_types.m_types
+	) (List.rev std_types.m_types)
 
 let load_string ctx =
 	let m = TypeloadModule.load_module ctx ([],"String") null_pos in
@@ -139,7 +136,6 @@ let create com macros =
 			delayed_min_index = 0;
 			debug_delayed = [];
 			retain_meta = Common.defined com Define.RetainUntypedMeta;
-			std_types = null_module;
 			global_using = [];
 			complete = false;
 			type_hints = [];

+ 7 - 0
std/StringBuf.hx

@@ -88,6 +88,13 @@ class StringBuf {
 		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.
 

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

@@ -21,31 +21,34 @@
  */
 
 import cpp.NativeString;
-
-using cpp.NativeArray;
+import cpp.Pointer;
 
 @:coreApi
 class StringBuf {
-	private var b:Array<String>;
+	private var b:Null<Array<String>> = null;
 
 	public var length(get, never):Int;
 
-	var charBuf:Array<cpp.Char>;
+	var charBuf:Null<Array<cpp.Char>> = null;
 
 	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 {
+		final charBufAsString = drainCharBuf();
 		if (b == null)
-			b = [charBufAsString()];
+			b = [charBufAsString];
 		else
-			b.push(charBufAsString());
-		charBuf = null;
+			b.push(charBufAsString);
 	}
 
 	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 {
 		if (charBuf != null)
 			flush();

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

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

+ 26 - 26
std/haxe/Serializer.hx

@@ -44,33 +44,33 @@ import haxe.ds.List;
 **/
 class Serializer {
 	/**
-		Enables object caching during serialization to handle circular references and 
+		Enables object caching during serialization to handle circular references and
 		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.
-	
-		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.
-	
-		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`.
 	 */
 	public static var USE_CACHE = false;
 
 	/**
 		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.
-	
-		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.
-	
-		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`.
 	 */
 	public static var USE_ENUM_INDEX = false;
@@ -84,20 +84,20 @@ class Serializer {
 	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;
 
 	/**
 		Determines whether this `Serializer` instance serializes enum values using their index
 		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.
 
 		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.
-		
+
 		This does not affect the `useCache` or `useEnumIndex` properties;
 		their values will remain unchanged after calling this method.
 	**/
 	public function reset() {
-		buf = new StringBuf();
+		buf.clear();
 		cache.resize(0);
 		shash.clear();
 		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.write(Buffer.from(postBytes.getData()));
 			}
-
+		req.on('error', function(e) {
+            		onError(e);
+         	});
 		req.end();
 	}
 }

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

@@ -27,6 +27,10 @@
 	public var length(get, never):Int;
 
 	public function new():Void {
+		initialize();
+	}
+
+	inline function initialize():Void {
 		pos = 0;
 		size = 8; // ensure 4 bytes expand for addChar()
 		b = new hl.Bytes(size);
@@ -55,16 +59,16 @@
 
 	public function add<T>(x:T):Void {
 		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;
 		}
 		var sbytes = hl.Bytes.fromValue(x, new hl.Ref(slen));
 		__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)
 			pos = 0;
 		if (pos >= s.length)
@@ -101,6 +105,10 @@
 			throw "Invalid unicode char " + c;
 	}
 
+	public function clear():Void {
+		initialize();
+	}
+
 	public function toString():String {
 		if (pos + 2 > size)
 			__expand(0);

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

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

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

@@ -20,15 +20,25 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
+import lua.Lua;
 import lua.Table;
 
 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 inline function new() {
 		b = Table.create();
+		this.bufferLength = 0;
 		this.length = 0;
 	}
 
@@ -37,23 +47,32 @@ class StringBuf {
 	}
 
 	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 {
-		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 {
-		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 {
-		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);
 		}
 
+	public inline function clear():Void {
+		buffer_reset(b);
+	}
+
 	public inline function toString():String {
 		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_sub:Dynamic = neko.Lib.load("std", "buffer_add_sub", 4);
 	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;
 }

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

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

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

@@ -20,7 +20,6 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
-import python.lib.io.IOBase.SeekSet;
 import python.lib.io.StringIO;
 
 @:coreApi
@@ -34,11 +33,7 @@ class StringBuf {
 	public var length(get, never):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 {
@@ -57,7 +52,13 @@ class StringBuf {
 		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;
 		}
 		var secure = (url_regexp.matched(1) == "https://");
+		var ownsSocket = false;
 		if (sock == null) {
 			if (secure) {
 				#if php
@@ -117,6 +118,7 @@ class Http extends haxe.http.HttpBase {
 				sock = new Socket();
 			}
 			sock.setTimeout(cnxTimeout);
+			ownsSocket = true;
 		}
 		var host = url_regexp.matched(2);
 		var portString = url_regexp.matched(3);
@@ -246,11 +248,13 @@ class Http extends haxe.http.HttpBase {
 			else
 				writeBody(b, null, 0, null, sock);
 			readHttpResponse(api, sock);
-			sock.close();
+			if (ownsSocket)
+				sock.close();
 		} catch (e:Dynamic) {
-			try
-				sock.close()
-			catch (e:Dynamic) {};
+			if (ownsSocket)
+				try
+					sock.close()
+				catch (e:Dynamic) {};
 			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);
 	}
 
+	static function inlineCallWithNullArg_shouldFail() {
+		inline function check(value: Int): Int {
+			return value;
+		}
+		shouldFail(check((null : Null<Int>)));
+	}
+
 	static function ternary_returnedFromInlinedFunction_shouldPass() {
 		var str:String = inlinedNullableSafeString([null]);
 	}
@@ -779,6 +786,12 @@ class TestStrict {
 				cb();
 			}
 		}
+		inline function cb2() {
+			if(foo != null) {
+				trace(foo);
+			}
+		}
+		cb2();
 	}
 
 	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 {
 		return lhs != null ? lhs.val() + rhs : rhs;
 	}
-}
+}

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

@@ -1,5 +1,6 @@
 package unit;
 
+import utest.Assert;
 import utest.Async;
 
 class TestHttps extends Test {
@@ -59,4 +60,35 @@ class TestHttps extends Test {
 		}
 		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
 var x = new StringBuf();
 x.toString() == "";
+x.length == 0;
 x.add(null);
 x.toString() == "null";
 
@@ -37,8 +38,47 @@ x.addSub("a👽b", 1, 1);
 x.toString() == "👽";
 #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
 function identityTest(s:StringBuf) {
 	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;